1. Введение
Возможность сохранить AR-опыт в файл MP4 и воспроизвести его из файла MP4 может быть полезна как разработчикам приложений, так и конечным пользователям.
Отлаживайте и тестируйте новые функции со своего рабочего стола
Самый простой вариант использования ARCore Record & Playback API — для разработчиков. Прошли те времена, когда вам приходилось создавать и запускать приложение на тестовом устройстве, отключать USB-кабель и ходить только для того, чтобы протестировать небольшое изменение кода. Теперь вам нужно только записать MP4 в тестовой среде при ожидаемом движении телефона и протестировать прямо со своего стола.
Запись и воспроизведение с разных устройств
С помощью API записи и воспроизведения один пользователь может записать сеанс на одном устройстве, а другой — воспроизвести тот же сеанс на другом устройстве. Можно поделиться опытом AR с другим пользователем. Есть много возможностей!
Вы впервые создаете приложение ARCore?
Как вы будете использовать эту кодовую лабораторию?
Что ты построишь
В этой лаборатории кода вы будете использовать API записи и воспроизведения для создания приложения, которое одновременно записывает AR-опыт в файл MP4 и воспроизводит его из того же файла. Вы узнаете:
- Как использовать API записи для сохранения сеанса AR в файл MP4.
- Как использовать API воспроизведения для воспроизведения сеанса AR из файла MP4.
- Как записать сеанс AR на одном устройстве и воспроизвести его на другом.
Что вам понадобится
В этой лаборатории кода вы измените Java-приложение Hello AR , созданное с помощью ARCore Android SDK . Для этого вам понадобится специальное оборудование и программное обеспечение.
Требования к оборудованию
- Устройство с поддержкой ARCore и включенными опциями разработчика . и включена отладка по USB, подключенная через USB-кабель к вашей машине разработки.
- Машина разработки, на которой вы запускаете Android Studio.
- Доступ к Интернету для загрузки библиотек во время разработки.
Требования к программному обеспечению
- Службы Google Play для AR (ARCore) 1.24 или более поздней версии на вашем устройстве ARCore, предназначенном для разработки. Эта служба обычно автоматически устанавливается на устройство через Play Store . Вы также можете установить его вручную на устройстве с поддержкой ARCore .
- Android Studio (v3.1 или новее) на машине разработки.
Для достижения наилучших результатов вам также необходимо иметь базовое понимание ARCore .
2. Настройте среду разработки
Начните с настройки среды разработки.
Загрузите ARCore Android SDK
Разархивируйте ARCore Android SDK
Загрузив Android SDK на свой компьютер, разархивируйте файл и перейдите в каталог arcore-android-sdk-1.24/samples/hello_ar_java
. Это корневой каталог приложения, с которым вы будете работать.
Загрузите Hello AR Java в Android Studio
Запустите Android Studio и нажмите « Открыть существующий проект Android Studio» .
В появившемся диалоговом окне выберите arcore-android-sdk-1.24/samples/hello_ar_java
и нажмите « Открыть» .
Подождите, пока Android Studio завершит синхронизацию проекта. Если компонент отсутствует, импорт проекта может завершиться неудачей с появлением сообщений об ошибках. Прежде чем продолжить, устраните эти проблемы.
Запустите пример приложения
- Подключите устройство с поддержкой ARCore к вашей машине разработки.
- Если устройство распознано правильно, вы должны увидеть имя устройства в Android Studio.
- Нажмите кнопку «Выполнить» или выберите «Выполнить» > «Запустить приложение», чтобы Android Studio установила и запустила приложение на устройстве.
- Вы увидите запрос на разрешение фотографировать и записывать видео. Выберите При использовании этого приложения , чтобы предоставить приложению разрешения на использование камеры. После этого вы увидите свою реальную среду на экране устройства.
- Переместите устройство горизонтально для сканирования плоскостей.
- Белая сетка появляется, когда приложение обнаруживает самолет. Нажмите на него, чтобы разместить маркер на этой плоскости.
Что вы сделали на этом этапе
- Настройка проекта Hello AR Java
- Создайте и запустите пример приложения на устройстве с поддержкой ARCore.
Далее вы запишете сеанс AR в файл MP4.
3. Запишите сеанс ARCore в файл MP4.
На этом этапе мы добавим функцию записи. Он состоит из:
- Кнопка для запуска или остановки записи.
- Функции хранения для сохранения файла MP4 на устройстве.
- Вызовы для запуска или остановки записи сеанса ARCore.
Добавить пользовательский интерфейс для кнопки записи
Прежде чем реализовать запись, добавьте кнопку в пользовательский интерфейс, чтобы пользователь мог сообщать ARCore, когда начинать или останавливать запись.
На панели «Проект» откройте файл app/res/layout/activity_main.xml
.
По умолчанию Android Studio будет использовать представление дизайна после открытия файла app/res/layout/activity_main.xml
. Нажмите кнопку «Код» в правом верхнем углу вкладки, чтобы переключиться на представление кода.
В 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()
в следующие несколько шагов.
Изменение текста на кнопке в зависимости от состояния
Кнопка «Запись» фактически управляет как записью, так и остановкой. Когда приложение не записывает данные, оно должно отображать слово «Запись». Когда приложение записывает данные, кнопка должна измениться и отобразить слово «Стоп».
Чтобы предоставить кнопке эту функциональность, приложение должно знать ее текущее состояние. Следующий код создает новое перечисление с именем 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()
для изменения пользовательского интерфейса кнопки. Добавьте следующий код в класс 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();
}
Включите приложение, чтобы начать запись.
Чтобы начать запись в ARCore, вам нужно сделать всего две вещи:
- Укажите URI файла записи в объекте
RecordingConfig
. - Вызов
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
Функции, связанные с хранилищем, в этой кодовой лаборатории разработаны в соответствии с новыми требованиями Android 11 к хранилищу .
Внесите небольшие изменения в файл app/build.gradle
для Android 11. На панели «Проект Android Studio» этот файл находится в узле «Сценарии Gradle» , связанном с модулем приложения .
Измените compileSdkVersion
и targetSdkVersion
на 30 .
compileSdkVersion 30
defaultConfig {
targetSdkVersion 30
}
Для записи используйте API Android MediaStore , чтобы создать файл 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
во внешнем хранилище вашего устройства.
Теперь у вас должен быть новый файл 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 для просмотра и получения файлов с устройства Android:
-
adb shell ls '$EXTERNAL_STORAGE/Movies/*'
для отображения файлов в каталоге фильмов во внешнем хранилище на устройстве. -
adb pull /absolute_path_from_previous_adb_shell_ls/arcore-xxxxxxxx_xxxxxx.mp4
, чтобы скопировать файл с устройства на машину разработки
Это пример вывода после использования этих двух команд (из 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, содержащих записанные сеансы. Теперь вы будете воспроизводить их с помощью API воспроизведения ARCore.
Добавить пользовательский интерфейс для кнопки воспроизведения
Прежде чем реализовать воспроизведение, добавьте кнопку в пользовательский интерфейс, чтобы пользователь мог сообщить ARCore, когда следует начать и остановить воспроизведение сеанса.
На панели «Проект» откройте файл app/res/layout/activity_main.xml
.
В 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
. Обновите перечисление AppState
и все существующие функции, которые принимают appState
в качестве аргумента, чтобы справиться с этим.
Добавьте Playingback
в перечисление 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 выбор файла осуществляется в системном средстве выбора файлов в другом действии. Это осуществляется через Storage Access Framework ( SAF ). Как только пользователь выбирает файл, приложение получает обратный вызов onActivityResult()
. Вы начнете фактическое воспроизведение внутри этой функции обратного вызова.
В 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
.
// 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
, чтобы обрабатывать обратный вызов из средства выбора файлов.
// 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 для воспроизведения файла MP4:
-
session.pause()
-
session.setPlaybackDataset()
-
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
для обработки изменений состояния приложения после:
- Воспроизведение MP4 было остановлено пользователем
- Воспроизведение 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()
в потоке пользовательского интерфейса.
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.
Когда приложение запустится, вы должны увидеть экран с красной кнопкой записи слева и зеленой кнопкой воспроизведения справа.
Нажмите кнопку «Воспроизведение» и выберите один из только что записанных файлов MP4. Если вы не видите имена файлов, начинающиеся с arcore-
, возможно, на вашем устройстве не отображается папка «Фильмы» . В этом случае перейдите в папку «Модель телефона» > «Фильмы» с помощью меню в верхнем левом углу. Вам также может потребоваться включить параметр «Показать внутреннюю память» , чтобы отобразить папку модели телефона.
Коснитесь имени файла на экране, чтобы выбрать файл MP4. Приложение должно воспроизвести файл MP4.
Разница между воспроизведением сеанса и воспроизведением обычного видео заключается в том, что вы можете взаимодействовать с записанным сеансом. Нажмите на обнаруженный самолет, чтобы разместить маркеры на экране.
Что вы сделали на этом этапе
- Добавлена кнопка запуска и остановки воспроизведения.
- Реализована функция запуска и остановки записи приложения.
- Воспроизведен ранее записанный сеанс ARCore на устройстве.
5. Запишите дополнительные данные в MP4.
В ARCore 1.24 можно записывать дополнительную информацию в файл MP4. Вы можете записать расположение объектов AR Pose
of 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 ...
}
Сохранять якорную позу во время записи
Каждый раз, когда пользователь нажимает на обнаруженную плоскость, маркер AR размещается на Anchor
, поза которого будет обновляться ARCore.
Запишите позу Anchor
в созданном кадре, если вы все еще записываете сеанс ARCore.
Измените функцию 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
очень просто. Добавьте функцию 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.
После остановки записи нажмите кнопку «Воспроизведение» и выберите файл, который вы только что записали. Воспроизведение должно начаться. Обратите внимание, как отображаются предыдущие размещения маркеров AR, когда вы нажимали на приложение.
Это все, что вам нужно будет написать для этой лаборатории кода.
6. Поздравления
Поздравляем, вы достигли конца этой лаборатории! Давайте еще раз посмотрим на то, что вы сделали в этой лаборатории кода:
- Создал и запустил образец ARCore Hello AR Java .
- В приложение добавлена кнопка «Запись», позволяющая сохранить сеанс AR в файл MP4.
- В приложение добавлена кнопка «Воспроизведение» для воспроизведения сеанса AR из файла MP4.
- Добавлена новая функция сохранения созданных пользователем привязок в MP4 для воспроизведения.