Architettura di acquisizione video con CameraX

Un sistema di acquisizione in genere registra flussi video e audio, li comprime, li multiplexa e quindi scrive il flusso risultante sul disco.

diagramma concettuale di un sistema di acquisizione video e audio
Figura 1. Diagramma concettuale di un sistema di acquisizione audio e video.

In CameraX, la soluzione per l'acquisizione video è il caso d'uso VideoCapture:

diagramma concettuale che mostra come Camera X gestisce il
         caso d'uso dell'acquisizione video
Figura 2. Diagramma concettuale che mostra come CameraX gestisce il caso d'uso VideoCapture.

Come mostrato nella figura 2, l'acquisizione video di CameraX include alcuni componenti architetturali di alto livello:

  • SurfaceProvider per la sorgente video.
  • AudioSource per la sorgente audio.
  • Due codificatori per codificare e comprimere video/audio.
  • Un muxer multimediale per muxare i due stream.
  • Un salvataggio di file per scrivere il risultato.

L'API VideoCapture astrae il complesso motore di acquisizione e fornisce alle applicazioni un'API molto più semplice e diretta.

Panoramica dell'API VideoCapture

VideoCapture è un caso d'uso di CameraX che funziona bene da solo o se combinato con altri casi d'uso. Le combinazioni specifiche supportate dipendono dalle funzionalità hardware della videocamera, ma Preview e VideoCapture sono una combinazione di casi d'uso valida su tutti i dispositivi.

L'API VideoCapture è costituita dai seguenti oggetti che comunicano con le applicazioni:

  • VideoCapture è la classe di casi d'uso di primo livello. VideoCapture si associa a un LifecycleOwner con un CameraSelector e altri UseCases di CameraX. Per ulteriori informazioni su questi concetti e utilizzi, vedi Architettura di CameraX.
  • Un Recorder è un'implementazione di VideoOutput strettamente accoppiata a VideoCapture. Recorder viene utilizzato per eseguire l'acquisizione di video e audio. Un'applicazione crea registrazioni da un Recorder.
  • Una PendingRecording configura una registrazione, fornendo opzioni come l'attivazione dell'audio e l'impostazione di un listener di eventi. Per creare un PendingRecording, devi utilizzare un Recorder. Un PendingRecording non registra nulla.
  • Un Recording esegue la registrazione effettiva. Per creare un Recording, devi utilizzare un PendingRecording.

La Figura 3 mostra le relazioni tra questi oggetti:

diagramma che mostra le interazioni che si verificano in un caso d'uso di acquisizione video
Figura 3. Diagramma che mostra le interazioni che si verificano in un caso d'uso di VideoCapture.

Legenda:

  1. Crea un Recorder con QualitySelector.
  2. Configura Recorder con uno dei OutputOptions.
  3. Se necessario, attiva l'audio con withAudioEnabled().
  4. Chiama start() con un VideoRecordEvent ascoltatore per iniziare la registrazione.
  5. Utilizza pause()/resume()/stop() sul Recording per controllare la registrazione.
  6. Rispondi a VideoRecordEvents all'interno del listener di eventi.

L'elenco dettagliato delle API è disponibile nel file current.txt all'interno del codice sorgente.

Utilizzo dell'API VideoCapture

Per integrare lo scenario d'uso VideoCapture di CameraX nella tua app, fai quanto segue:

  1. Associa VideoCapture.
  2. Prepara e configura la registrazione.
  3. Avvia e controlla la registrazione della durata.

Le sezioni seguenti descrivono cosa puoi fare in ogni passaggio per ottenere una sessione di registrazione end-to-end.

Bind VideoCapture

Per associare lo scenario d'uso VideoCapture:

  1. Crea un oggetto Recorder.
  2. Crea l'oggetto VideoCapture.
  3. Associa a un Lifecycle.

L'API CameraX VideoCapture segue il pattern di progettazione del builder. Le applicazioni utilizzano Recorder.Builder per creare un Recorder. Puoi anche configurare la risoluzione video per Recorder tramite un oggetto QualitySelector.

CameraX Recorder supporta le seguenti Qualities predefinite per le risoluzioni video:

  • Quality.UHD per le dimensioni dei video 4K Ultra HD (2160p)
  • Quality.FHD per le dimensioni video Full HD (1080p)
  • Quality.HD per le dimensioni dei video HD (720p)
  • Quality.SD per le dimensioni dei video SD (480p)

Tieni presente che CameraX può anche scegliere altre risoluzioni quando autorizzato dall'app.

Le dimensioni esatte del video di ogni selezione dipendono dalle funzionalità della videocamera e del codificatore. Per saperne di più, consulta la documentazione relativa a CamcorderProfile.

Le applicazioni possono configurare la risoluzione creando un QualitySelector. Puoi creare un QualitySelector utilizzando uno dei seguenti metodi:

  • Fornisci alcune risoluzioni preferite utilizzando fromOrderedList() e includi una strategia di fallback da utilizzare nel caso in cui nessuna delle risoluzioni preferite sia supportata.

    CameraX può decidere la migliore corrispondenza di riserva in base alla funzionalità della videocamera selezionata. Per maggiori dettagli, consulta QualitySelector's FallbackStrategy specification. Ad esempio, il seguente codice richiede la risoluzione più alta supportata per la registrazione e, se nessuna delle risoluzioni richieste può essere supportata, autorizza CameraX a scegliere quella più vicina alla risoluzione Quality.SD:

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD),
             FallbackStrategy.lowerQualityOrHigherThan(Quality.SD))
    
  • Esegui prima una query sulle funzionalità della videocamera e scegli tra le risoluzioni supportate utilizzando 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()
        }
    }
    
    

    Tieni presente che la funzionalità restituita da QualitySelector.getSupportedQualities() funziona sicuramente per il caso d'uso VideoCapture o per la combinazione dei casi d'uso VideoCapture e Preview. Quando viene eseguito il binding insieme al caso d'uso ImageCapture o ImageAnalysis, CameraX potrebbe comunque non riuscire a eseguire il binding quando la combinazione richiesta non è supportata dalla fotocamera richiesta.

Una volta ottenuto un QualitySelector, l'applicazione può creare un oggetto VideoCapture ed eseguire il binding. Tieni presente che questo binding è lo stesso degli altri casi d'uso:

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

Tieni presente che bindToLifecycle() restituisce un oggetto Camera. Per ulteriori informazioni sul controllo dell'output della videocamera, ad esempio zoom ed esposizione, consulta questa guida.

Recorder seleziona il formato più adatto al sistema. Il codec video più comune è H.264 AVC con formato contenitore MPEG-4.

Configurare e creare una registrazione

Da un Recorder, l'applicazione può creare oggetti di registrazione per eseguire l'acquisizione di video e audio. Le applicazioni creano registrazioni eseguendo le seguenti operazioni:

  1. Configura OutputOptions con prepareRecording().
  2. (Facoltativo) Attiva la registrazione audio.
  3. Utilizza start() per registrare un VideoRecordEvent ascoltatore e avvia l'acquisizione video.

Recorder restituisce un oggetto Recording quando chiami la funzione start(). La tua applicazione può utilizzare questo oggetto Recording per terminare l'acquisizione o per eseguire altre azioni, come la sospensione o la ripresa.

Un Recorder supporta un solo oggetto Recording alla volta. Puoi iniziare una nuova registrazione dopo aver chiamato Recording.stop() o Recording.close() sull'oggetto Recording precedente.

Esaminiamo questi passaggi più nel dettaglio. Innanzitutto, l'applicazione configura OutputOptions per un registratore con Recorder.prepareRecording(). Un Recorder supporta i seguenti tipi di OutputOptions:

  • FileDescriptorOutputOptions per l'acquisizione in un FileDescriptor.
  • FileOutputOptions per l'acquisizione in un File.
  • MediaStoreOutputOptions per l'acquisizione in un MediaStore.

Tutti i tipi OutputOptions consentono di impostare una dimensione massima del file con setFileSizeLimit(). Altre opzioni sono specifiche per il singolo tipo di output, ad esempio ParcelFileDescriptor per FileDescriptorOutputOptions.

prepareRecording() restituisce un oggetto PendingRecording, che è un oggetto intermedio utilizzato per creare l'oggetto Recording corrispondente. PendingRecording è una classe temporanea che dovrebbe essere invisibile nella maggior parte dei casi e raramente viene memorizzata nella cache dall'app.

Le applicazioni possono configurare ulteriormente la registrazione, ad esempio:

  • Attiva l'audio con withAudioEnabled().
  • Registra un listener per ricevere eventi di registrazione video con start(Executor, Consumer<VideoRecordEvent>).
  • Consenti a una registrazione di registrare continuamente mentre VideoCapture a cui è collegata viene riassegnato a un'altra videocamera, con PendingRecording.asPersistentRecording().

Per avviare la registrazione, chiama il numero PendingRecording.start(). CameraX trasforma PendingRecording in un Recording, mette in coda la richiesta di registrazione e restituisce all'applicazione l'oggetto Recording appena creato. Una volta avviata la registrazione sul dispositivo videocamera corrispondente, CameraX invia un evento VideoRecordEvent.EVENT_TYPE_START.

L'esempio seguente mostra come registrare video e audio in un file 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)

Mentre l'anteprima della videocamera viene sottoposta a mirroring sulla videocamera anteriore per impostazione predefinita, i video registrati da VideoCapture non vengono sottoposti a mirroring per impostazione predefinita. Con CameraX 1.3, ora è possibile eseguire il mirroring delle registrazioni video in modo che l'anteprima della fotocamera anteriore e il video registrato corrispondano.

Esistono tre opzioni per MirrorMode: MIRROR_MODE_OFF, MIRROR_MODE_ON e MIRROR_MODE_ON_FRONT_ONLY. Per allinearsi all'anteprima della fotocamera, Google consiglia di utilizzare MIROR_MODE_ON_FRONT_ONLY, il che significa che il mirroring non è abilitato per la fotocamera posteriore, ma è abilitato per quella anteriore. Per saperne di più su MirrorMode, vedi MirrorMode constants.

Questo snippet di codice mostra come chiamare VideoCapture.Builder.setMirrorMode() utilizzando MIRROR_MODE_ON_FRONT_ONLY. Per maggiori informazioni, vedi 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);

Controllare una registrazione attiva

Puoi mettere in pausa, riprendere e interrompere un Recording in corso utilizzando i seguenti metodi:

  • pause per mettere in pausa la registrazione attiva corrente.
  • resume() per riprendere una registrazione attiva in pausa.
  • stop() per terminare la registrazione ed eliminare gli eventuali oggetti di registrazione associati.
  • mute() per disattivare o riattivare l'audio della registrazione corrente.

Tieni presente che puoi chiamare il numero stop() per terminare una Recording indipendentemente dal fatto che la registrazione sia in stato di pausa o attiva.

Se hai registrato un EventListener con PendingRecording.start(), il Recording comunica utilizzando un VideoRecordEvent.

  • VideoRecordEvent.EVENT_TYPE_STATUS viene utilizzato per registrare statistiche come le dimensioni attuali del file e l'intervallo di tempo registrato.
  • VideoRecordEvent.EVENT_TYPE_FINALIZE viene utilizzato per il risultato della registrazione e include informazioni quali l'URI del file finale insieme a eventuali errori correlati.

Una volta che l'app riceve un EVENT_TYPE_FINALIZE che indica una sessione di registrazione riuscita, puoi accedere al video acquisito dalla posizione specificata in OutputOptions.

Risorse aggiuntive

Per saperne di più su CameraX, consulta le seguenti risorse aggiuntive: