ข้อมูลเบื้องต้นเกี่ยวกับ ARCore Recording and Playback API

ข้อมูลเบื้องต้นเกี่ยวกับ ARCore Recording and Playback API

เกี่ยวกับ Codelab นี้

subjectอัปเดตล่าสุดเมื่อ ส.ค. 12, 2021
account_circleเขียนโดย Kai Zhou

1 บทนำ

การบันทึกประสบการณ์ AR เป็นไฟล์ MP4 และเล่นจากไฟล์ MP4 มีประโยชน์กับทั้งนักพัฒนาแอปและผู้ใช้ปลายทาง

แก้ไขข้อบกพร่องและทดสอบฟีเจอร์ใหม่ได้จากโต๊ะทำงาน

การใช้งานระเบียน ARCore ที่ง่ายที่สุด API การเล่นมีไว้สำหรับนักพัฒนา ไม่ต้องเสียเวลาไปกับการสร้างและเรียกใช้แอปบนอุปกรณ์ทดสอบ ถอดสาย USB และเดินไปรอบๆ เพียงเพื่อทดสอบการเปลี่ยนแปลงโค้ดเล็กๆ น้อยๆ ตอนนี้คุณเพียงแค่บันทึก MP4 ในสภาพแวดล้อมการทดสอบที่มีการเคลื่อนไหวของโทรศัพท์ที่คาดไว้ และทดสอบจากโต๊ะทำงานได้โดยตรง

บันทึกและเล่นจากอุปกรณ์เครื่องอื่น

เมื่อใช้ API การบันทึกและการเล่น ผู้ใช้คนหนึ่งจะสามารถบันทึกเซสชันโดยใช้อุปกรณ์หนึ่ง ส่วนอีกเครื่องหนึ่งสามารถเล่นเซสชันเดียวกันนั้นในอีกอุปกรณ์หนึ่งได้ คุณอาจแชร์ประสบการณ์ AR กับผู้ใช้รายอื่นได้ มีหลากหลายความเป็นไปได้!

นี่เป็นการสร้างแอป ARCore เป็นครั้งแรกใช่ไหม

คุณจะใช้ Codelab นี้อย่างไร

สิ่งที่คุณจะสร้าง

ใน Codelab นี้ คุณจะใช้การบันทึกและ API การเล่น เพื่อสร้างแอปที่บันทึกประสบการณ์ AR ลงในไฟล์ MP4 และเล่นไฟล์เดียวกันนั้น คุณจะได้เรียนรู้:

  • วิธีใช้ API การบันทึกเพื่อบันทึกเซสชัน AR เป็นไฟล์ MP4
  • วิธีใช้ API การเล่นเพื่อเล่นเซสชัน AR ซ้ำจากไฟล์ MP4
  • วิธีบันทึกเซสชัน AR ในอุปกรณ์หนึ่งและเล่นซ้ำในอุปกรณ์อีกเครื่องหนึ่ง

สิ่งที่ต้องมี

ใน Codelab นี้ คุณจะได้แก้ไขแอป Hello AR Java ที่สร้างด้วย ARCore Android SDK และคุณจะต้องใช้ฮาร์ดแวร์และซอฟต์แวร์ที่เฉพาะเจาะจงตามมาด้วย

ข้อกำหนดเกี่ยวกับฮาร์ดแวร์

ข้อกำหนดของซอฟต์แวร์

นอกจากนี้ คุณควรมีความเข้าใจเบื้องต้นเกี่ยวกับ ARCore ด้วยเพื่อให้ได้ผลลัพธ์ที่ดีที่สุด

2 ตั้งค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์

เริ่มต้นด้วยการตั้งค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์

ดาวน์โหลด ARCore Android SDK

คลิก เพื่อดาวน์โหลด SDK

แตกไฟล์ ARCore Android SDK

เมื่อดาวน์โหลด Android SDK ลงในเครื่องแล้ว ให้แตกไฟล์และไปที่ไดเรกทอรี arcore-android-sdk-1.24/samples/hello_ar_java นี่คือไดเรกทอรีรากของแอปที่คุณจะใช้งาน

hello-ar-java-extracted

โหลด Hello AR Java ลงใน Android Studio

เปิด Android Studio แล้วคลิกเปิดโปรเจ็กต์ Android Studio ที่มีอยู่

android-studio-open-projects

ในหน้าต่างที่ปรากฏขึ้น ให้เลือก arcore-android-sdk-1.24/samples/hello_ar_java แล้วคลิกเปิด

รอให้ Android Studio ซิงค์โปรเจ็กต์ให้เสร็จ หากมีคอมโพเนนต์ขาดหายไป การนำเข้าโปรเจ็กต์อาจล้มเหลวโดยมีข้อความแสดงข้อผิดพลาด โปรดแก้ไขปัญหาเหล่านี้ก่อนดำเนินการต่อ

เรียกใช้แอปตัวอย่าง

  1. เชื่อมต่ออุปกรณ์ที่รองรับ ARCore กับเครื่องพัฒนา
  2. หากระบบรู้จักอุปกรณ์อย่างถูกต้อง คุณควรเห็นชื่ออุปกรณ์ปรากฏใน Android Studio android-studio-pixel-5.png
  3. คลิกปุ่ม Run หรือเลือก Run > (เรียกใช้) เรียกใช้ "แอป" เพื่อให้ Android Studio ติดตั้งและเปิดแอปในอุปกรณ์ android-studio-run-button.png
  4. คุณจะเห็นข้อความที่ขออนุญาตถ่ายภาพและบันทึกวิดีโอ เลือกขณะใช้แอปนี้เพื่อให้สิทธิ์กล้องถ่ายรูปแก่แอป จากนั้นคุณจะเห็นสภาพแวดล้อมในโลกจริงบนหน้าจออุปกรณ์ hello-ar-java-permission
  5. เลื่อนอุปกรณ์ในแนวนอนเพื่อสแกนหาเครื่องบิน
  6. ตารางกริดสีขาวจะปรากฏขึ้นเมื่อแอปตรวจพบเครื่องบิน แตะเพื่อวางเครื่องหมายบนระนาบนั้น ตำแหน่ง AR ของ Hello

สิ่งที่คุณดำเนินการในขั้นตอนนี้

  • ตั้งค่าโปรเจ็กต์ Hello AR Java
  • สร้างและเรียกใช้แอปตัวอย่างในอุปกรณ์ที่รองรับ ARCore

ขั้นตอนต่อไปคือการบันทึกเซสชัน AR เป็นไฟล์ MP4

3 บันทึกเซสชัน ARCore เป็นไฟล์ MP4

เราจะเพิ่มฟีเจอร์การบันทึกในขั้นตอนนี้ ซึ่งประกอบด้วย

  • ปุ่มสำหรับเริ่มหรือหยุดการบันทึก
  • ฟังก์ชันพื้นที่เก็บข้อมูลเพื่อบันทึกไฟล์ MP4 ในอุปกรณ์
  • เรียกใช้หรือหยุดการบันทึกเซสชัน ARCore

เพิ่ม UI สำหรับปุ่มบันทึก

ก่อนใช้การบันทึก ให้เพิ่มปุ่มใน UI เพื่อให้ผู้ใช้แจ้ง ARCore ว่าจะเริ่มหรือหยุดบันทึกได้เมื่อใด

เปิดไฟล์ app/res/layout/activity_main.xml ในแผงโปรเจ็กต์

activity_main-xml-location-in-project

โดยค่าเริ่มต้น Android Studio จะใช้มุมมองการออกแบบหลังจากที่คุณเปิดไฟล์ app/res/layout/activity_main.xml คลิกปุ่มโค้ดที่มุมขวาบนของแท็บเพื่อเปลี่ยนเป็นมุมมองโค้ด

swith-to-the-code-view.png

ใน activity_main.xml ให้เพิ่มโค้ดต่อไปนี้ก่อนแท็กปิดเพื่อสร้างปุ่มบันทึกใหม่ และตั้งค่าเครื่องจัดการเหตุการณ์เป็นเมธอดที่ชื่อ 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" />

หลังจากเพิ่มโค้ดข้างต้นแล้ว ระบบอาจแสดงข้อผิดพลาดขึ้นชั่วคราว: Corresponding method handler 'public void onClickRecord(android.view.View)' not found" กรณีนี้เป็นสิ่งที่คาดว่าจะเกิดอยู่แล้ว คุณจะแก้ไขข้อผิดพลาดโดยการสร้างฟังก์ชัน onClickRecord() ในไม่กี่ขั้นตอน

เปลี่ยนข้อความตามปุ่มตามสถานะ

ปุ่มบันทึกจะจัดการทั้งการบันทึกและการหยุด เมื่อแอปไม่ได้บันทึกข้อมูลไว้ แอปควรแสดงคำว่า "บันทึก" เมื่อแอปกำลังบันทึกข้อมูล ปุ่มควรเปลี่ยนเป็นคำว่า "หยุด"

หากต้องการให้ปุ่มมีฟังก์ชันการทำงานนี้ แอปต้องทราบสถานะปัจจุบันของปุ่ม โค้ดต่อไปนี้จะสร้าง Enum ใหม่ที่เรียกว่า AppState เพื่อแสดงสถานะการทำงานของแอปและติดตามการเปลี่ยนแปลงสถานะที่เจาะจงผ่านตัวแปรสมาชิกส่วนตัวที่ชื่อ appState เพิ่มใน HelloArActivity.java เมื่อเริ่มชั้นเรียน HelloArActivity

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

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

ตอนนี้คุณติดตามสถานะภายในของแอปได้แล้ว ให้สร้างฟังก์ชันที่ชื่อว่า updateRecordButton() ซึ่งจะเปลี่ยนแปลงข้อความของปุ่มตามสถานะปัจจุบันของแอป เพิ่มโค้ดต่อไปนี้ในชั้นเรียน HelloArActivity ใน 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;
   
}
 
}

จากนั้น สร้างเมธอด onClickRecord() ที่จะตรวจสอบสถานะของแอป เปลี่ยนเป็นเมธอดถัดไป และเรียกใช้ updateRecordButton() เพื่อเปลี่ยน UI ของปุ่ม เพิ่มโค้ดต่อไปนี้ในชั้นเรียน HelloArActivity ใน 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
();
 
}

เปิดใช้แอปเพื่อเริ่มบันทึก

เพียงทำ 2 อย่างเพื่อเริ่มบันทึกใน ARCore

  1. ระบุ URI ของไฟล์บันทึกในออบเจ็กต์ RecordingConfig
  2. เรียก session.startRecording ด้วยออบเจ็กต์ RecordingConfig

ส่วนที่เหลือเป็นเพียงโค้ดสำเร็จรูป ซึ่งได้แก่ การกำหนดค่า การบันทึก และการตรวจสอบความถูกต้อง

สร้างฟังก์ชันใหม่ชื่อ startRecording() ซึ่งจะบันทึกข้อมูลและบันทึกไว้ใน URI ของ MP4 เพิ่มโค้ดต่อไปนี้ในชั้นเรียน HelloArActivity ใน 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;
 
}

หากต้องการหยุดชั่วคราวและกลับมาดำเนินเซสชัน ARCore ต่ออย่างปลอดภัย ให้สร้าง pauseARCoreSession() และ resumeARCoreSession() ใน 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;
 
}

เปิดใช้แอปเพื่อหยุดบันทึก

สร้างฟังก์ชันชื่อ stopRecording() ใน HelloArActivity.java เพื่อหยุดไม่ให้แอปบันทึกข้อมูลใหม่ ฟังก์ชันนี้เรียกใช้ session.stopRecording() และส่งข้อผิดพลาดไปยังบันทึกของคอนโซลในกรณีที่แอปหยุดการบันทึกไม่ได้

  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;
 
}

ออกแบบพื้นที่เก็บข้อมูลไฟล์โดยใช้พื้นที่เก็บข้อมูลที่กำหนดขอบเขตของ Android 11

ฟังก์ชันที่เกี่ยวข้องกับพื้นที่เก็บข้อมูลใน Codelab นี้ได้รับการออกแบบตามข้อกำหนดด้านพื้นที่เก็บข้อมูลที่กำหนดขอบเขตใหม่ของ Android 11

ทำการเปลี่ยนแปลงเล็กน้อยในไฟล์ app/build.gradle เพื่อกำหนดเป้าหมายเป็น Android 11 ในแผงโปรเจ็กต์ Android Studio ไฟล์นี้จะอยู่ภายใต้โหนด Gradle Scripts ที่เชื่อมโยงกับโมดูลแอป

app-build.gradle.png

เปลี่ยน compileSdkVersion และ targetSdkVersion เป็น 30

    compileSdkVersion 30
    defaultConfig
{
      targetSdkVersion
30
   
}

สำหรับการบันทึก ให้ใช้ Android MediaStore API เพื่อสร้างไฟล์ MP4 ในไดเรกทอรีภาพยนตร์ที่แชร์

สร้างฟังก์ชันชื่อ createMp4File() ใน 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;
 
}

จัดการสิทธิ์ของพื้นที่เก็บข้อมูล

หากใช้อุปกรณ์ Android 11 คุณจะเริ่มทดสอบโค้ดได้ หากต้องการรองรับอุปกรณ์ Android 10 หรือต่ำกว่า คุณจะต้องให้สิทธิ์พื้นที่เก็บข้อมูลของแอปเพื่อบันทึกข้อมูลลงในระบบไฟล์ของอุปกรณ์เป้าหมาย

ใน AndroidManifest.xml ให้ประกาศว่าแอปต้องการสิทธิ์อ่านและเขียนพื้นที่เก็บข้อมูลก่อน Android 11 (API ระดับ 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" />

เพิ่มฟังก์ชันตัวช่วยชื่อ checkAndRequestStoragePermission() ใน HelloArActivity.java เพื่อขอสิทธิ์ WRITE_EXTERNAL_STORAGE ระหว่างรันไทม์

// 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;
 
}

หากคุณใช้ API ระดับ 29 หรือเวอร์ชันก่อนหน้า ให้เพิ่มการตรวจสอบสิทธิ์ของพื้นที่เก็บข้อมูลที่ด้านบนของ createMp4File() และออกจากฟังก์ชันตั้งแต่เนิ่นๆ หากแอปไม่มีสิทธิ์ที่ถูกต้อง API ระดับ 30 (Android 11) ไม่จำเป็นต้องใช้สิทธิ์เข้าถึงพื้นที่เก็บข้อมูลเพื่อเข้าถึงไฟล์ใน 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 ...
 
}

บันทึกจากอุปกรณ์เป้าหมาย

ถึงเวลาดูว่าคุณสร้างอะไรมาบ้างแล้ว เชื่อมต่ออุปกรณ์เคลื่อนที่กับเครื่องพัฒนาซอฟต์แวร์ แล้วคลิกเรียกใช้ใน Android Studio

คุณจะเห็นปุ่มบันทึกสีแดงที่ด้านซ้ายล่างของหน้าจอ การแตะควรเปลี่ยนข้อความเป็นหยุด เคลื่อนอุปกรณ์ไปรอบๆ เพื่อบันทึกเซสชัน แล้วคลิกปุ่มหยุด เมื่อคุณต้องการสิ้นสุดการบันทึก ซึ่งจะบันทึกไฟล์ใหม่ชื่อ arcore-xxxxxx_xxxxxx.mp4 ลงในที่จัดเก็บข้อมูลภายนอกของอุปกรณ์

record-button.png

ตอนนี้คุณควรมีไฟล์ arcore-xxxxxx_xxxxxx.mp4 ใหม่ในพื้นที่เก็บข้อมูลภายนอกของอุปกรณ์ เส้นทางในอุปกรณ์ Pixel 5 คือ /storage/emulated/0/Movies/ คุณจะดูเส้นทางได้ในหน้าต่าง Logcat หลังจากเริ่มการบันทึก

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

ดูวิดีโอที่บันทึกไว้

คุณใช้แอประบบไฟล์ เช่น Files by Google เพื่อดูหรือคัดลอกไฟล์ไปยังเครื่องพัฒนาของคุณได้ คำสั่ง adb 2 รายการสำหรับแสดงรายการและดึงไฟล์จากอุปกรณ์ Android มีดังนี้

  • adb shell ls '$EXTERNAL_STORAGE/Movies/*' เพื่อแสดงไฟล์ในไดเรกทอรีภาพยนตร์ในพื้นที่เก็บข้อมูลภายนอกของอุปกรณ์
  • adb pull /absolute_path_from_previous_adb_shell_ls/arcore-xxxxxxxx_xxxxxx.mp4 เพื่อคัดลอกไฟล์จากอุปกรณ์ไปยังเครื่องพัฒนา

ต่อไปนี้คือตัวอย่างเอาต์พุตหลังจากใช้คำสั่ง 2 คำสั่งนี้ (จาก 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

สิ่งที่คุณดำเนินการในขั้นตอนนี้

  • เพิ่มปุ่มเพื่อเริ่มและหยุดการบันทึก
  • ใช้ฟังก์ชันต่างๆ เพื่อเริ่มและหยุดการบันทึก
  • ทดสอบแอปในอุปกรณ์
  • คัดลอก MP4 ที่บันทึกไปยังเครื่องและยืนยัน

ถัดไป คุณจะเล่นเซสชัน AR จากไฟล์ MP4

4 เล่นเซสชัน ARCore จากไฟล์ MP4

ตอนนี้คุณมีปุ่มบันทึกและไฟล์ MP4 บางไฟล์ที่มีเซสชันที่บันทึกไว้แล้ว ตอนนี้คุณจะเล่นวิดีโอเหล่านั้นโดยใช้ ARCore Playback API

เพิ่ม UI สำหรับปุ่มการเล่น

ก่อนที่จะใช้การเล่น ให้เพิ่มปุ่มใน UI เพื่อให้ผู้ใช้แจ้ง ARCore ว่าเมื่อใดควรเริ่มเล่นและหยุดเล่นเซสชัน

เปิดไฟล์ app/res/layout/activity_main.xml ในแผงโครงการ

activity_main-xml-location-in-project

ใน activity_main.xml ให้เพิ่มโค้ดด้านล่างก่อนแท็กปิดเพื่อสร้างปุ่มเล่นใหม่ และตั้งค่าเครื่องจัดการเหตุการณ์เป็นเมธอดที่ชื่อ onClickPlayback() ปุ่มนี้จะคล้ายกับปุ่มบันทึก และจะแสดงที่ด้านขวาของหน้าจอ

  <!--
    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" />

อัปเดตปุ่มในระหว่างการเล่น

ตอนนี้แอปมีสถานะใหม่ที่ชื่อว่า Playingback อัปเดต enum ของ AppState และฟังก์ชันที่มีอยู่ทั้งหมดซึ่งใช้ appState เป็นอาร์กิวเมนต์ในการจัดการเรื่องนี้

เพิ่ม Playingback ใน enum ของ AppState ใน HelloArActivity.java

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

หากปุ่มบันทึกยังอยู่บนหน้าจอระหว่างการเล่น ผู้ใช้อาจคลิกปุ่มโดยไม่ตั้งใจ เพื่อหลีกเลี่ยงปัญหานี้ ให้ซ่อนปุ่มบันทึกระหว่างการเล่น วิธีนี้จะทำให้คุณไม่ต้องจัดการสถานะสำหรับPlayingbackในonClickRecord()

แก้ไขฟังก์ชัน updateRecordButton() ใน HelloArActivity.java เพื่อซ่อนปุ่มบันทึกเมื่อแอปอยู่ในสถานะ 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;
   
}
 
}

ในทำนองเดียวกัน ให้ซ่อนปุ่มเล่นเมื่อผู้ใช้บันทึกเซสชันแล้วเปลี่ยนเป็น "หยุด" เมื่อผู้ใช้กำลังเล่นเซสชันอยู่ วิธีนี้จะช่วยให้ผู้ใช้หยุดการเล่นได้โดยไม่ต้องรอให้เล่นจบด้วยตัวเอง

เพิ่มฟังก์ชัน updatePlaybackButton() ใน 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;
   
}
 
}

สุดท้าย ให้อัปเดต onClickRecord() เพื่อโทรหา updatePlaybackButton() เพิ่มบรรทัดต่อไปนี้ใน HelloArActivity.java:

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

เลือกไฟล์ที่มีปุ่ม "เล่น"

เมื่อแตะ ปุ่มเล่นควรให้ผู้ใช้เลือกไฟล์ที่จะเล่นได้ ใน Android การเลือกไฟล์จะจัดการในเครื่องมือเลือกไฟล์ของระบบในกิจกรรมอื่น ซึ่งดำเนินการผ่านเฟรมเวิร์กการเข้าถึงพื้นที่เก็บข้อมูล (SAF) เมื่อผู้ใช้เลือกไฟล์ แอปจะได้รับการติดต่อกลับที่ชื่อว่า onActivityResult() คุณจะเริ่มเล่นจริงภายในฟังก์ชัน Callback นี้

ใน HelloArActivity.java ให้สร้างฟังก์ชัน onClickPlayback() เพื่อเลือกไฟล์และหยุดเล่นไฟล์

  // 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
();
 
}

ใน HelloArActivity.java ให้สร้างฟังก์ชัน selectFileToPlayback() ที่จะเลือกไฟล์จากอุปกรณ์ หากต้องการเลือกไฟล์จากระบบไฟล์ Android ให้ใช้ ACTION_OPEN_DOCUMENT Intent

// 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 เป็นค่าคงที่ที่จะระบุคำขอนี้ คุณจะกำหนดได้โดยใช้ค่าตัวยึดตำแหน่งภายใน HelloArActivity ใน HelloArActivity.java ดังนี้

  private int REQUEST_MP4_SELECTOR = 1;

ลบล้างฟังก์ชัน onActivityResult() ใน HelloArActivity.java เพื่อจัดการ Callback จากเครื่องมือเลือกไฟล์

  // 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);
 
}

เปิดใช้แอปเพื่อเริ่มเล่น

เซสชัน ARCore จำเป็นต้องมีการเรียก API 3 ครั้งเพื่อเล่นไฟล์ MP4 ดังนี้

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

ใน HelloArActivity.java ให้สร้างฟังก์ชัน 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;
 
}

เปิดใช้แอปเพื่อหยุดเล่น

สร้างฟังก์ชันชื่อ stopPlayingback() ใน HelloArActivity.java เพื่อจัดการการเปลี่ยนแปลงสถานะของแอปหลังจาก

  1. ผู้ใช้หยุดการเล่น MP4
  2. การเล่น MP4 เสร็จสมบูรณ์ด้วยตัวเอง

หากผู้ใช้หยุดการเล่น แอปควรกลับสู่สถานะเดียวกับในตอนที่ผู้ใช้เปิดแอปเป็นครั้งแรก

  // 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;
 
}

การเล่นสามารถหยุดลงตามธรรมชาติหลังจากที่โปรแกรมเล่นวิดีโอเล่นไปถึงส่วนท้ายของไฟล์ MP4 แล้ว เมื่อเกิดกรณีนี้ขึ้น stopPlayingback() ควรเปลี่ยนสถานะของแอปกลับไปเป็น Idle ใน onDrawFrame() ให้ตรวจสอบ PlaybackStatus หากเป็น FINISHED ให้เรียกใช้ฟังก์ชัน stopPlayingback() ในชุดข้อความ UI

  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 ...
 
}

เล่นจากอุปกรณ์เป้าหมาย

ถึงเวลาดูว่าคุณสร้างอะไรมาบ้างแล้ว เชื่อมต่ออุปกรณ์เคลื่อนที่กับเครื่องพัฒนาซอฟต์แวร์ แล้วคลิกเรียกใช้ใน Android Studio

เมื่อแอปเปิดขึ้น คุณควรเห็นหน้าจอที่มีปุ่มบันทึกสีแดงที่ด้านซ้ายและปุ่มเล่นสีเขียวทางด้านขวา

playback-button.png

แตะปุ่มเล่น แล้วเลือกไฟล์ MP4 ที่คุณเพิ่งบันทึกไป หากคุณไม่เห็นชื่อไฟล์ใดๆ ที่ขึ้นต้นด้วย arcore- อาจเป็นเพราะอุปกรณ์ของคุณไม่แสดงโฟลเดอร์ภาพยนตร์ ในกรณีนี้ ให้ไปที่รุ่นของโทรศัพท์ > ภาพยนตร์ โฟลเดอร์โดยใช้เมนูที่มุมซ้ายบน คุณอาจต้องเปิดใช้ตัวเลือกแสดงที่จัดเก็บข้อมูลภายในเพื่อแสดงโฟลเดอร์รุ่นโทรศัพท์ด้วย

show-internal-storage-button.png

nativate-to-movies-file-picker.jpg

แตะชื่อไฟล์บนหน้าจอเพื่อเลือกไฟล์ MP4 แอปควรเล่นไฟล์ MP4

playback-stop-button.png

ความแตกต่างระหว่างการเล่นเซสชันกับการเล่นวิดีโอทั่วไปคือคุณสามารถโต้ตอบกับเซสชันที่บันทึกไว้ได้ แตะระนาบที่ตรวจพบเพื่อวางเครื่องหมายบนหน้าจอ

ตำแหน่งการเล่น

สิ่งที่คุณดำเนินการในขั้นตอนนี้

  • เพิ่มปุ่มสำหรับเริ่มและหยุดเล่น
  • ใช้ฟังก์ชันที่ทำให้แอปเริ่มและหยุดการบันทึก
  • เล่นเซสชัน ARCore ที่บันทึกไว้ก่อนหน้านี้ในอุปกรณ์

5 บันทึกข้อมูลเพิ่มเติมใน MP4

เมื่อใช้ ARCore 1.24 คุณจะบันทึกข้อมูลเพิ่มเติมในไฟล์ MP4 ได้ คุณบันทึก Pose ของตำแหน่งวัตถุ AR ได้ จากนั้นระหว่างเล่น ให้สร้างวัตถุ AR ในตำแหน่งเดียวกัน

กำหนดค่าแทร็กใหม่ที่จะบันทึก

กำหนดแทร็กใหม่ด้วย UUID และแท็ก MIME ใน 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 ...
 
}

อัปเดตโค้ดทางออกเพื่อสร้างออบเจ็กต์ RecordingConfig ที่มีการเรียก 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 ...
 
}

บันทึกท่าทาง Anchor ระหว่างบันทึก

ทุกครั้งที่ผู้ใช้แตะบนเครื่องบินที่ตรวจพบ เครื่องหมาย AR จะวางอยู่บน Anchor ซึ่ง ARCore จะอัปเดตท่าทาง

หากคุณยังบันทึกเซสชัน ARCore อยู่ ให้บันทึกท่าทางของ Anchor ในเฟรมที่สร้างขึ้น

แก้ไขฟังก์ชัน handleTap() ใน 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 ...
 
}

สาเหตุที่เราคงกล้องไว้ซึ่งสัมพันธ์กับ Pose แทนที่จะเป็นโลกแบบ Pose ก็เพราะว่าโลกกำเนิดของเซสชันการบันทึกและแหล่งที่มาในโลกของเซสชันการเล่นนั้นไม่เหมือนกัน จุดเริ่มต้นเซสชันการบันทึกครั้งแรกเมื่อเซสชันกลับมาทำงานอีกครั้ง เมื่อมีการเรียก Session.resume() เป็นครั้งแรก ต้นทางโลกของเซสชันการเล่นจะเริ่มขึ้นเมื่อมีการบันทึกเฟรมแรก โดยจะมีการเรียก Session.resume() หลังจาก Session.startRecording() เป็นครั้งแรก

การสร้าง Anchor สำหรับการเล่น

การสร้าง Anchor ใหม่นั้นไม่ซับซ้อน เพิ่มฟังก์ชันที่ชื่อว่า createRecordedAnchors() ใน 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);
   
}
 
}

เรียก createRecordedAnchors() ในฟังก์ชัน onDrawFrame() ใน 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 ...
 
}

ทดสอบกับอุปกรณ์เป้าหมาย

เชื่อมต่ออุปกรณ์เคลื่อนที่กับเครื่องพัฒนาซอฟต์แวร์ แล้วคลิกเรียกใช้ใน Android Studio

ก่อนอื่น ให้แตะปุ่มบันทึกเพื่อบันทึกเซสชัน ระหว่างการบันทึก ให้แตะเครื่องบินที่ตรวจพบเพื่อวางเครื่องหมาย AR 2-3 เครื่องหมาย

หลังจากบันทึกแล้ว ให้แตะปุ่มเล่น แล้วเลือกไฟล์ที่คุณเพิ่งบันทึก วิดีโอควรเริ่มเล่น คุณจะสังเกตได้ว่าตำแหน่งเครื่องหมาย AR ก่อนหน้าจะปรากฏขึ้นเหมือนกับที่คุณแตะในแอป

นี่คือการเขียนโค้ดสำหรับ Codelab นี้ทั้งหมด

6 ขอแสดงความยินดี

ขอแสดงความยินดี คุณมาถึงส่วนสุดท้ายของ Codelab แล้ว มาย้อนดูสิ่งที่คุณได้ทำใน Codelab กัน

  • สร้างและเรียกใช้ตัวอย่าง Hello AR Java ของ ARCore
  • เพิ่มปุ่มบันทึกลงในแอปเพื่อบันทึกเซสชัน AR เป็นไฟล์ MP4
  • เพิ่มปุ่ม "เล่น" ลงในแอปเพื่อเล่นเซสชัน AR จากไฟล์ MP4
  • เพิ่มฟีเจอร์ใหม่เพื่อบันทึก Anchor ที่ผู้ใช้สร้างไว้ใน MP4 เพื่อเล่น

สนุกกับการทำ Codelab นี้ไหม

ได้เรียนรู้สิ่งที่เป็นประโยชน์ในการทำ Codelab นี้ไหม

คุณสร้างแอปใน Codelab นี้เสร็จแล้วหรือยัง