Architecture de capture vidéo de CameraX

Un système de capture enregistre généralement des flux vidéo et audio, les compresse, multiplexe les deux flux, puis écrit le flux obtenu sur le disque.

Schéma conceptuel d'un système de capture vidéo et audio
Figure 1. Schéma conceptuel d'un système de capture vidéo et audio

Dans CameraX, la solution de capture vidéo est le cas d'utilisation VideoCapture :

Schéma conceptuel montrant comment CameraX gère la capture vidéo.
Figure 2. Schéma conceptuel montrant comment CameraX gère le cas d'utilisation VideoCapture.

Comme le montre la figure 2, la capture vidéo de CameraX inclut quelques composants architecturaux de haut niveau :

  • SurfaceProvider pour la source vidéo.
  • AudioSource pour la source audio.
  • Deux encodeurs permettent d'encoder et de compresser la vidéo et l'audio.
  • Un multiplexeur multimédia pour combiner les deux flux.
  • Un saver de fichier pour écrire le résultat.

L'API VideoCapture fait abstraction du moteur de capture complexe et fournit aux applications une API bien plus simple et directe.

Présentation de l'API VideoCapture

VideoCapture est un cas d'utilisation de CameraX qui fonctionne bien seul ou lorsqu'il est combiné à d'autres cas d'utilisation. Les combinaisons spécifiques compatibles dépendent des capacités matérielles de l'appareil photo, mais Preview et VideoCapture forment une combinaison valide pour tous les appareils.

L'API VideoCapture comprend les objets suivants qui communiquent avec les applications :

  • VideoCapture est la classe du cas d'utilisation de premier niveau. VideoCapture se lie à un LifecycleOwner avec un CameraSelector et à d'autres cas d'utilisation de CameraX. Pour en savoir plus sur ces concepts et ces utilisations, consultez la page Architecture de CameraX.
  • Un Recorder est une implémentation de VideoOutput étroitement couplée à VideoCapture. Recorder permet de capture du contenu vidéo et audio. Une application crée des enregistrements à partir d'un Recorder.
  • Un PendingRecording configure un enregistrement, ce qui permet d'activer l'audio et de définir un écouteur d'événements. Vous devez utiliser un Recorder pour créer un PendingRecording. Un PendingRecording n'enregistre rien.
  • Un Recording effectue l'enregistrement réel. Vous devez utiliser un PendingRecording pour créer un Recording.

La figure 3 illustre les relations entre ces objets :

Diagramme illustrant les interactions qui se produisent dans un cas d'utilisation de capture vidéo
Figure 3. Schéma illustrant les interactions dans un cas d'utilisation de VideoCapture.

Légende :

  1. Créez un Recorder avec QualitySelector.
  2. Configurez le Recorder avec l'une des OutputOptions.
  3. Si nécessaire, activez l'audio avec withAudioEnabled().
  4. Appelez start() avec un écouteur VideoRecordEvent pour commencer l'enregistrement.
  5. Utilisez pause()/resume()/stop() sur le Recording pour contrôler l'enregistrement.
  6. Répondez à VideoRecordEvents dans votre écouteur d'événements.

La liste détaillée des API se trouve dans le fichier current.txt à l'intérieur du code source.

Utiliser l'API VideoCapture

Pour intégrer le cas d'utilisation VideoCapture de CameraX dans votre application, procédez comme suit :

  1. Liez VideoCapture.
  2. Préparez et configurez l'enregistrement.
  3. Démarrez et contrôlez l'enregistrement de l'environnement d'exécution.

Les sections suivantes décrivent ce que vous pouvez faire à chaque étape pour obtenir une session d'enregistrement de bout en bout.

Lier VideoCapture

Pour lier le cas d'utilisation VideoCapure, procédez comme suit :

  1. Créez un objet Recorder.
  2. Créez un objet VideoCapture.
  3. Liez-les à un Lifecycle.

L'API CameraX VideoCapture respecte le schéma de conception du compilateur. Les applications utilisent Recorder.Builder pour créer un Recorder. Vous pouvez également configurer la résolution vidéo de Recorder via un objet QualitySelector.

CameraX Recorder est compatible avec les Qualities prédéfinies pour les résolutions vidéo :

  • Quality.UHD pour une vidéo 4K Ultra HD (2 160 p)
  • Quality.FHD pour une vidéo en Full HD (1 080 p)
  • Quality.HD pour une vidéo HD (720 p)
  • Quality.SD pour une vidéo SD (480 p)

Sachez que CameraX peut également choisir d'autres résolutions si l'application le permet.

La taille exacte de la vidéo de chaque sélection dépend des capacités de l'appareil photo et de l'encodeur. Pour en savoir plus, consultez la documentation pour CamcorderProfile.

Les applications peuvent configurer la résolution en créant un QualitySelector. Vous pouvez créer un QualitySelector à l'aide de l'une des méthodes suivantes :

  • Indiquez quelques résolutions privilégiées à l'aide de fromOrderedList() et incluez une stratégie de remplacement à utiliser si aucune des résolutions souhaitées n'est compatible.

    CameraX peut choisir la meilleure correspondance de remplacement en fonction de la capacité de l'appareil photo sélectionné. Pour en savoir plus, consultez la FallbackStrategy specification de QualitySelector. Par exemple, le code suivant demande la résolution la plus élevée pour l'enregistrement. Si aucune des résolutions de requête ne peut être acceptée, autorisez CameraX à en choisir une résolution qui se rapproche le plus de la résolution Quality.SD :

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD),
             FallbackStrategy.lowerQualityOrHigherThan(Quality.SD))
    
  • Commencez par interroger les capacités de l'appareil photo, puis choisissez l'une des résolutions compatibles à l'aide de QualitySelector::from() :

    val cameraInfo = cameraProvider.availableCameraInfos.filter {
        Camera2CameraInfo
        .from(it)
        .getCameraCharacteristic(CameraCharacteristics.LENS\_FACING) == CameraMetadata.LENS_FACING_BACK
    }
    
    val supportedQualities = QualitySelector.getSupportedQualities(cameraInfo[0])
    val filteredQualities = arrayListOf (Quality.UHD, Quality.FHD, Quality.HD, Quality.SD)
                           .filter { supportedQualities.contains(it) }
    
    // Use a simple ListView with the id of simple_quality_list_view
    viewBinding.simpleQualityListView.apply {
        adapter = ArrayAdapter(context,
                               android.R.layout.simple_list_item_1,
                               filteredQualities.map { it.qualityToString() })
    
        // Set up the user interaction to manually show or hide the system UI.
        setOnItemClickListener { _, _, position, _ ->
            // Inside View.OnClickListener,
            // convert Quality.* constant to QualitySelector
            val qualitySelector = QualitySelector.from(filteredQualities[position])
    
            // Create a new Recorder/VideoCapture for the new quality
            // and bind to lifecycle
            val recorder = Recorder.Builder()
                .setQualitySelector(qualitySelector).build()
    
             // ...
        }
    }
    
    // A helper function to translate Quality to a string
    fun Quality.qualityToString() : String {
        return when (this) {
            Quality.UHD -> "UHD"
            Quality.FHD -> "FHD"
            Quality.HD -> "HD"
            Quality.SD -> "SD"
            else -> throw IllegalArgumentException()
        }
    }
    
    

    Notez que la capacité renvoyée par QualitySelector.getSupportedQualities() garantit le fonctionnement avec le cas d'utilisation de VideoCapture ou pour la combinaison des cas d'utilisation VideoCapture et Preview. Lors de la liaison avec le cas d'utilisation ImageCapture ou ImageAnalysis, CameraX peut tout de même échouer si la combinaison requise n'est pas compatible avec l'appareil photo demandé.

Une fois que vous disposez d'un QualitySelector, l'application peut créer un objet VideoCapture et effectuer la liaison. Notez que cette liaison est identique à celle utilisée pour d'autres cas d'utilisation :

val recorder = Recorder.Builder()
    .setExecutor(cameraExecutor).setQualitySelector(qualitySelector)
    .build()
val videoCapture = VideoCapture.withOutput(recorder)

try {
    // Bind use cases to camera
    cameraProvider.bindToLifecycle(
            this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture)
} catch(exc: Exception) {
    Log.e(TAG, "Use case binding failed", exc)
}

Notez que bindToLifecycle() renvoie un objet Camera. Pour en savoir plus sur le contrôle de la sortie de l'appareil photo, comme le zoom et l'exposition, consultez ce guide.

Le Recorder sélectionne le format le plus adapté au système. Le codec vidéo le plus courant est H.264 AVC au format de conteneur MPEG-4.

Configurer et créer un enregistrement

À partir d'un Recorder, l'application peut créer des objets d'enregistrement pour effectuer la capture vidéo et audio. Les applications créent des enregistrements en procédant comme suit :

  1. Configurez OutputOptions avec le prepareRecording().
  2. (Facultatif) Activez l'enregistrement audio.
  3. Utilisez start() pour enregistrer un écouteur VideoRecordEvent et commencer à enregistrer des vidéos.

Le Recorder renvoie un objet Recording lorsque vous appelez la fonction start(). Votre application peut utiliser cet objet Recording pour terminer la capture ou effectuer d'autres actions, telles que la mise en pause ou la reprise.

Un objet Recorder accepte un seul objet Recording à la fois. Vous pouvez démarrer un nouvel enregistrement une fois que vous avez appelé Recording.stop() ou Recording.close() sur l'objet Recording précédent.

Examinons ces étapes plus en détail. Tout d'abord, l'application configure les OutputOptions pour un enregistreur avec Recorder.prepareRecording(). Un Recorder est compatible avec les types de OutputOptions suivants :

  • FileDescriptorOutputOptions pour la capture dans un FileDescriptor.
  • FileOutputOptions pour la capture dans un File.
  • MediaStoreOutputOptions pour la capture dans un MediaStore.

Tous les types OutputOptions vous permettent de définir une taille de fichier maximale avec setFileSizeLimit(). D'autres options sont propres au type de sortie individuel, par exemple ParcelFileDescriptor pour les FileDescriptorOutputOptions.

prepareRecording() renvoie un objet PendingRecording, qui est un objet intermédiaire utilisé pour créer l'objet Recording correspondant. PendingRecording est une classe temporaire qui doit être invisible dans la plupart des cas et qui est rarement mise en cache par l'application.

Les applications peuvent également configurer l'enregistrement, par exemple :

  • Activez l'audio avec withAudioEnabled().
  • Enregistrez un écouteur pour recevoir des événements d'enregistrement vidéo avec start(Executor, Consumer<VideoRecordEvent>).
  • Permet à un enregistrement d'enregistrer en continu alors que la VideoCapture à laquelle il est associé est reliée à une autre caméra, avec PendingRecording.asPersistentRecording().

Pour commencer l'enregistrement, appelez PendingRecording.start(). CameraX transforme le PendingRecording en un Recording, met la requête en file d'attente et renvoie l'objet Recording nouvellement créé à l'application. Une fois que l'enregistrement a commencé sur l'appareil photo correspondant, CameraX envoie un événement VideoRecordEvent.EVENT_TYPE_START.

L'exemple suivant montre comment enregistrer du contenu vidéo et audio dans un fichier MediaStore :

// Create MediaStoreOutputOptions for our recorder
val name = "CameraX-recording-" +
        SimpleDateFormat(FILENAME_FORMAT, Locale.US)
                .format(System.currentTimeMillis()) + ".mp4"
val contentValues = ContentValues().apply {
   put(MediaStore.Video.Media.DISPLAY_NAME, name)
}
val mediaStoreOutput = MediaStoreOutputOptions.Builder(this.contentResolver,
                              MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                              .setContentValues(contentValues)
                              .build()

// 2. Configure Recorder and Start recording to the mediaStoreOutput.
val recording = videoCapture.output
                .prepareRecording(context, mediaStoreOutput)
                .withAudioEnabled()
                .start(ContextCompat.getMainExecutor(this), captureListener)

Alors que par défaut, l'aperçu de la caméra avant est mis en miroir, ce n'est pas le cas des vidéos enregistrées par VideoCapture. Avec CameraX 1.3, il est maintenant possible de mettre en miroir les enregistrements vidéo afin que l'aperçu de la caméra avant et la vidéo enregistrée correspondent.

Trois options existent pour le mode MirrorMode : MIRROR_MODE_OFF, MIRROR_MODE_ON et MIRROR_MODE_ON_FRONT_ONLY. Pour aligner l'aperçu de la caméra, Google recommande d'utiliser MIROR_MODE_ON_FRONT_ONLY, ce qui signifie que la mise en miroir n'est pas activée pour la caméra arrière, mais l'est pour la caméra avant. Pour en savoir plus sur le mode MirrorMode, consultez MirrorMode constants.

Cet extrait de code vous montre comment appeler VideoCapture.Builder.setMirrorMode() à l'aide de MIRROR_MODE_ON_FRONT_ONLY. Pour en savoir plus, consultez setMirrorMode().

Kotlin


val recorder = Recorder.Builder().build()

val videoCapture = VideoCapture.Builder(recorder)
    .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
    .build()

useCases.add(videoCapture);

Java


Recorder.Builder builder = new Recorder.Builder();
if (mVideoQuality != QUALITY_AUTO) {
    builder.setQualitySelector(
        QualitySelector.from(mVideoQuality));
}
  VideoCapture<Recorder> videoCapture = new VideoCapture.Builder<>(builder.build())
      .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
      .build();
    useCases.add(videoCapture);

Contrôler un enregistrement actif

Vous pouvez suspendre, reprendre et arrêter un Recording en cours à l'aide des méthodes suivantes :

  • pause pour suspendre l'enregistrement actif en cours.
  • resume() pour reprendre un enregistrement actif mis en pause
  • stop() pour terminer l'enregistrement et vider tous les objets d'enregistrement associés.
  • mute() pour activer ou désactiver le son de l'enregistrement actuel.

Notez que vous pouvez appeler stop() pour arrêter un Recording, que l'enregistrement soit mis en pause ou actif.

Si vous avez enregistré un EventListener avec PendingRecording.start(), Recording communique à l'aide d'un VideoRecordEvent.

  • VideoRecordEvent.EVENT_TYPE_STATUS permet d'enregistrer des statistiques telles que la taille de fichier actuelle et la période enregistrée.
  • VideoRecordEvent.EVENT_TYPE_FINALIZE est utilisé pour le résultat de l'enregistrement et inclut des informations telles que l'URI du fichier final ainsi que les erreurs associées.

Une fois que votre application reçoit un EVENT_TYPE_FINALIZE indiquant une session d'enregistrement réussie, vous pouvez accéder à la vidéo capturée à partir de l'emplacement spécifié dans OutputOptions.

Ressources supplémentaires

Pour en savoir plus sur CameraX, consultez les ressources supplémentaires suivantes :