Architettura di acquisizione video con CameraX

In genere, un sistema di acquisizione registra stream video e audio, li comprime, esegue il Mux dei due flussi e scrive il flusso risultante su disco.

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

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

diagramma concettuale che mostra in che modo la fotocamera x gestisce il caso d'uso dell'acquisizione video
Figura 2. Diagramma concettuale che mostra in che modo CameraX gestisce il caso d'uso di VideoCapture.

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

  • SurfaceProvider per l'origine video.
  • AudioSource per la sorgente audio.
  • Due codificatori per codificare e comprimere i contenuti video/audio.
  • Un media muxer per eseguire il mux dei due stream.
  • Un salvaschermo per scrivere il risultato.

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

Panoramica dell'API Video Capture

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

L'API Video Capture è composta dai seguenti oggetti che comunicano con le applicazioni:

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

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 Video Capture.

Legenda:

  1. Crea una Recorder con QualitySelector.
  2. Configura Recorder con uno degli OutputOptions.
  3. Abilita l'audio con withAudioEnabled(), se necessario.
  4. Chiama start() con un ascoltatore VideoRecordEvent per iniziare a registrare.
  5. Usa pause()/resume()/stop() sulla Recording per controllare la registrazione.
  6. Rispondi a VideoRecordEvents all'interno del listener di eventi.

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

Utilizzo dell'API Video Capture

Per integrare il caso d'uso VideoCapture di CameraX nella tua app, procedi nel seguente modo:

  1. Associa VideoCapture.
  2. Preparare e configurare la registrazione.
  3. Avvia e controlla la registrazione di runtime.

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

Associa acquisizione video

Per associare il caso d'uso VideoCapure:

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

L'API CameraX Video Capture segue il pattern di progettazione dello strumento per la creazione. 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 risoluzioni video Qualities predefinite:

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

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

Le dimensioni esatte del video di ogni selezione dipendono dalle capacità della videocamera e del codificatore. Per ulteriori informazioni, 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 delle risoluzioni preferite utilizzando fromOrderedList() e includi una strategia di riserva da utilizzare nel caso in cui nessuna delle risoluzioni preferite sia supportata.

    CameraX può decidere la corrispondenza di riserva migliore in base alle funzionalità della videocamera selezionata. Per ulteriori dettagli, consulta FallbackStrategy specification di QualitySelector. Ad esempio, il codice seguente richiede la risoluzione massima supportata per la registrazione e, se nessuna delle risoluzioni 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))
    
  • Prima esegui una query sulle funzionalità della fotocamera 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() è garantita per il funzionamento sia per il caso d'uso VideoCapture o per la combinazione di casi d'uso VideoCapture e Preview. Quando si esegue l'associazione insieme al caso d'uso ImageCapture o ImageAnalysis, CameraX potrebbe comunque non riuscire a eseguire l'associazione quando la combinazione richiesta non è supportata sulla videocamera richiesta.

Una volta che hai un QualitySelector, l'applicazione può creare un oggetto VideoCapture ed eseguire l'associazione. Tieni presente che questa associazione è uguale agli 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. Consulta questa guida per ulteriori informazioni su come controllare l'output della fotocamera, come lo zoom e l'esposizione.

L'Recorder seleziona il formato più adatto per il sistema. Il codec video più comune è H.264 AVC) con formato container MPEG-4.

Configura e crea registrazione

Da un Recorder, l'applicazione può creare oggetti registrazione per eseguire l'acquisizione video e audio. Le applicazioni creano registrazioni procedendo nel seguente modo:

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

Recorder restituisce un oggetto Recording quando chiami la funzione start(). L'applicazione può utilizzare questo oggetto Recording per completare l'acquisizione o eseguire altre azioni, ad esempio mettere in pausa o riprendere.

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

Esaminiamo questi passaggi in modo più dettagliato. Innanzitutto, l'applicazione configura OutputOptions per un Registratore con Recorder.prepareRecording(). Un Recorder supporta i seguenti tipi di OutputOptions:

  • FileDescriptorOutputOptions per acquisire immagini in una FileDescriptor.
  • FileOutputOptions per acquisire immagini in una File.
  • MediaStoreOutputOptions per acquisire immagini in una MediaStore.

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

prepareRecording() restituisce un oggetto PendingRecording, ovvero un oggetto intermedio utilizzato per creare l'oggetto Recording corrispondente. PendingRecording è una classe temporanea che dovrebbe essere invisibile nella maggior parte dei casi e che 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 in modo continuo mentre la funzionalità Video Capture a cui è collegata viene riportata a un'altra fotocamera, con PendingRecording.asPersistentRecording().

Per avviare la registrazione, chiama il numero PendingRecording.start(). CameraX trasforma PendingRecording in Recording, mette in coda la richiesta di registrazione e restituisce all'applicazione l'oggetto Recording appena creato. Una volta avviata la registrazione sulla 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)

Per impostazione predefinita, l'anteprima della fotocamera è visibile sulla fotocamera anteriore, mentre i video registrati da Video Capture 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.

Sono disponibili tre opzioni 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 la mirroring non è abilitata per la fotocamera posteriore, ma è abilitata per la fotocamera anteriore. Per saperne di più su MirrorMode, consulta MirrorMode constants.

Questo snippet di codice mostra come chiamare VideoCapture.Builder.setMirrorMode() utilizzando MIRROR_MODE_ON_FRONT_ONLY. Per ulteriori informazioni, visita la pagina 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 in quel momento.
  • resume() per riprendere una registrazione attiva in pausa.
  • stop() per terminare la registrazione e svuotare gli eventuali oggetti registrati associati.
  • mute() per disattivare o riattivare l'audio della registrazione corrente.

Tieni presente che puoi chiamare stop() per terminare Recording indipendentemente dal fatto che la registrazione sia in 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 la dimensione attuale 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 ed eventuali errori correlati.

Quando 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 scoprire di più su CameraX, consulta le seguenti risorse aggiuntive: