Présentation de l'API ARCore Recording and Playback

1. Présentation

La possibilité d'enregistrer une expérience de RA dans un fichier MP4 et de lancer la lecture depuis ce fichier peut être utile à la fois pour les développeurs d'applications et pour les utilisateurs finaux.

Déboguer et tester de nouvelles fonctionnalités depuis votre bureau

L'utilisation la plus simple de l'API ARCore Recording and Playback est destinée aux développeurs. L'époque où l'on devait créer et exécuter l'application sur un appareil de test, débrancher le câble USB et se déplacer afin d'effectuer un petit changement de code est révolue. Désormais, il vous suffit d'enregistrer un fichier MP4 dans l'environnement de test avec les mouvements attendus du téléphone, puis d'effectuer un test directement à partir de votre bureau.

Enregistrer et lire des contenus depuis différents appareils

Grâce aux API Recording and Playback, un utilisateur peut enregistrer une session sur un appareil et un autre utilisateur peut la lire sur un autre appareil. Il est possible de partager une expérience de RA avec un autre utilisateur. Les possibilités sont nombreuses.

Est-ce la première fois que vous créez une application ARCore ?

Non. Oui.

Comment allez-vous utiliser cet atelier de programmation ?

Je vais le lire uniquement Je vais le lire et effectuer les exercices

Ce que vous allez faire

Dans cet atelier de programmation, vous allez utiliser l'API Recording and Playback pour créer une application qui enregistre une expérience de RA dans un fichier MP4 et la relance à partir du même fichier. Vous allez découvrir :

  • comment utiliser l'API Recording pour enregistrer une session de RA dans un fichier MP4 ;
  • comment utiliser l'API Playback pour relancer une session de RA à partir d'un fichier MP4 ;
  • comment enregistrer une session de RA sur un appareil et la relancer sur un autre appareil.

Ce dont vous avez besoin

Dans cet atelier de programmation, vous allez modifier l'application Hello AR Java créée avec le SDK Android ARCore. Pour cela, vous aurez besoin de matériel et de logiciels spécifiques.

Matériel requis

Logiciels requis

Pour un résultat optimal, vous devez également connaître les concepts de base d'ARCore.

2. Configurer l'environnement de développement

Commencez par configurer votre environnement de développement.

Télécharger le SDK ARCore pour Android

Cliquez sur  pour télécharger le SDK.

Décompresser le SDK Android ARCore

Une fois que vous avez téléchargé le SDK Android sur votre ordinateur, décompressez le fichier et accédez au répertoire arcore-android-sdk-1.24/samples/hello_ar_java. Il s'agit du répertoire racine de l'application avec laquelle vous allez travailler.

hello-ar-java-extracted

Charger la bibliothèque Hello AR Java dans Android Studio

Lancez Android Studio, puis cliquez sur Open an existing Android Studio project (Ouvrir un projet Android Studio existant).

android-studio-open-projects

Dans la boîte de dialogue qui s'affiche, sélectionnez arcore-android-sdk-1.24/samples/hello_ar_java et cliquez sur Open (Ouvrir).

Attendez qu'Android Studio effectue la synchronisation du projet. S'il manque un ou plusieurs composants, l'importation du projet peut échouer et générer des messages d'erreur. Corrigez ces problèmes avant de continuer.

Exécuter l'exemple d'application

  1. Connectez un appareil compatible ARCore à votre ordinateur de développement.
  2. Si l'appareil est reconnu correctement, son nom doit s'afficher dans Android Studio. android-studio-pixel-5.png
  3. Cliquez sur le bouton "Run" (Exécuter) ou sélectionnez Run > Run "app" (Exécuter > Exécuter "application") pour qu'Android Studio installe et lance l'application sur l'appareil. android-studio-run-button.png
  4. Un message vous demandant l'autorisation de prendre des photos et d'enregistrer des vidéos s'affiche. Sélectionnez While using this app (Lors de l'utilisation de cette application) pour accorder à l'application les autorisations d'accès pour l'appareil photo. Votre environnement réel s'affiche sur l'écran de l'appareil. hello-ar-java-permission
  5. Déplacez l'appareil horizontalement pour rechercher des surfaces planes.
  6. Une grille de couleur blanche apparaît lorsque l'application détecte une surface plane. Appuyez dessus pour placer un repère sur cette surface plane. Emplacement de Hello AR

Opérations réalisées à cette étape

  • Configuration du projet Java Hello AR
  • Création et exécution de l'exemple d'application sur un appareil compatible ARCore

Vous allez maintenant enregistrer une session de RA dans un fichier MP4.

3. Enregistrer une session ARCore dans un fichier MP4

Nous allons ajouter la fonctionnalité d'enregistrement à cette étape. Elle se compose des éléments suivants :

  • Bouton pour démarrer ou arrêter l'enregistrement
  • Fonctions de stockage pour enregistrer le fichier MP4 sur l'appareil
  • Appels pour démarrer ou arrêter l'enregistrement d'une session ARCore

Ajouter un bouton pour l'enregistrement dans l'interface utilisateur

Avant d'implémenter l'enregistrement, ajoutez un bouton dans l'UI afin que l'utilisateur puisse signaler à ARCore quand démarrer ou arrêter l'enregistrement.

Dans le panneau "Project", ouvrez le fichier app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

Par défaut, Android Studio utilise la vue Conception une fois que vous avez ouvert le fichier app/res/layout/activity_main.xml. Cliquez sur le bouton Code en haut à droite de l'onglet pour passer à la vue Code.

swith-to-the-code-view.png

Dans activity_main.xml, ajoutez le code suivant avant la balise de fermeture pour créer le bouton Record (Enregistrement) et définissez son gestionnaire d'événements sur une méthode nommée 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" />

Une fois le code ci-dessus ajouté, une erreur peut s'afficher temporairement : Corresponding method handler 'public void onClickRecord(android.view.View)' not found". Ce comportement est normal. Vous allez résoudre l'erreur en créant la fonction onClickRecord() lors des prochaines étapes.

Modifier le texte du bouton en fonction de son état

Le bouton Record gère de fait l'enregistrement et son arrêt. Lorsque l'application n'enregistre pas de données, le bouton doit afficher le mot "Record" (Enregistrement). Lorsque l'application enregistre des données, le bouton doit alors afficher le mot "Stop" (Arrêt).

Pour que le bouton se comporte ainsi, l'application doit connaître son état actuel. Le code suivant crée une énumération appelée AppState pour représenter l'état de fonctionnement de l'application et suit les changements d'état spécifiques via une variable de membre privé nommée appState. Ajoutez-le au fichier HelloArActivity.java au début de la classe HelloArActivity.

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

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

Maintenant que vous pouvez suivre l'état interne de l'application, créez une fonction nommée updateRecordButton() qui modifie le texte du bouton en fonction de l'état actuel de l'application. Ajoutez le code suivant dans la classe HelloArActivity dans 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;
    }
  }

Ensuite, créez la méthode onClickRecord() qui vérifie l'état de l'application, le remplace par l'état suivant et appelle updateRecordButton() pour modifier l'UI du bouton. Ajoutez le code suivant dans la classe HelloArActivity dans 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();
  }

Permettre à l'application de démarrer l'enregistrement

Pour lancer l'enregistrement dans ARCore, vous n'avez que deux opérations à effectuer :

  1. Spécifier l'URI du fichier d'enregistrement dans un objet RecordingConfig
  2. Appeler session.startRecording avec l'objet RecordingConfig

Le reste n'est que du code récurrent : configuration, journalisation et vérification de l'exactitude.

Créez une fonction appelée startRecording() qui enregistre les données et les sauvegarde dans un URI MP4. Ajoutez le code suivant dans la classe HelloArActivity dans 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;
  }

Pour mettre en pause et reprendre une session ARCore en toute sécurité, créez les fonctions pauseARCoreSession() et resumeARCoreSession() dans 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;
  }

Permettre à l'application d'arrêter l'enregistrement

Créez une fonction appelée stopRecording() dans HelloArActivity.java pour empêcher votre application d'enregistrer de nouvelles données. Cette fonction appelle session.stopRecording() et envoie une erreur au journal de la console si l'application ne parvient pas arrêter l'enregistrement.

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

Concevoir le stockage de fichiers en utilisant l'espace de stockage cloisonné d'Android 11

Les fonctions liées au stockage dans cet atelier de programmation sont conçues conformément aux nouvelles exigences d'espace de stockage cloisonné d'Android 11.

Effectuez quelques modifications mineures dans le fichier app/build.gradle pour cibler Android 11. Dans le panneau du projet Android Studio, ce fichier se trouve sous le nœud Gradle Scripts (Scripts Gradle) associé au module de l'application.

app-build.gradle.png

Définissez compileSdkVersion et targetSdkVersion sur 30.

    compileSdkVersion 30
    defaultConfig {
      targetSdkVersion 30
    }

Pour l'enregistrement, utilisez l'API Android MediaStore pour créer le fichier MP4 dans le répertoire "Movies" (Films) partagé.

Créez une fonction appelée createMp4File() dans 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;
  }

Gérer les autorisations de stockage

Si vous utilisez un appareil Android 11, vous pouvez commencer à tester le code. Pour assurer la compatibilité avec les appareils Android 10 ou versions antérieures, vous devez accorder les autorisations de stockage à l'application pour qu'elle puisse enregistrer les données dans le système de fichiers de l'appareil cible.

Dans AndroidManifest.xml, déclarez que l'application a besoin d'autorisations en lecture et en écriture sur le stockage avant Android 11 (niveau d'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" />

Ajoutez une fonction d'assistance appelée checkAndRequestStoragePermission() dans HelloArActivity.java pour demander les autorisations WRITE_EXTERNAL_STORAGE pendant l'exécution.

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

Si vous utilisez le niveau d'API 29 ou un niveau antérieur, ajoutez une vérification des autorisations de stockage avant createMp4File() et fermez la fonction plus tôt si l'application ne dispose pas des autorisations appropriées. Le niveau d'API 30 (Android 11) ne nécessite pas d'autorisation de stockage pour l'accès aux fichiers dans 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 ...
  }

Enregistrement à partir de l'appareil cible

C'est le moment de voir ce que vous avez créé jusqu'à présent. Connectez votre appareil mobile à votre ordinateur de développement, puis cliquez sur Run (Exécuter) dans Android Studio.

Un bouton rouge Record (Enregistrement) doit s'afficher en bas à gauche de l'écran. Si vous appuyez dessus, le texte est remplacé par Stop (Arrêt). Déplacez votre appareil pour enregistrer une session, puis cliquez sur le bouton Stop lorsque vous souhaitez mettre à fin à l'enregistrement. Cette action doit entraîner l'enregistrement d'un fichier nommé arcore-xxxxxx_xxxxxx.mp4 dans la mémoire de stockage externe de votre appareil.

record-button.png

Un nouveau fichier arcore-xxxxxx_xxxxxx.mp4 doit maintenant se trouver dans la mémoire de stockage externe de votre appareil. Sur les appareils Pixel 5, le chemin d'accès est /storage/emulated/0/Movies/. Vous le trouverez dans la fenêtre "Logcat" après avoir démarré un enregistrement.

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

Afficher l'enregistrement

Vous pouvez utiliser une application de système de fichiers telle que Files by Google pour afficher l'enregistrement ou le copier sur votre ordinateur de développement. Voici les deux commandes adb permettant de lister et de récupérer les fichiers à partir de l'appareil Android :

  • adb shell ls '$EXTERNAL_STORAGE/Movies/*' pour afficher les fichiers du répertoire "Movies" dans l'espace de stockage externe de l'appareil
  • adb pull /absolute_path_from_previous_adb_shell_ls/arcore-xxxxxxxx_xxxxxx.mp4 pour copier le fichier de l'appareil vers l'ordinateur de développement

Voici un exemple de résultat obtenu avec ces deux commandes (depuis 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

Opérations réalisées à cette étape

  • Ajout d'un bouton pour démarrer et arrêter l'enregistrement
  • Implémentation de fonctions pour démarrer et arrêter l'enregistrement
  • Test de l'application sur l'appareil
  • Copie du fichier MP4 enregistré vers votre ordinateur et validation de ce fichier

Vous allez ensuite relancer une session de RA à partir d'un fichier MP4.

4. Lire une session ARCore à partir d'un fichier MP4

Vous disposez désormais d'un bouton Record et de certains fichiers MP4 contenant des sessions enregistrées. Vous allez maintenant lire ces fichiers à l'aide de l'API ARCore Playback.

Ajouter un bouton à l'interface utilisateur pour la lecture

Avant d'implémenter la lecture, ajoutez un bouton dans l'interface utilisateur afin que l'utilisateur puisse signaler à ARCore quand démarrer ou arrêter la lecture de la session.

Dans le panneau Project (Projet), ouvrez le fichier app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

Dans activity_main.xml, ajoutez le code ci-dessous avant la balise de fermeture pour créer le bouton Playback (Lecture) et définissez son gestionnaire d'événements sur une méthode appelée onClickPlayback(). Ce bouton est semblable au bouton Record et s'affiche à droite de l'écran.

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

Actualiser les boutons pendant la lecture

L'application dispose désormais d'un nouvel état nommé Playingback. Mettez à jour l'énumération AppState et toutes les fonctions existantes qui prennent appState comme argument pour gérer ce processus.

Ajoutez Playingback à l'énumération AppState dans HelloArActivity.java :

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

Si le bouton Record est toujours affiché à l'écran pendant la lecture, l'utilisateur risque de cliquer dessus accidentellement. Pour éviter cela, masquez le bouton Record pendant la lecture. Ainsi, vous n'avez pas besoin de gérer l'état Playingback dans onClickRecord().

Modifiez la fonction updateRecordButton() dans HelloArActivity.java pour masquer le bouton Record lorsque l'application est à l'état 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;
    }
  }

De même, masquez le bouton Playback lorsque l'utilisateur enregistre une session, et remplacez-le par "Stop" lorsqu'une lecture est en cours. L'utilisateur peut ainsi arrêter la lecture sans attendre qu'elle se termine automatiquement.

Ajoutez une fonction updatePlaybackButton() dans 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;
    }
  }

Enfin, mettez à jour onClickRecord() pour appeler updatePlaybackButton(). Ajoutez la ligne suivante au fichier HelloArActivity.java :

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

Sélectionner un fichier à l'aide du bouton de lecture

Lorsque l'utilisateur appuie sur le bouton Playback, il doit être invité à sélectionner un fichier à lire. Sur Android, la sélection de fichiers est gérée dans le sélecteur de fichiers système dans une autre activité. Cette opération est effectuée à l'aide de SAF (Storage Access Framework). Une fois que l'utilisateur a sélectionné un fichier, l'application reçoit un rappel nommé onActivityResult(). Vous lancerez la lecture dans cette fonction de rappel.

Dans HelloArActivity.java, créez une fonction onClickPlayback() pour effectuer la sélection de fichiers et arrêter la lecture.

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

Dans HelloArActivity.java, créez une fonction selectFileToPlayback() qui sélectionne un fichier sur l'appareil. Pour sélectionner un fichier dans le système de fichiers Android, utilisez un 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 est une constante qui permet d'identifier cette requête. Vous pouvez la définir à l'aide de n'importe quelle valeur d'espace réservé au sein de HelloArActivity dans HelloArActivity.java :

  private int REQUEST_MP4_SELECTOR = 1;

Ignorez la fonction onActivityResult() dans HelloArActivity.java pour gérer le rappel à partir du sélecteur de fichier.

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

Permettre à l'application de démarrer la lecture

Une session ARCore nécessite trois appels d'API pour la lecture d'un fichier MP4 :

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

Dans HelloArActivity.java, créez la fonction 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;
  }

Permettre à l'application d'arrêter la lecture

Créez une fonction appelée stopPlayingback() dans HelloArActivity.java pour gérer les changements d'état de l'application après :

  1. l'arrêt de la lecture du fichier MP4 par l'utilisateur ;
  2. l'arrêt automatique de la lecture à la fin du fichier MP4.

Si l'utilisateur a arrêté la lecture, l'application doit revenir à l'état dans lequel elle se trouvait quand il l'a lancée pour la première fois.

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

La lecture peut également s'arrêter naturellement lorsque le lecteur arrive à la fin du fichier MP4. Lorsque cela se produit, stopPlayingback() doit rétablir l'état de l'application sur Idle. Dans onDrawFrame(), vérifiez PlaybackStatus. Si la valeur est FINISHED, appelez la fonction stopPlayingback() sur le thread 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 ...
  }

Lire à partir de l'appareil cible

C'est le moment de voir ce que vous avez créé jusqu'à présent. Connectez votre appareil mobile à votre ordinateur de développement, puis cliquez sur Run (Exécuter) dans Android Studio.

Au lancement de l'application, un écran doit s'afficher avec un bouton rouge Record (Enregistrement) à gauche et un bouton vert Playback (Lecture) à droite.

playback-button.png

Appuyez sur le bouton Playback et sélectionnez l'un des fichiers MP4 que vous venez d'enregistrer. Si vous ne voyez aucun nom de fichier commençant par arcore-, il est possible que le dossier Movies ne soit pas visible sur votre appareil. Dans ce cas, accédez au dossier Modèle du téléphone > Movies à l'aide du menu situé en haut à gauche. Vous devrez peut-être aussi activer l'option Afficher la mémoire de stockage interne pour faire apparaître le dossier du modèle du téléphone.

show-internal-storage-button.png

nativate-to-movies-file-picker.jpg

Appuyez sur un nom de fichier à l'écran pour sélectionner le fichier MP4. L'application devrait lire le fichier MP4.

playback-stop-button.png

La différence entre la lecture d'une session et celle d'une vidéo ordinaire est la possibilité d'interagir avec la session enregistrée. Appuyez sur une surface plane détectée pour placer des repères à l'écran.

playback-placement

Opérations réalisées à cette étape

  • Ajout d'un bouton pour démarrer et arrêter la lecture
  • Implémentation d'une fonction permettant à l'application de démarrer et d'arrêter l'enregistrement
  • Lecture d'une session ARCore précédemment enregistrée sur l'appareil

5. Enregistrer des données supplémentaires dans le fichier MP4

ARCore 1.24 permet d'enregistrer des informations supplémentaires dans le fichier MP4. Vous pouvez enregistrer la Pose des emplacements d'objets RA, puis créer les objets RA au même emplacement pendant la lecture.

Configurer la nouvelle piste à enregistrer

Définissez une nouvelle piste avec un UUID et un tag MIME dans le fichier 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 ...
  }

Mettez à jour le code existant pour créer l'objet RecordingConfig avec un appel à 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 ...
  }

Enregistrer la position de l'ancre lors de l'enregistrement

Chaque fois que l'utilisateur appuie sur une surface plane détectée, un repère RA est placé sur une ancre (Anchor), dont la position est mise à jour par ARCore.

Enregistrez la position d'une Anchor au niveau de l'image où elle est créée, si l'enregistrement de la session ARCore est toujours en cours.

Modifiez la fonction handleTap() dans 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 ...
  }

La raison pour laquelle nous conservons la Pose par rapport à la caméra plutôt que la Pose par rapport à l'environnement est que l'environnement de départ d'une session d'enregistrement et l'environnement de départ d'une session de lecture ne sont pas les mêmes. L'origine de l'environnement d'une session d'enregistrement correspond à la première fois où la session est relancée, lorsque Session.resume() est appelé pour la première fois. L'origine de l'environnement d'une session de lecture correspond au moment où la première image est enregistrée, lorsque Session.resume() est appelé pour la première fois après Session.startRecording().

Création d'une ancre pour la lecture

Recréer une Anchor est simple. Ajoutez une fonction appelée createRecordedAnchors() dans 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);
    }
  }

Appelez createRecordedAnchors() dans la fonction onDrawFrame() dans 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 ...
  }

Tester sur l'appareil cible

Connectez votre appareil mobile à votre ordinateur de développement, puis cliquez sur Run (Exécuter) dans Android Studio.

Appuyez sur le bouton Record pour enregistrer une session. Pendant l'enregistrement, appuyez sur les surfaces planes détectées pour placer quelques repères RA.

Une fois l'enregistrement terminé, appuyez sur le bouton Playback et sélectionnez le fichier que vous venez d'enregistrer. La lecture devrait commencer. Notez que les précédents repères RA s'affichent exactement là où vous avez appuyé dans l'application.

Voilà tout le codage que vous aurez à faire dans cet atelier de programmation.

6. Félicitations

Félicitations, vous avez terminé cet atelier de programmation. Récapitulons ce que vous avez accompli :

  • Création et exécution de l'exemple ARCore Hello AR Java
  • Ajout d'un bouton d'enregistrement à l'application pour enregistrer une session de RA dans un fichier MP4
  • Ajout d'un bouton de lecture à l'application pour lire une session de RA à partir d'un fichier MP4
  • Ajout d'une nouvelle fonctionnalité pour enregistrer les ancres créées par l'utilisateur dans le fichier MP4 pour la lecture

Est-ce que cet atelier de programmation vous a plu ?

Oui Non

Avez-vous appris des choses utiles dans cet atelier de programmation ?

Oui Non

Avez-vous terminé la création de l'application dans cet atelier de programmation ?

Oui Non