1. Введение
Возможность сохранять AR-контент в файл MP4 и воспроизводить его из этого файла может быть полезна как разработчикам приложений, так и конечным пользователям.
Отлаживайте и тестируйте новые функции, не вставая с рабочего стола.
Наиболее простое применение API записи и воспроизведения ARCore предназначено для разработчиков. Ушли в прошлое те времена, когда нужно было собирать и запускать приложение на тестовом устройстве, отключать USB-кабель и ходить вокруг, чтобы протестировать небольшое изменение кода. Теперь достаточно записать MP4-файл в тестовой среде с учетом ожидаемых движений телефона и протестировать прямо за рабочим столом.
Запись и воспроизведение с различных устройств.
Благодаря API для записи и воспроизведения, один пользователь может записать сессию на одном устройстве, а другой — воспроизвести ту же сессию на другом устройстве. Можно поделиться опытом дополненной реальности с другим пользователем. Возможностей очень много!
Это ваш первый опыт создания приложения на ARCore?
Как вы будете использовать этот практический урок?
Что вы построите
В этом практическом занятии вы будете использовать API записи и воспроизведения для создания приложения, которое одновременно записывает AR-контент в файл MP4 и воспроизводит его из того же файла. Вы узнаете:
- Как использовать API записи для сохранения сеанса дополненной реальности в файл MP4.
- Как использовать API воспроизведения для повторного воспроизведения сеанса дополненной реальности из файла MP4.
- Как записать сеанс дополненной реальности на одном устройстве и воспроизвести его на другом.
Что вам понадобится
В этом практическом занятии вы модифицируете Java-приложение Hello AR , созданное с использованием Android SDK ARCore . Для выполнения задания вам потребуется определенное оборудование и программное обеспечение.
Требования к оборудованию
- Устройство с поддержкой ARCore , включенными параметрами разработчика и отладкой по USB, подключенное к вашей машине для разработки через USB-кабель.
- Компьютер для разработки, на котором вы запускаете Android Studio.
- Доступ к интернету для загрузки библиотек в процессе разработки.
Требования к программному обеспечению
- Для работы с ARCore-устройством требуется Google Play Services for AR (ARCore) версии 1.24 или более поздней. Обычно эта служба устанавливается автоматически через Play Store . Вы также можете установить ее вручную на поддерживаемом ARCore-устройстве .
- На компьютере разработчика должна быть установлена программа Android Studio (версия 3.1 или более поздняя).
Для достижения наилучших результатов вам также необходимо иметь базовое понимание ARCore .
2. Настройте среду разработки.
Для начала настройте среду разработки.
Загрузите Android SDK ARCore.
Нажмите на ссылку чтобы загрузить 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.
Далее вам предстоит записать сеанс дополненной реальности в файл формата 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() , которая записывает данные и сохраняет их в формате MP4 URI. Добавьте следующий код в класс 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;
}
Разрешите приложению останавливать запись.
Создайте в файле HelloArActivity.java функцию с именем stopRecording() , чтобы остановить запись новых данных вашим приложением. Эта функция вызывает 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 Scripts , связанном с модулем приложения .

Измените значения 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" />
Добавьте в файл HelloArActivity.java вспомогательную функцию с именем checkAndRequestStoragePermission() для запроса разрешений 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/*'выводит список файлов в каталоге 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-файл на свой компьютер и проверил его подлинность.
Далее вам предстоит воспроизвести сеанс дополненной реальности из файла 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 выбор файла осуществляется в системном файловом редакторе в другом Activity. Это происходит с помощью 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 используйте Intent 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);
}
Разрешите приложению начать воспроизведение.
Для воспроизведения файла MP4 в рамках сессии ARCore требуется три вызова API:
-
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;
}
Разрешите приложению останавливать воспроизведение.
Создайте в файле HelloArActivity.java функцию с именем stopPlayingback() для обработки изменений состояния приложения после:
- Воспроизведение 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. Можно записать Pose объектов дополненной реальности, а затем во время воспроизведения создать объекты дополненной реальности в том же месте.
Настройте новую дорожку для записи.
Создайте новый трек с 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-маркер, положение которого обновляется с помощью 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 заново довольно просто. Добавьте функцию с именем 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.
Сначала нажмите кнопку «Запись» , чтобы записать сессию. Во время записи нажимайте на обнаруженные плоскости, чтобы разместить несколько маркеров дополненной реальности.
После остановки записи нажмите кнопку «Воспроизведение» и выберите только что записанный файл. Воспроизведение должно начаться. Обратите внимание, как ваши предыдущие точки размещения маркеров дополненной реальности отображаются именно в тот момент, когда вы нажимали на них в приложении.
Это всё, что вам нужно будет написать для этого практического задания.
6. Поздравляем!
Поздравляем, вы дошли до конца этого практического занятия! Давайте вспомним, что вы сделали в этом занятии:
- Собрал и запустил пример ARCore Hello AR на Java .
- В приложение добавлена кнопка «Запись», позволяющая сохранять сеанс дополненной реальности в файл формата MP4.
- В приложение добавлена кнопка «Воспроизведение» для воспроизведения сеанса дополненной реальности из файла MP4.
- Добавлена новая функция сохранения созданных пользователем якорей в формате MP4 для воспроизведения.