Arquitectura de captura de video de CameraX

Por lo general, un sistema de captura graba transmisiones de video y audio, las comprime, combina varias transmisiones y, luego, escribe la transmisión resultante en el disco.

diagrama conceptual de un sistema de captura de video y audio
Figura 1: Diagrama conceptual de un sistema de captura de video y audio

En CameraX, la solución para la captura de video es el caso de uso de VideoCapture:

diagrama conceptual que muestra cómo camerax maneja el caso de uso de captura de video
Figura 2: Diagrama conceptual que muestra cómo CameraX maneja el caso de uso de VideoCapture

Como se muestra en la figura 2, la captura de video de CameraX incluye algunos componentes arquitectónicos de alto nivel:

  • SurfaceProvider para la fuente de video
  • AudioSource para la fuente de audio
  • Dos codificadores para codificar y comprimir video/audio
  • Un combinador de medios para combinar las dos transmisiones
  • Un ahorro de archivos para escribir el resultado

La API de VideoCapture simplifica el motor de captura complejo y proporciona a las aplicaciones una API mucho más simple y directa.

Descripción general de la API de VideoCapture

VideoCapture es un caso de uso de CameraX que funciona bien por sí solo o cuando se combina con otros casos de uso. Las combinaciones específicas admitidas dependen de las capacidades del hardware de la cámara, aunque Preview y VideoCapture son una combinación válida de casos de uso en todos los dispositivos.

La API de VideoCapture consta de los siguientes objetos que se comunican con aplicaciones:

  • VideoCapture es la clase de caso de uso de nivel superior. VideoCapture se vincula a un LifecycleOwner con un CameraSelector y otros UseCases de CameraX. Para obtener más información sobre estos conceptos y usos, consulta Arquitectura de CameraX.
  • Un Recorder es una implementación de VideoOutput que tiene un acoplamiento alto con VideoCapture. Recorder se usa para realizar la captura de video y audio. Una aplicación crea grabaciones a partir de un Recorder.
  • Un PendingRecording configura una grabación y proporciona opciones como habilitar el audio y configurar un objeto de escucha de eventos. Debes usar Recorder para crear una PendingRecording. PendingRecording no graba nada.
  • Recording realiza la grabación. Debes usar PendingRecording para crear un Recording.

En la Figura 3, se muestran las relaciones entre estos objetos:

diagrama que muestra las interacciones en un caso de uso de videocapture
Figura 3: Diagrama que muestra las interacciones en un caso de uso de VideoCapture

Leyenda:

  1. Crea una Recorder con QualitySelector.
  2. Configura Recorder con una de las OutputOptions.
  3. Si es necesario, habilita el audio con withAudioEnabled().
  4. Llama a start() con un objeto de escucha VideoRecordEvent para comenzar a grabar.
  5. Usa pause()/resume()/stop() en Recording para controlar la grabación.
  6. Responde a VideoRecordEvents dentro del objeto de escucha de eventos.

La lista detallada de las API se encuentra en el archivo current.txt dentro del código fuente.

Cómo usar la API de VideoCapture

Para integrar el caso de uso de CameraX VideoCapture a tu app, haz lo siguiente:

  1. Vincula VideoCapture.
  2. Prepara y configura la grabación.
  3. Inicia y controla la grabación del tiempo de ejecución.

En las siguientes secciones, se describe lo que puedes hacer en cada paso para obtener una sesión de grabación completa.

Cómo vincular VideoCapture

Para vincular el caso de uso de VideoCapture, haz lo siguiente:

  1. Crea un objeto Recorder.
  2. Crea el objeto VideoCapture.
  3. Haz la vinculación a Lifecycle.

La API de CameraX VideoCapture sigue el patrón de diseño del compilador. Las aplicaciones usan Recorder.Builder para crear un Recorder. También puedes configurar la resolución del video para Recorder mediante un objeto QualitySelector.

CameraX Recorder admite las siguientes Qualities predefinidas para resoluciones de video:

  • Quality.UHD para videos con resolución 4K Ultra HD (2160p)
  • Quality.QHD para videos con resolución Quad HD (1440p), que se introdujo en CameraX 1.7.0-alpha02
  • Quality.FHD para videos con resolución Full HD (1080p)
  • Quality.HD para videos con resolución HD (720p)
  • Quality.SD para videos con resolución SD (480p)

Ten en cuenta que CameraX también podrá elegir otras resoluciones cuando lo autorice la app.

La resolución exacta del video de cada selección depende de la cámara y las capacidades del codificador. Para obtener más información, consulta la documentación de CamcorderProfile.

Para configurar la resolución, las aplicaciones pueden crear un QualitySelector. QualitySelector se puede crear con uno de los siguientes métodos:

  • Brinda algunas resoluciones preferidas mediante fromOrderedList() y agrega una estrategia de resguardo para usar en caso de que no se admita ninguna de las resoluciones preferidas.

    CameraX puede decidir la mejor coincidencia de resguardo según la capacidad de la cámara seleccionada. Para obtener más detalles, consulta el objeto FallbackStrategy specification de QualitySelector. Por ejemplo, el siguiente código solicita la resolución más alta compatible para la grabación y, si no se admite ninguna de las resoluciones de solicitud, autoriza a CameraX a elegir la que esté más cerca de la resolución Quality.SD:

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, Quality.QHD, Quality.FHD, Quality.HD, Quality.SD),
             FallbackStrategy.lowerQualityOrHigherThan(Quality.SD))
    
  • Consulta las capacidades de la cámara primero y elige una de las resoluciones compatibles mediante 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.QHD, 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.QHD -> "QHD"
            Quality.FHD -> "FHD"
            Quality.HD -> "HD"
            Quality.SD -> "SD"
            else -> throw IllegalArgumentException()
        }
    }
    
    

    Ten en cuenta que se garantiza que la capacidad que se muestra de QualitySelector.getSupportedQualities() funcione para el caso de uso de VideoCapture o la combinación de VideoCapture y Preview. Cuando se realiza la vinculación con el caso de uso de ImageCapture o ImageAnalysis, es posible que CameraX falle al hacerlo si la combinación requerida no es compatible con la cámara solicitada.

Una vez que tengas un QualitySelector, la aplicación podrá crear un objeto VideoCapture y realizar la vinculación. Ten en cuenta que esta vinculación es la misma que con otros casos de 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)
}

Ten en cuenta que bindToLifecycle() muestra un objeto Camera. Consulta esta guía a fin de obtener más información para controlar la salida de la cámara, como el zoom y la exposición.

Recorder selecciona el formato más adecuado para el sistema. El códec de video más común es H.264 AVC con formato de contenedor MPEG-4.

Cómo configurar y crear una grabación

Desde un Recorder, la aplicación puede crear objetos de grabación para realizar la captura de audio y de video. Las aplicaciones crean grabaciones de la siguiente manera:

  1. Configuran OutputOptions con prepareRecording().
  2. Habilita la grabación de audio (opcional).
  3. Usa start() para registrar un objeto de escucha VideoRecordEvent y comienza a capturar videos.

El objeto Recorder muestra un objeto Recording cuando llamas a la función start(). Tu aplicación puede usar este objeto Recording para finalizar la captura o realizar otras acciones, como pausar o reanudar.

Recorder admite un objeto Recording por vez. Puedes iniciar una grabación nueva una vez que hayas llamado a Recording.stop() o Recording.close() en el objeto Recording anterior.

Analicemos estos pasos con más detalle. Primero, la aplicación configura OutputOptions para una grabadora con Recorder.prepareRecording(). Recorder admite los siguientes tipos de OutputOptions:

  • FileDescriptorOutputOptions para hacer la captura en un FileDescriptor
  • FileOutputOptions para hacer la captura en un File
  • MediaStoreOutputOptions para hacer la captura en un MediaStore

Todos los tipos OutputOptions te permiten establecer un tamaño de archivo máximo con setFileSizeLimit(). Otras opciones son específicas del tipo de salida individual, como ParcelFileDescriptor para FileDescriptorOutputOptions.

prepareRecording() muestra un objeto PendingRecording, que es un objeto intermedio que se usa para crear la Recording correspondiente. PendingRecording es una clase transitoria que debería ser invisible en la mayoría de los casos y la app rara vez almacena en caché.

Las aplicaciones pueden configurar las siguientes opciones de la grabación:

  • Habilitar el audio con withAudioEnabled()
  • Registrar un objeto de escucha para recibir eventos de grabación de video con start(Executor, Consumer<VideoRecordEvent>)
  • Permitir una grabación continua mientras que el elemento VideoCapture adjunto se envía a otra cámara, con PendingRecording.asPersistentRecording().

Para comenzar a grabar, llama a PendingRecording.start(). CameraX convierte el PendingRecording en un Recording, pone en cola la solicitud de grabación y muestra el objeto Recording recién creado a la aplicación. Una vez que comienza la grabación en el dispositivo correspondiente, CameraX envía un evento VideoRecordEvent.EVENT_TYPE_START.

En el siguiente ejemplo, se muestra cómo grabar video y audio en un archivo 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)

La vista previa de la cámara se duplica en la cámara frontal de forma predeterminada, pero eso no sucede con los videos grabados por VideoCapture. Con CameraX 1.3, ahora es posible duplicar las grabaciones de video de modo que la vista previa de la cámara frontal coincida con el video grabado.

Existen tres opciones de MirrorMode: MIRROR_MODE_OFF, MIRROR_MODE_ON y MIRROR_MODE_ON_FRONT_ONLY. Para alinear la vista previa de la cámara, Google recomienda que uses MIRROR_MODE_ON_FRONT_ONLY, lo que significa que no se habilitará la duplicación para la cámara posterior, pero sí para la frontal. Para obtener más información sobre MirrorMode, consulta MirrorMode constants.

Este fragmento de código te muestra cómo llamar a VideoCapture.Builder.setMirrorMode() usando MIRROR_MODE_ON_FRONT_ONLY. Si deseas obtener información detallada, consulta 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);

Rango dinámico (video HDR)

CameraX admite la grabación de video de alto rango dinámico (HDR) de 10 bits, como HLG10, HDR10, HDR10+ o Dolby Vision, con DynamicRange.

Para usar la grabación de video en HDR, completa los siguientes pasos:

  1. Consulta los rangos dinámicos compatibles de la cámara con VideoCapabilities.getSupportedDynamicRanges().
  2. Opcional: Si configuras un QualitySelector personalizado, verifica qué calidades se admiten para ese rango dinámico con getSupportedQualities(DynamicRange).
  3. Configura VideoCapture.Builder con setDynamicRange().

En el siguiente ejemplo, se habilita la grabación en HDR de 10 bits con HLG cuando la cámara lo admite:

Kotlin

val videoCapabilities = Recorder.getVideoCapabilities(cameraInfo)
val supportedHdrRanges = videoCapabilities.supportedDynamicRanges
    .filter { it != DynamicRange.SDR }

val recorder = Recorder.Builder().build()
val videoCapture = VideoCapture.Builder(recorder)
    .apply {
        if (supportedHdrRanges.contains(DynamicRange.HLG_10_BIT)) {
            setDynamicRange(DynamicRange.HLG_10_BIT)
        }
    }
    .build()

Java

VideoCapabilities videoCapabilities = Recorder.getVideoCapabilities(cameraInfo);
Set<DynamicRange> supportedRanges = videoCapabilities.getSupportedDynamicRanges();

Recorder recorder = new Recorder.Builder().build();
VideoCapture.Builder<Recorder> builder = new VideoCapture.Builder<>(recorder);
if (supportedRanges.contains(DynamicRange.HLG_10_BIT)) {
    builder.setDynamicRange(DynamicRange.HLG_10_BIT);
}
VideoCapture<Recorder> videoCapture = builder.build();

Estabilización de video

CameraX admite la estabilización de video para reducir la vibración y la inestabilidad de la cámara durante la grabación de video.

Para habilitar la estabilización de video, sigue estos pasos:

  1. Verifica si el dispositivo de la cámara admite la estabilización con VideoCapabilities.isStabilizationSupported().
  2. Habilítalo en VideoCapture.Builder con setVideoStabilizationEnabled(true).

En el siguiente ejemplo, se habilita la estabilización cuando la cámara la admite:

Kotlin

val videoCapabilities = Recorder.getVideoCapabilities(cameraInfo)
val videoCapture = VideoCapture.Builder(recorder)
    .setVideoStabilizationEnabled(videoCapabilities.isStabilizationSupported)
    .build()

Java

VideoCapabilities videoCapabilities = Recorder.getVideoCapabilities(cameraInfo);
VideoCapture<Recorder> videoCapture = new VideoCapture.Builder<>(recorder)
    .setVideoStabilizationEnabled(videoCapabilities.isStabilizationSupported())
    .build();

Cómo controlar una grabación activa

Puedes pausar, reanudar y detener una Recording en curso mediante los siguientes métodos:

  • pause para pausar la grabación activa actual
  • resume() para reanudar una grabación activa en pausa
  • stop() para finalizar la grabación y limpiar los objetos de grabación asociados
  • mute() para silenciar o activar el sonido de la grabación actual

Ten en cuenta que puedes llamar a stop() para finalizar una Recording, independientemente de si la grabación está en estado de pausa o en estado activo.

Si registraste un EventListener con PendingRecording.start(), Recording se comunica mediante VideoRecordEvent.

  • VideoRecordEvent.EVENT_TYPE_STATUS se usa para grabar estadísticas, como el tamaño actual del archivo y el intervalo de tiempo grabado.
  • VideoRecordEvent.EVENT_TYPE_FINALIZE se usa para el resultado de la grabación y, además, incluye información como el URI del archivo final junto con cualquier error relacionado.

Una vez que la app reciba un EVENT_TYPE_FINALIZE que indique una sesión de grabación exitosa, podrás acceder al video capturado desde la ubicación que se haya especificado en OutputOptions.

Amplitud y estado del audio en tiempo real

Durante la grabación, los eventos VideoRecordEvent.Status proporcionan estadísticas de audio en tiempo real a través de AudioStats:

  • AudioStats.getAudioAmplitude() devuelve la amplitud máxima de audio actual, que varía de 0.0 a 1.0. Puedes usar este valor para controlar los medidores de la IU o los visualizadores de formas de onda de audio.
  • AudioStats.getAudioState() indica el estado actual de la transmisión de audio, como AUDIO_STATE_ACTIVE, AUDIO_STATE_MUTED o AUDIO_STATE_SOURCE_SILENCED.

En el siguiente ejemplo, se lee la amplitud de audio de un evento de estado:

Kotlin

when (event) {
    is VideoRecordEvent.Status -> {
        val audioStats = event.recordingStats.audioStats
        if (audioStats.hasAudio()) {
            val amplitude = audioStats.audioAmplitude
            updateAudioWaveform(amplitude)
        }
    }
    // ...
}

Java

if (event instanceof VideoRecordEvent.Status) {
    VideoRecordEvent.Status status = (VideoRecordEvent.Status) event;
    AudioStats audioStats = status.getRecordingStats().getAudioStats();
    if (audioStats.hasAudio()) {
        double amplitude = audioStats.getAudioAmplitude();
        updateAudioWaveform(amplitude);
    }
}

Videos de alta velocidad y en cámara lenta

CameraX admite la grabación de video de alta velocidad, como 120, 240 o 480 fps, lo que permite dos casos de uso distintos:

  • Video con alta velocidad de fotogramas (cámara lenta controlada por el reproductor): CameraX captura y guarda fotogramas con una alta velocidad de fotogramas (por ejemplo, 120 fps). El reproductor de video controla la velocidad de reproducción, lo que permite a los usuarios alternar entre la velocidad normal y la cámara lenta, o realizar un análisis fotograma por fotograma.
  • Video en cámara lenta listo para reproducir: CameraX procesa la transmisión de alta velocidad y genera un archivo de video estándar de 30 fps con marcas de tiempo de fotogramas ajustadas. El efecto de cámara lenta está "incorporado", por lo que el video se reproduce en cámara lenta automáticamente en cualquier reproductor de video estándar sin compatibilidad especial con el reproductor de video. Por ejemplo, una captura de 120 fps reproducida a 30 fps produce un video en cámara lenta de 1/4x.

Verifica las capacidades de alta velocidad

La captura de video de alta velocidad requiere compatibilidad de hardware y las definiciones de CamcorderProfile correspondientes de los fabricantes de dispositivos, como CamcorderProfile.QUALITY_HIGH_SPEED_1080P o QUALITY_HIGH_SPEED_720P.

Antes de configurar la grabación de alta velocidad, consulta las capacidades de alta velocidad con Recorder.getHighSpeedVideoCapabilities(CameraInfo):

Kotlin

val highSpeedCapabilities = Recorder.getHighSpeedVideoCapabilities(cameraInfo)
if (highSpeedCapabilities == null) {
    // Camera does not support high-speed video capture
    return
}
val supportedQualities = highSpeedCapabilities.getSupportedQualities(DynamicRange.SDR)

Java

VideoCapabilities highSpeedCapabilities = Recorder.getHighSpeedVideoCapabilities(cameraInfo);
if (highSpeedCapabilities == null) {
    // Camera does not support high-speed video capture
    return;
}
List<Quality> supportedQualities = highSpeedCapabilities.getSupportedQualities(DynamicRange.SDR);

Configura HighSpeedVideoSessionConfig

Para usar la captura de alta velocidad, combina tus casos de uso de VideoCapture y Preview en un HighSpeedVideoSessionConfig. Para configurar la sesión, completa los siguientes pasos:

  1. Crea un caso de uso de VideoCapture y un caso de uso de Preview.
  2. Inicializa HighSpeedVideoSessionConfig.Builder con el caso de uso VideoCapture y adjunta el Preview.
  3. Consulta los rangos de velocidad de fotogramas admitidos con CameraInfo.getSupportedFrameRateRanges(SessionConfig) y establece el rango seleccionado.
  4. Elige entre un video con una velocidad de fotogramas alta o un video en cámara lenta listo para reproducir:
    • Para crear un video con una velocidad de fotogramas alta, deja setSlowMotionEnabled(false) (predeterminado).
    • Para crear un video en cámara lenta listo para reproducir, llama a setSlowMotionEnabled(true).
  5. Vincula la configuración de la sesión con cameraProvider.bindToLifecycle().

En el siguiente ejemplo, se configura y vincula una sesión de cámara lenta lista para reproducirse:

Kotlin

val preview = Preview.Builder().build()
val recorder = Recorder.Builder()
    .setQualitySelector(QualitySelector.from(supportedQualities.first()))
    .build()
val videoCapture = VideoCapture.withOutput(recorder)

// Build HighSpeedVideoSessionConfig
val sessionConfigBuilder = HighSpeedVideoSessionConfig.Builder(videoCapture)
    .setPreview(preview)
    // Enable automatic slow-motion playback at 30 fps
    .setSlowMotionEnabled(true)

// Query and set a supported high-speed frame rate range (such as 120 or 240 fps).
// The set is non-empty because getHighSpeedVideoCapabilities() returned non-null earlier.
val supportedFpsRanges = cameraInfo.getSupportedFrameRateRanges(sessionConfigBuilder.build())
sessionConfigBuilder.setFrameRateRange(supportedFpsRanges.first())

val sessionConfig = sessionConfigBuilder.build()

// Bind session config to lifecycle
cameraProvider.bindToLifecycle(
    lifecycleOwner,
    CameraSelector.DEFAULT_BACK_CAMERA,
    sessionConfig
)

Java

Preview preview = new Preview.Builder().build();
Recorder recorder = new Recorder.Builder()
    .setQualitySelector(QualitySelector.from(supportedQualities.get(0)))
    .build();
VideoCapture<Recorder> videoCapture = VideoCapture.withOutput(recorder);

HighSpeedVideoSessionConfig.Builder sessionConfigBuilder =
    new HighSpeedVideoSessionConfig.Builder(videoCapture)
        .setPreview(preview)
        .setSlowMotionEnabled(true);

// The set is non-empty because getHighSpeedVideoCapabilities() returned non-null earlier.
Set<Range<Integer>> supportedFpsRanges =
    cameraInfo.getSupportedFrameRateRanges(sessionConfigBuilder.build());
sessionConfigBuilder.setFrameRateRange(supportedFpsRanges.iterator().next());

HighSpeedVideoSessionConfig sessionConfig = sessionConfigBuilder.build();

cameraProvider.bindToLifecycle(
    lifecycleOwner,
    CameraSelector.DEFAULT_BACK_CAMERA,
    sessionConfig
);

También puedes usar el DSL de Kotlin, que se introdujo en CameraX 1.7.0-alpha02, para configurar sesiones de alta velocidad de forma concisa:

val sessionConfig = highSpeedVideoSessionConfig(videoCapture) {
    this.preview = preview
    isSlowMotionEnabled = true
    frameRateRange = supportedFpsRanges.first()
}
cameraProvider.bindToLifecycle(
    lifecycleOwner,
    CameraSelector.DEFAULT_BACK_CAMERA,
    sessionConfig
)

Cómo grabar videos en alta velocidad o en cámara lenta

Después de vincular el HighSpeedVideoSessionConfig, comienza a grabar con VideoCapture de la misma manera que con las grabaciones de video estándar:

Kotlin

val recording = videoCapture.output
    .prepareRecording(context, mediaStoreOutput)
    .withAudioEnabled()
    .start(ContextCompat.getMainExecutor(context)) { event ->
        when (event) {
            is VideoRecordEvent.Start -> Log.d(TAG, "Recording started")
            is VideoRecordEvent.Finalize -> {
                if (!event.hasError()) {
                    Log.d(TAG, "Video saved: ${event.outputResults.outputUri}")
                }
            }
        }
    }

Java

Recording recording = videoCapture.getOutput()
    .prepareRecording(context, mediaStoreOutput)
    .withAudioEnabled()
    .start(ContextCompat.getMainExecutor(context), event -> {
        if (event instanceof VideoRecordEvent.Start) {
            Log.d(TAG, "Recording started");
        } else if (event instanceof VideoRecordEvent.Finalize) {
            VideoRecordEvent.Finalize finalize = (VideoRecordEvent.Finalize) event;
            if (!finalize.hasError()) {
                Log.d(TAG, "Video saved: " + finalize.getOutputResults().getOutputUri());
            }
        }
    });

Recursos adicionales

Para obtener más información sobre CameraX, consulta los siguientes recursos adicionales: