Giới thiệu về API Ghi và phát lại ARCore

1. Giới thiệu

Khả năng lưu trải nghiệm thực tế tăng cường vào tệp MP4 và phát lại từ tệp MP4 có thể hữu ích cho cả nhà phát triển ứng dụng và người dùng cuối.

Gỡ lỗi và thử nghiệm các tính năng mới trên không gian làm việc

Cách sử dụng đơn giản nhất của Bản ghi ARCore và Playback API dành cho nhà phát triển. Còn đâu cái thời mà bạn phải tạo và chạy ứng dụng trên thiết bị thử nghiệm, ngắt kết nối cáp USB và đi bộ xung quanh chỉ để thử nghiệm một thay đổi nhỏ về mã. Giờ đây, bạn chỉ cần ghi một tệp MP4 trong môi trường thử nghiệm với khả năng di chuyển dự kiến của điện thoại và thử nghiệm ngay trên bàn làm việc.

Ghi hình và phát lại trên nhiều thiết bị

Với API Ghi và Phát, một người dùng có thể ghi lại phiên bằng một thiết bị và người dùng khác có thể phát lại phiên đó trên thiết bị khác. Bạn có thể chia sẻ trải nghiệm thực tế tăng cường với một người dùng khác. Có rất nhiều khả năng!

Đây có phải là lần đầu tiên bạn tạo ứng dụng ARCore?

Không. Có.

Bạn sẽ sử dụng lớp học lập trình này như thế nào?

Chỉ đọc qua Đọc và hoàn thành bài tập

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn sẽ sử dụng tính năng Bản ghi và Playback API (API Phát) để tạo một ứng dụng vừa ghi lại trải nghiệm thực tế tăng cường vào tệp MP4, vừa phát lại trải nghiệm từ cùng một tệp. Bạn sẽ tìm hiểu:

  • Cách sử dụng API ghi để lưu một phiên thực tế tăng cường vào tệp MP4.
  • Cách sử dụng Playback API để phát lại một phiên thực tế tăng cường (AR) từ tệp MP4.
  • Cách ghi lại một phiên thực tế tăng cường trên một thiết bị và phát lại phiên đó trên một thiết bị khác.

Bạn cần có

Trong lớp học lập trình này, bạn sẽ sửa đổi ứng dụng Hello AR Java được xây dựng bằng SDK Android ARCore. Bạn cần có phần cứng và phần mềm cụ thể để theo dõi.

Yêu cầu về phần cứng

  • Một thiết bị hỗ trợ ARCore đã bật Tuỳ chọn cho nhà phát triển. và tính năng gỡ lỗi qua USB đã được bật, được kết nối qua cáp USB với máy phát triển của bạn.
  • Máy phát triển để bạn chạy Android Studio.
  • Truy cập Internet, để tải thư viện xuống trong quá trình phát triển.

Yêu cầu về phần mềm

Bạn cũng cần có hiểu biết cơ bản về ARCore để có kết quả tốt nhất.

2. Thiết lập môi trường phát triển

Hãy bắt đầu bằng cách thiết lập môi trường phát triển của bạn.

Tải SDK Android ARCore xuống

Nhấp vào xuống để tải SDK xuống.

Giải nén SDK Android ARCore

Sau khi bạn tải SDK Android xuống máy, hãy giải nén tệp rồi chuyển đến thư mục arcore-android-sdk-1.24/samples/hello_ar_java. Đây là thư mục gốc của ứng dụng mà bạn sẽ làm việc.

hello-ar-java-extracted

Tải Hello AR Java vào Android Studio

Chạy Android Studio rồi nhấp vào Open an existing Android Studio project (Mở một dự án hiện có trong Android Studio).

android-studio-open-projects

Trong cửa sổ hộp thoại thu được, hãy chọn arcore-android-sdk-1.24/samples/hello_ar_java rồi nhấp vào Open (Mở).

Chờ Android Studio hoàn tất quá trình đồng bộ hoá dự án. Nếu thiếu thành phần, việc nhập dự án có thể không thành công kèm theo thông báo lỗi. Hãy khắc phục những vấn đề này trước khi tiếp tục.

Chạy ứng dụng mẫu

  1. Kết nối thiết bị được hỗ trợ ARCore với máy phát triển của bạn.
  2. Nếu hệ thống nhận dạng được thiết bị đúng cách, bạn sẽ thấy tên thiết bị xuất hiện trong Android Studio. android-studio-pixel-5.png
  3. Nhấp vào nút Run (Chạy) hoặc chọn Run > (Chạy >) Chạy "app" (ứng dụng) để Android Studio cài đặt và khởi chạy ứng dụng trên thiết bị. android-studio-run-button.png
  4. Bạn sẽ thấy một thông báo yêu cầu bạn cấp quyền chụp ảnh và quay video. Chọn Trong khi dùng ứng dụng này để cấp quyền truy cập Máy ảnh cho ứng dụng. Sau đó, bạn sẽ thấy môi trường thực tế của mình trên màn hình của thiết bị. hello-ar-java-permission
  5. Di chuyển thiết bị theo chiều ngang để quét tìm các máy bay.
  6. Một lưới màu trắng sẽ xuất hiện khi ứng dụng phát hiện thấy một máy bay. Nhấn vào đó để đặt điểm đánh dấu trên mặt phẳng đó. Xin chào vị trí thực tế tăng cường

Những việc bạn đã làm ở bước này

  • Thiết lập dự án Hello AR Java
  • Đã tạo và chạy ứng dụng mẫu trên thiết bị được hỗ trợ ARCore

Tiếp theo, bạn sẽ ghi một phiên AR vào tệp MP4.

3. Ghi phiên ARCore vào tệp MP4

Chúng ta sẽ thêm tính năng ghi lại trong bước này. Cấu trúc này bao gồm:

  • Nút để bắt đầu hoặc dừng ghi.
  • Các chức năng lưu trữ để lưu tệp MP4 trên thiết bị.
  • Lệnh gọi để bắt đầu hoặc dừng ghi phiên ARCore.

Thêm giao diện người dùng cho nút Ghi

Trước khi triển khai quá trình ghi, hãy thêm một nút trên giao diện người dùng để người dùng có thể thông báo cho ARCore biết thời điểm bắt đầu hoặc dừng ghi.

Trong bảng điều khiển Project (Dự án), hãy mở tệp app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

Theo mặc định, Android Studio sẽ sử dụng thành phần hiển thị thiết kế sau khi bạn mở tệp app/res/layout/activity_main.xml. Nhấp vào nút ở góc trên cùng bên phải của thẻ để chuyển sang chế độ xem mã.

swith-to-the-code-view.png

Trong activity_main.xml, hãy thêm mã sau đây trước thẻ đóng để tạo nút Record (Ghi) mới và thiết lập trình xử lý sự kiện của thành phần này thành một phương thức tên là onClickRecord():

  <!--
    Add a new "Record" button with those attributes:
        text is "Record",
        onClick event handler is "onClickRecord",
        text color is "red".
  -->
  <Button
      android:id="@+id/record_button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignLeft="@id/surfaceview"
      android:layout_alignBottom="@id/surfaceview"
      android:layout_marginBottom="100dp"
      android:onClick="onClickRecord"
      android:text="Record"
      android:textColor="@android:color/holo_red_light" />

Sau khi bạn thêm mã ở trên, một lỗi có thể tạm thời hiển thị: Corresponding method handler 'public void onClickRecord(android.view.View)' not found". Lỗi này có thể xảy ra. Bạn sẽ khắc phục lỗi này bằng cách tạo hàm onClickRecord() trong các bước tiếp theo.

Thay đổi văn bản trên nút theo trạng thái

Trên thực tế, nút Record (Ghi) xử lý cả quá trình ghi và dừng. Khi không ghi lại dữ liệu, ứng dụng sẽ hiển thị từ "Record". Khi ứng dụng đang ghi dữ liệu, nút này sẽ thay đổi để cho thấy từ "Dừng".

Để cung cấp chức năng này cho nút, ứng dụng phải biết trạng thái hiện tại của nút. Đoạn mã sau đây sẽ tạo một enum mới có tên là AppState để thể hiện trạng thái hoạt động của ứng dụng và theo dõi các thay đổi cụ thể về trạng thái thông qua biến thành phần riêng tư có tên là appState. Thêm phương thức này vào HelloArActivity.java, ở đầu lớp HelloArActivity.

  // Represents the app's working state.
  public enum AppState {
    Idle,
    Recording
  }

  // Tracks app's specific state changes.
  private AppState appState = AppState.Idle;

Giờ đây, khi đã có thể theo dõi trạng thái nội bộ của ứng dụng, bạn hãy tạo một hàm có tên là updateRecordButton(). Hàm này sẽ thay đổi văn bản của nút dựa trên trạng thái hiện tại của ứng dụng. Hãy thêm mã sau đây vào lớp HelloArActivity trong HelloArActivity.java.

// Add imports to the beginning of the file.
import android.widget.Button;

  // Update the "Record" button based on app's internal state.
  private void updateRecordButton() {
    View buttonView = findViewById(R.id.record_button);
    Button button = (Button) buttonView;

    switch (appState) {
      case Idle:
        button.setText("Record");
        break;
      case Recording:
        button.setText("Stop");
        break;
    }
  }

Tiếp theo, hãy tạo phương thức onClickRecord() để kiểm tra trạng thái ứng dụng, thay đổi thành phương thức tiếp theo rồi gọi updateRecordButton() để thay đổi giao diện người dùng của nút. Hãy thêm mã sau đây vào lớp HelloArActivity trong HelloArActivity.java.

  // Handle the "Record" button click event.
  public void onClickRecord(View view) {
    Log.d(TAG, "onClickRecord");

    // Check the app's internal state and switch to the new state if needed.
    switch (appState) {
        // If the app is not recording, begin recording.
      case Idle: {
        boolean hasStarted = startRecording();
        Log.d(TAG, String.format("onClickRecord start: hasStarted %b", hasStarted));

        if (hasStarted)
          appState = AppState.Recording;

        break;
      }

      // If the app is recording, stop recording.
      case Recording: {
        boolean hasStopped = stopRecording();
        Log.d(TAG, String.format("onClickRecord stop: hasStopped %b", hasStopped));

        if (hasStopped)
          appState = AppState.Idle;

        break;
      }

      default:
        // Do nothing.
        break;
    }

    updateRecordButton();
  }

Cho phép ứng dụng bắt đầu quay

Bạn chỉ cần làm hai việc để bắt đầu ghi trong ARCore:

  1. Chỉ định URI tệp bản ghi trong đối tượng RecordingConfig.
  2. Gọi session.startRecording bằng đối tượng RecordingConfig

Phần còn lại chỉ là mã nguyên mẫu: định cấu hình, ghi nhật ký và kiểm tra tính chính xác.

Tạo một hàm mới có tên là startRecording(). Hàm này sẽ ghi lại dữ liệu và lưu vào URI MP4. Hãy thêm mã sau đây vào lớp HelloArActivity trong HelloArActivity.java.

// Add imports to the beginning of the file.
import android.net.Uri;
import com.google.ar.core.RecordingConfig;
import com.google.ar.core.RecordingStatus;
import com.google.ar.core.exceptions.RecordingFailedException;

  private boolean startRecording() {
    Uri mp4FileUri = createMp4File();
    if (mp4FileUri == null)
      return false;

    Log.d(TAG, "startRecording at: " + mp4FileUri);

    pauseARCoreSession();

    // Configure the ARCore session to start recording.
    RecordingConfig recordingConfig = new RecordingConfig(session)
        .setMp4DatasetUri(mp4FileUri)
        .setAutoStopOnPause(true);

    try {
      // Prepare the session for recording, but do not start recording yet.
      session.startRecording(recordingConfig);
    } catch (RecordingFailedException e) {
      Log.e(TAG, "startRecording - Failed to prepare to start recording", e);
      return false;
    }

    boolean canResume = resumeARCoreSession();
    if (!canResume)
      return false;

    // Correctness checking: check the ARCore session's RecordingState.
    RecordingStatus recordingStatus = session.getRecordingStatus();
    Log.d(TAG, String.format("startRecording - recordingStatus %s", recordingStatus));
    return recordingStatus == RecordingStatus.OK;
  }

Để tạm dừng và tiếp tục phiên ARCore một cách an toàn, hãy tạo pauseARCoreSession()resumeARCoreSession() trong HelloArActivity.java.

  private void pauseARCoreSession() {
    // Pause the GLSurfaceView so that it doesn't update the ARCore session.
    // Pause the ARCore session so that we can update its configuration.
    // If the GLSurfaceView is not paused,
    //   onDrawFrame() will try to update the ARCore session
    //   while it's paused, resulting in a crash.
    surfaceView.onPause();
    session.pause();
  }

  private boolean resumeARCoreSession() {
    // We must resume the ARCore session before the GLSurfaceView.
    // Otherwise, the GLSurfaceView will try to update the ARCore session.
    try {
      session.resume();
    } catch (CameraNotAvailableException e) {
      Log.e(TAG, "CameraNotAvailableException in resumeARCoreSession", e);
      return false;
    }

    surfaceView.onResume();
    return true;
  }

Bật ứng dụng để dừng ghi âm

Tạo một hàm có tên là stopRecording() trong HelloArActivity.java để ngăn ứng dụng ghi lại dữ liệu mới. Hàm này gọi session.stopRecording() và gửi thông báo lỗi vào nhật ký bảng điều khiển nếu ứng dụng không thể dừng ghi.

  private boolean stopRecording() {
    try {
      session.stopRecording();
    } catch (RecordingFailedException e) {
      Log.e(TAG, "stopRecording - Failed to stop recording", e);
      return false;
    }

    // Correctness checking: check if the session stopped recording.
    return session.getRecordingStatus() == RecordingStatus.NONE;
  }

Thiết kế bộ nhớ tệp bằng bộ nhớ có giới hạn trên Android 11

Các hàm liên quan đến bộ nhớ trong lớp học lập trình này được thiết kế theo các yêu cầu mới về bộ nhớ có giới hạn trên Android 11.

Thực hiện một số thay đổi nhỏ trong tệp app/build.gradle để nhắm đến Android 11. Trong bảng điều khiển Dự án Android Studio, tệp này nằm trong nút Gradle Scripts (Tập lệnh Gradle), liên kết với mô-đun app (ứng dụng).

app-build.gradle.png

Thay đổi compileSdkVersiontargetSdkVersion thành 30.

    compileSdkVersion 30
    defaultConfig {
      targetSdkVersion 30
    }

Để quay video, hãy sử dụng API Android MediaStore để tạo tệp MP4 trong thư mục Movie dùng chung.

Tạo một hàm có tên là createMp4File() trong HelloArActivity.java:

// Add imports to the beginning of the file.
import java.text.SimpleDateFormat;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.content.ContentValues;
import java.io.File;
import android.content.CursorLoader;
import android.database.Cursor;
import java.util.Date;


  private final String MP4_VIDEO_MIME_TYPE = "video/mp4";

  private Uri createMp4File() {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
    String mp4FileName = "arcore-" + dateFormat.format(new Date()) + ".mp4";

    ContentResolver resolver = this.getContentResolver();

    Uri videoCollection = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
      videoCollection = MediaStore.Video.Media.getContentUri(
          MediaStore.VOLUME_EXTERNAL_PRIMARY);
    } else {
      videoCollection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    }

    // Create a new Media file record.
    ContentValues newMp4FileDetails = new ContentValues();
    newMp4FileDetails.put(MediaStore.Video.Media.DISPLAY_NAME, mp4FileName);
    newMp4FileDetails.put(MediaStore.Video.Media.MIME_TYPE, MP4_VIDEO_MIME_TYPE);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
      // The Relative_Path column is only available since API Level 29.
      newMp4FileDetails.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES);
    } else {
      // Use the Data column to set path for API Level <= 28.
      File mp4FileDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
      String absoluteMp4FilePath = new File(mp4FileDir, mp4FileName).getAbsolutePath();
      newMp4FileDetails.put(MediaStore.Video.Media.DATA, absoluteMp4FilePath);
    }

    Uri newMp4FileUri = resolver.insert(videoCollection, newMp4FileDetails);

    // Ensure that this file exists and can be written.
    if (newMp4FileUri == null) {
      Log.e(TAG, String.format("Failed to insert Video entity in MediaStore. API Level = %d", Build.VERSION.SDK_INT));
      return null;
    }

    // This call ensures the file exist before we pass it to the ARCore API.
    if (!testFileWriteAccess(newMp4FileUri)) {
      return null;
    }

    Log.d(TAG, String.format("createMp4File = %s, API Level = %d", newMp4FileUri, Build.VERSION.SDK_INT));

    return newMp4FileUri;
  }

  // Test if the file represented by the content Uri can be open with write access.
  private boolean testFileWriteAccess(Uri contentUri) {
    try (java.io.OutputStream mp4File = this.getContentResolver().openOutputStream(contentUri)) {
      Log.d(TAG, String.format("Success in testFileWriteAccess %s", contentUri.toString()));
      return true;
    } catch (java.io.FileNotFoundException e) {
      Log.e(TAG, String.format("FileNotFoundException in testFileWriteAccess %s", contentUri.toString()), e);
    } catch (java.io.IOException e) {
      Log.e(TAG, String.format("IOException in testFileWriteAccess %s", contentUri.toString()), e);
    }

    return false;
  }

Xử lý quyền truy cập vào bộ nhớ

Nếu đang sử dụng thiết bị Android 11, bạn có thể bắt đầu kiểm thử mã. Để hỗ trợ các thiết bị chạy Android 10 trở xuống, bạn cần cấp quyền lưu trữ ứng dụng để lưu dữ liệu vào hệ thống tệp của thiết bị mục tiêu.

Trong AndroidManifest.xml, hãy khai báo rằng ứng dụng cần quyền đọc và ghi bộ nhớ trước Android 11 (API cấp 30).

  <!-- Inside the <manifest> tag, below the existing Camera permission -->
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
      android:maxSdkVersion="29" />

  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
      android:maxSdkVersion="29" />

Thêm một hàm trợ giúp có tên là checkAndRequestStoragePermission() trong HelloArActivity.java để yêu cầu các quyền WRITE_EXTERNAL_STORAGE trong thời gian chạy.

// Add imports to the beginning of the file.
import android.Manifest;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

  private final int REQUEST_WRITE_EXTERNAL_STORAGE = 1;
  public boolean checkAndRequestStoragePermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
        != PackageManager.PERMISSION_GRANTED) {
      ActivityCompat.requestPermissions(this,
          new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
          REQUEST_WRITE_EXTERNAL_STORAGE);
      return false;
    }

    return true;
  }

Nếu bạn đang sử dụng API cấp 29 trở xuống, hãy thêm phần kiểm tra quyền truy cập bộ nhớ ở đầu createMp4File() và thoát khỏi hàm này sớm nếu ứng dụng không có quyền phù hợp. API cấp 30 (Android 11) không yêu cầu quyền truy cập vào bộ nhớ để truy cập vào các tệp trong MediaStore.

  private Uri createMp4File() {
    // Since we use legacy external storage for Android 10,
    // we still need to request for storage permission on Android 10.
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
      if (!checkAndRequestStoragePermission()) {
        Log.i(TAG, String.format(
            "Didn't createMp4File. No storage permission, API Level = %d",
            Build.VERSION.SDK_INT));
        return null;
      }
    }
    // ... omitted code ...
  }

Ghi từ thiết bị mục tiêu

Đã đến lúc xem những gì bạn đã xây dựng được cho đến thời điểm này. Kết nối thiết bị di động với máy phát triển rồi nhấp vào Run (Chạy) trong Android Studio.

Bạn sẽ thấy nút Ghi lại màu đỏ ở dưới cùng bên trái màn hình. Khi nhấn vào nút đó, nội dung sẽ chuyển thành Dừng. Di chuyển thiết bị ra xung quanh để ghi một phiên và nhấp vào nút Stop (Dừng) khi bạn muốn hoàn tất quá trình ghi. Thao tác này sẽ lưu tệp mới có tên là arcore-xxxxxx_xxxxxx.mp4 vào bộ nhớ ngoài của thiết bị.

record-button.png

Bây giờ, bạn sẽ có một tệp arcore-xxxxxx_xxxxxx.mp4 mới trong bộ nhớ ngoài của thiết bị. Trên các thiết bị Pixel 5, đường dẫn là /storage/emulated/0/Movies/. Bạn có thể tìm thấy đường dẫn trong cửa sổ Logcat sau khi bắt đầu ghi.

com.google.ar.core.examples.java.helloar D/HelloArActivity: startRecording at:/storage/emulated/0/Movies/arcore-xxxxxxxx_xxxxxx.mp4
com.google.ar.core.examples.java.helloar D/HelloArActivity: startRecording - RecordingStatus OK

Xem bản ghi

Bạn có thể sử dụng ứng dụng hệ thống tệp như Files by Google để xem bản ghi hoặc sao chép bản ghi đó vào máy phát triển. Dưới đây là hai lệnh adb để liệt kê và tìm nạp tệp từ thiết bị Android:

  • adb shell ls '$EXTERNAL_STORAGE/Movies/*' để hiển thị các tệp trong thư mục Phim trong bộ nhớ ngoài trên thiết bị
  • adb pull /absolute_path_from_previous_adb_shell_ls/arcore-xxxxxxxx_xxxxxx.mp4 để sao chép tệp từ thiết bị sang máy phát triển

Đây là ví dụ về kết quả sau khi sử dụng 2 lệnh sau (từ macOS):

$ adb shell ls '$EXTERNAL_STORAGE/Movies/*'
/sdcard/Movies/arcore-xxxxxxxx_xxxxxx.mp4


$ adb pull /sdcard/Movies/arcore-xxxxxxxx_xxxxxx.mp4
/sdcard/Movies/arcore-xxxxxxxx_xxxxxx.mp4: ... pulled

Những việc bạn đã làm ở bước này

  • Đã thêm một nút để bắt đầu và dừng ghi
  • Các hàm đã triển khai để bắt đầu và dừng ghi
  • Kiểm thử ứng dụng trên thiết bị
  • Đã sao chép tệp MP4 được ghi vào máy của bạn và xác minh tệp đó

Tiếp theo, bạn sẽ phát một phiên thực tế tăng cường từ tệp MP4.

4. Phát phiên ARCore từ tệp MP4

Giờ đây, bạn có nút Record (Ghi) và một số tệp MP4 chứa các phiên đã ghi. Giờ đây, bạn sẽ phát lại các video đó bằng ARCore Playback API.

Thêm giao diện người dùng cho nút Phát

Trước khi triển khai tính năng phát, hãy thêm một nút trên giao diện người dùng để người dùng có thể thông báo cho ARCore khi nào cần bắt đầu và dừng phát lại phiên.

Trong bảng điều khiển Project (Dự án), hãy mở tệp app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

Trong activity_main.xml, hãy thêm mã bên dưới vào trước thẻ đóng để tạo nút Playback (Phát lại) mới và thiết lập trình xử lý sự kiện của trình xử lý sự kiện thành một phương thức có tên là onClickPlayback(). Nút này tương tự như nút Record (Ghi) và hiển thị ở bên phải màn hình.

  <!--
    Add a new "Playback" button with those attributes:
        text is "Playback",
        onClick event handler is "onClickPlayback",
        text color is "green".
  -->
  <Button
      android:id="@+id/playback_button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignEnd="@id/surfaceview"
      android:layout_alignBottom="@id/surfaceview"
      android:layout_marginBottom="100dp"
      android:onClick="onClickPlayback"
      android:text="Playback"
      android:textColor="@android:color/holo_green_light" />

Cập nhật các nút trong khi phát

Ứng dụng hiện có một trạng thái mới tên là Playingback. Cập nhật enum AppState và tất cả các hàm hiện có lấy appState làm đối số để xử lý việc này.

Thêm Playingback vào enum AppState trong HelloArActivity.java:

  public enum AppState {
    Idle,
    Recording,
    Playingback // New enum value.
  }

Nếu nút Record (Ghi) vẫn còn trên màn hình trong khi phát, thì người dùng có thể vô tình nhấp vào nút đó. Để tránh điều này, hãy ẩn nút Record (Ghi) trong quá trình Phát. Theo đó, bạn không cần xử lý trạng thái Playingback trong onClickRecord().

Sửa đổi hàm updateRecordButton() trong HelloArActivity.java để ẩn nút Record (Ghi) khi ứng dụng ở trạng thái Playingback.

  // Update the "Record" button based on app's internal state.
  private void updateRecordButton() {
    View buttonView = findViewById(R.id.record_button);
    Button button = (Button)buttonView;

    switch (appState) {

      // The app is neither recording nor playing back. The "Record" button is visible.
      case Idle:
        button.setText("Record");
        button.setVisibility(View.VISIBLE);
        break;

      // While recording, the "Record" button is visible and says "Stop".
      case Recording:
        button.setText("Stop");
        button.setVisibility(View.VISIBLE);
        break;

      // During playback, the "Record" button is not visible.
      case Playingback:
        button.setVisibility(View.INVISIBLE);
        break;
    }
  }

Tương tự, ẩn nút Phát lại khi người dùng đang ghi lại một phiên và thay đổi nút này để nói "Dừng" khi người dùng chủ động phát lại một phiên. Bằng cách này, họ có thể dừng quá trình phát mà không phải đợi quá trình này hoàn tất.

Thêm một hàm updatePlaybackButton() trong HelloArActivity.java:

  // Update the "Playback" button based on app's internal state.
  private void updatePlaybackButton() {
    View buttonView = findViewById(R.id.playback_button);
    Button button = (Button)buttonView;

    switch (appState) {

      // The app is neither recording nor playing back. The "Playback" button is visible.
      case Idle:
        button.setText("Playback");
        button.setVisibility(View.VISIBLE);
        break;

      // While playing back, the "Playback" button is visible and says "Stop".
      case Playingback:
        button.setText("Stop");
        button.setVisibility(View.VISIBLE);
        break;

      // During recording, the "Playback" button is not visible.
      case Recording:
        button.setVisibility(View.INVISIBLE);
        break;
    }
  }

Cuối cùng, hãy cập nhật onClickRecord() để gọi updatePlaybackButton(). Thêm dòng sau vào HelloArActivity.java:

  public void onClickRecord(View view) {
    // ... omitted code ...
    updatePlaybackButton(); // Add this line to the end of the function.
  }

Chọn một tệp bằng nút Phát

Khi được nhấn, nút Playback sẽ cho phép người dùng chọn một tệp để phát lại. Trên Android, việc lựa chọn tệp được xử lý trong bộ chọn tệp hệ thống trong một Hoạt động khác. Việc này được thực hiện thông qua Khung truy cập bộ nhớ (SAF). Sau khi người dùng chọn một tệp, ứng dụng sẽ nhận được một lệnh gọi lại có tên là onActivityResult(). Bạn sẽ bắt đầu phát thực tế bên trong hàm callback này.

Trong HelloArActivity.java, hãy tạo một hàm onClickPlayback() để chọn tệp và ngừng phát lại.

  // Handle the click event of the "Playback" button.
  public void onClickPlayback(View view) {
    Log.d(TAG, "onClickPlayback");

    switch (appState) {

      // If the app is not playing back, open the file picker.
      case Idle: {
        boolean hasStarted = selectFileToPlayback();
        Log.d(TAG, String.format("onClickPlayback start: selectFileToPlayback %b", hasStarted));
        break;
      }

      // If the app is playing back, stop playing back.
      case Playingback: {
        boolean hasStopped = stopPlayingback();
        Log.d(TAG, String.format("onClickPlayback stop: hasStopped %b", hasStopped));
        break;
      }

      default:
        // Recording - do nothing.
        break;
    }

    // Update the UI for the "Record" and "Playback" buttons.
    updateRecordButton();
    updatePlaybackButton();
  }

Trong HelloArActivity.java, hãy tạo một hàm selectFileToPlayback() để chọn một tệp trên thiết bị. Để chọn một tệp trong Hệ thống tệp Android, hãy sử dụng Ý định ACTION_OPEN_DOCUMENT.

// Add imports to the beginning of the file.
import android.content.Intent;
import android.provider.DocumentsContract;

  private boolean selectFileToPlayback() {
    // Start file selection from Movies directory.
    // Android 10 and above requires VOLUME_EXTERNAL_PRIMARY to write to MediaStore.
    Uri videoCollection;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
      videoCollection = MediaStore.Video.Media.getContentUri(
          MediaStore.VOLUME_EXTERNAL_PRIMARY);
    } else {
      videoCollection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    }

    // Create an Intent to select a file.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

    // Add file filters such as the MIME type, the default directory and the file category.
    intent.setType(MP4_VIDEO_MIME_TYPE); // Only select *.mp4 files
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, videoCollection); // Set default directory
    intent.addCategory(Intent.CATEGORY_OPENABLE); // Must be files that can be opened

    this.startActivityForResult(intent, REQUEST_MP4_SELECTOR);

    return true;
  }

REQUEST_MP4_SELECTOR là hằng số để xác định yêu cầu này. Bạn có thể xác định lớp này bằng cách sử dụng bất kỳ giá trị phần giữ chỗ nào bên trong HelloArActivity trong HelloArActivity.java:

  private int REQUEST_MP4_SELECTOR = 1;

Ghi đè hàm onActivityResult() trong HelloArActivity.java để xử lý lệnh gọi lại từ bộ chọn tệp.

  // Begin playback once the user has selected the file.
  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Check request status. Log an error if the selection fails.
    if (resultCode != android.app.Activity.RESULT_OK || requestCode != REQUEST_MP4_SELECTOR) {
      Log.e(TAG, "onActivityResult select file failed");
      return;
    }

    Uri mp4FileUri = data.getData();
    Log.d(TAG, String.format("onActivityResult result is %s", mp4FileUri));

    // Begin playback.
    startPlayingback(mp4FileUri);
  }

Bật ứng dụng để bắt đầu phát

Một phiên ARCore cần 3 lệnh gọi API để phát tệp MP4:

  1. session.pause()
  2. session.setPlaybackDataset()
  3. session.resume()

Trong HelloArActivity.java, hãy tạo hàm startPlayingback().

// Add imports to the beginning of the file.
import com.google.ar.core.PlaybackStatus;
import com.google.ar.core.exceptions.PlaybackFailedException;

  private boolean startPlayingback(Uri mp4FileUri) {
    if (mp4FileUri == null)
      return false;

    Log.d(TAG, "startPlayingback at:" + mp4FileUri);

    pauseARCoreSession();

    try {
      session.setPlaybackDatasetUri(mp4FileUri);
    } catch (PlaybackFailedException e) {
      Log.e(TAG, "startPlayingback - setPlaybackDataset failed", e);
    }

    // The session's camera texture name becomes invalid when the
    // ARCore session is set to play back.
    // Workaround: Reset the Texture to start Playback
    // so it doesn't crashes with AR_ERROR_TEXTURE_NOT_SET.
    hasSetTextureNames = false;

    boolean canResume = resumeARCoreSession();
    if (!canResume)
      return false;

    PlaybackStatus playbackStatus = session.getPlaybackStatus();
    Log.d(TAG, String.format("startPlayingback - playbackStatus %s", playbackStatus));


    if (playbackStatus != PlaybackStatus.OK) { // Correctness check
      return false;
    }

    appState = AppState.Playingback;
    updateRecordButton();
    updatePlaybackButton();

    return true;
  }

Bật ứng dụng để dừng phát

Tạo một hàm có tên là stopPlayingback() trong HelloArActivity.java để xử lý các thay đổi về trạng thái ứng dụng sau:

  1. Người dùng đã dừng phát MP4
  2. Quá trình phát MP4 đã tự hoàn tất

Nếu người dùng dừng phát, ứng dụng sẽ trở về trạng thái như khi người dùng khởi chạy ứng dụng lần đầu tiên.

  // Stop the current playback, and restore app status to Idle.
  private boolean stopPlayingback() {
    // Correctness check, only stop playing back when the app is playing back.
    if (appState != AppState.Playingback)
      return false;

    pauseARCoreSession();

    // Close the current session and create a new session.
    session.close();
    try {
      session = new Session(this);
    } catch (UnavailableArcoreNotInstalledException
        |UnavailableApkTooOldException
        |UnavailableSdkTooOldException
        |UnavailableDeviceNotCompatibleException e) {
      Log.e(TAG, "Error in return to Idle state. Cannot create new ARCore session", e);
      return false;
    }
    configureSession();

    boolean canResume = resumeARCoreSession();
    if (!canResume)
      return false;

    // A new session will not have a camera texture name.
    // Manually set hasSetTextureNames to false to trigger a reset.
    hasSetTextureNames = false;

    // Reset appState to Idle, and update the "Record" and "Playback" buttons.
    appState = AppState.Idle;
    updateRecordButton();
    updatePlaybackButton();

    return true;
  }

Quá trình phát cũng có thể tự nhiên dừng sau khi trình phát đã nghe hết tệp MP4. Khi điều này xảy ra, stopPlayingback() sẽ chuyển trạng thái của ứng dụng về Idle. Trong onDrawFrame(), hãy kiểm tra PlaybackStatus. Nếu giá trị là FINISHED, hãy gọi hàm stopPlayingback() trên luồng giao diện người dùng.

  public void onDrawFrame(SampleRender render) {
      // ... omitted code ...

      // Insert before this line:
      // frame = session.update();

      // Check the playback status and return early if playback reaches the end.
      if (appState == AppState.Playingback
          && session.getPlaybackStatus() == PlaybackStatus.FINISHED) {
        this.runOnUiThread(this::stopPlayingback);
        return;
      }

      // ... omitted code ...
  }

Phát nội dung trên thiết bị mục tiêu

Đã đến lúc xem những gì bạn đã xây dựng được cho đến thời điểm này. Kết nối thiết bị di động với máy phát triển rồi nhấp vào Run (Chạy) trong Android Studio.

Khi ứng dụng chạy, bạn sẽ thấy màn hình có nút Record (Ghi) màu đỏ ở bên trái và nút Playback (Phát lại) màu xanh lục ở bên phải.

playback-button.png

Nhấn vào nút Phát lại rồi chọn một trong các tệp MP4 mà bạn vừa quay. Nếu bạn không thấy tên tệp nào bắt đầu bằng arcore-, thì có thể thiết bị của bạn đang không hiển thị thư mục Movies (Phim). Trong trường hợp này, hãy chuyển đến Mẫu điện thoại > Phim bằng trình đơn ở góc trên cùng bên trái. Bạn cũng có thể cần bật tuỳ chọn Hiển thị bộ nhớ trong để hiển thị thư mục mẫu điện thoại.

show-internal-storage-button.png

nativate-to-movies-file-picker.jpg

Nhấn vào tên tệp trên màn hình để chọn tệp MP4. Ứng dụng sẽ phát lại tệp MP4.

playback-stop-button.png

Điểm khác biệt giữa việc phát lại một phiên và phát lại một video thông thường là bạn có thể tương tác với phiên đã ghi lại. Nhấn vào một mặt phẳng đã phát hiện để đặt điểm đánh dấu trên màn hình.

vị trí phát

Những việc bạn đã làm ở bước này

  • Đã thêm một nút để bắt đầu và dừng phát
  • Triển khai một hàm để giúp ứng dụng bắt đầu và dừng ghi
  • Đã phát lại một phiên ARCore đã ghi trước đó trên thiết bị

5. Ghi dữ liệu bổ sung ở định dạng MP4

Với ARCore 1.24, có thể ghi thêm thông tin vào tệp MP4. Bạn có thể ghi lại Pose vị trí đặt đối tượng thực tế tăng cường, sau đó tạo các đối tượng thực tế tăng cường tại cùng một vị trí trong quá trình phát.

Định cấu hình kênh mới để ghi

Xác định một kênh mới bằng một mã nhận dạng duy nhất (UUID) và một thẻ MIME trong HelloArActivity.java.

// Add imports to the beginning of the file.
import java.util.UUID;
import com.google.ar.core.Track;

  // Inside the HelloArActiity class.
  private static final UUID ANCHOR_TRACK_ID = UUID.fromString("53069eb5-21ef-4946-b71c-6ac4979216a6");;
  private static final String ANCHOR_TRACK_MIME_TYPE = "application/recording-playback-anchor";

  private boolean startRecording() {
    // ... omitted code ...

    // Insert after line:
    //   pauseARCoreSession();

    // Create a new Track, with an ID and MIME tag.
    Track anchorTrack = new Track(session)
        .setId(ANCHOR_TRACK_ID).
        .setMimeType(ANCHOR_TRACK_MIME_TYPE);
    // ... omitted code ...
  }

Cập nhật mã thoát để tạo đối tượng RecordingConfig có lệnh gọi đến addTrack().

  private boolean startRecording() {
    // ... omitted code ...

    // Update the lines below with a call to the addTrack() function:
    //   RecordingConfig recordingConfig = new RecordingConfig(session)
    //    .setMp4DatasetUri(mp4FileUri)
    //    .setAutoStopOnPause(true);

    RecordingConfig recordingConfig = new RecordingConfig(session)
        .setMp4DatasetUri(mp4FileUri)
        .setAutoStopOnPause(true)
        .addTrack(anchorTrack); // add the new track onto the recordingConfig

    // ... omitted code ...
  }

Lưu tư thế neo trong khi ghi hình

Mỗi khi người dùng nhấn vào một máy bay được phát hiện, một điểm đánh dấu thực tế tăng cường sẽ được đặt trên một Anchor. Tư thế này sẽ được ARCore cập nhật.

Ghi lại tư thế của Anchor tại khung hình mà nó được tạo, nếu bạn vẫn đang ghi phiên ARCore.

Sửa đổi hàm handleTap() trong HelloArActivity.java.

// Add imports to the beginning of the file.
import com.google.ar.core.Pose;
import java.nio.FloatBuffer;

  private void handleTap(Frame frame, Camera camera) {
          // ... omitted code ...

          // Insert after line:
          // anchors.add(hit.createAnchor());

          // If the app is recording a session,
          // save the new Anchor pose (relative to the camera)
          // into the ANCHOR_TRACK_ID track.
          if (appState == AppState.Recording) {
            // Get the pose relative to the camera pose.
            Pose cameraRelativePose = camera.getPose().inverse().compose(hit.getHitPose());
            float[] translation = cameraRelativePose.getTranslation();
            float[] quaternion = cameraRelativePose.getRotationQuaternion();
            ByteBuffer payload = ByteBuffer.allocate(4 * (translation.length + quaternion.length));
            FloatBuffer floatBuffer = payload.asFloatBuffer();
            floatBuffer.put(translation);
            floatBuffer.put(quaternion);

            try {
              frame.recordTrackData(ANCHOR_TRACK_ID, payload);
            } catch (IllegalStateException e) {
              Log.e(TAG, "Error in recording anchor into external data track.", e);
            }
          }
          // ... omitted code ...
  }

Lý do chúng ta duy trì máy ảnh tương đối Pose thay vì Pose tương đối trên thế giới là vì nguồn gốc thế giới của một phiên ghi và nguồn gốc trên thế giới của một phiên phát không giống nhau. Nguồn gốc thế giới của phiên ghi bắt đầu từ lần đầu tiên phiên được tiếp tục, khi Session.resume() được gọi lần đầu tiên. Nguồn gốc thế giới của phiên phát bắt đầu khi khung hình đầu tiên được ghi lại, khi Session.resume() được gọi lần đầu sau Session.startRecording().

Tạo neo phát

Việc tạo lại Anchor rất đơn giản. Thêm một hàm có tên là createRecordedAnchors() trong HelloArActivity.java.

// Add imports to the beginning of the file.
import com.google.ar.core.TrackData;

  // Extract poses from the ANCHOR_TRACK_ID track, and create new anchors.
  private void createRecordedAnchors(Frame frame, Camera camera) {
    // Get all `ANCHOR_TRACK_ID` TrackData from the frame.
    for (TrackData trackData : frame.getUpdatedTrackData(ANCHOR_TRACK_ID)) {
      ByteBuffer payload = trackData.getData();
      FloatBuffer floatBuffer = payload.asFloatBuffer();

      // Extract translation and quaternion from TrackData payload.
      float[] translation = new float[3];
      float[] quaternion = new float[4];

      floatBuffer.get(translation);
      floatBuffer.get(quaternion);

      // Transform the recorded anchor pose
      // from the camera coordinate
      // into world coordinates.
      Pose worldPose = camera.getPose().compose(new Pose(translation, quaternion));

      // Re-create an anchor at the recorded pose.
      Anchor recordedAnchor = session.createAnchor(worldPose);

      // Add the new anchor into the list of anchors so that
      // the AR marker can be displayed on top.
      anchors.add(recordedAnchor);
    }
  }

Gọi createRecordedAnchors() trong hàm onDrawFrame() trong HelloArActivity.java.

  public void onDrawFrame(SampleRender render) {
    // ... omitted code ...

    // Insert after this line:
    // handleTap(frame, camera);

    // If the app is currently playing back a session, create recorded anchors.
    if (appState == AppState.Playingback) {
      createRecordedAnchors(frame, camera);
    }
    // ... omitted code ...
  }

Kiểm thử trên thiết bị mục tiêu

Kết nối thiết bị di động với máy phát triển rồi nhấp vào Run (Chạy) trong Android Studio.

Trước tiên, hãy nhấn vào nút Ghi lại để ghi lại một phiên. Trong khi quay, hãy nhấn vào các máy bay đã phát hiện để đặt một số điểm đánh dấu thực tế tăng cường.

Sau khi dừng quay, nhấn vào nút Phát lại rồi chọn tệp bạn vừa quay. Quá trình phát sẽ bắt đầu. Hãy chú ý đến cách các vị trí điểm đánh dấu AR trước đó của bạn xuất hiện giống như khi bạn nhấn vào ứng dụng.

Đó là tất cả các bước lập trình bạn sẽ phải thực hiện trong lớp học lập trình này.

6. Xin chúc mừng

Xin chúc mừng, bạn đã đến phần cuối của lớp học lập trình này! Hãy xem lại những gì bạn đã làm trong lớp học lập trình này:

  • Tạo và chạy mẫu ARCore Hello AR Java.
  • Thêm nút Ghi vào ứng dụng để lưu một phiên thực tế tăng cường vào tệp MP4
  • Thêm nút Phát vào ứng dụng để phát một phiên thực tế tăng cường qua tệp MP4
  • Thêm một tính năng mới để lưu các neo do người dùng tạo trong MP4 để phát lại

Bạn có thấy thú vị khi tham gia lớp học lập trình này không?

Không

Bạn có học được điều hữu ích khi thực hiện lớp học lập trình này không?

Không

Bạn đã hoàn tất việc tạo ứng dụng trong lớp học lập trình này chưa?

Không