Arquitetura de captura de vídeo do CameraX

Um sistema de captura geralmente grava streams de vídeo e áudio, faz a compactação e a multiplexação deles e grava o stream resultante no disco.

Diagrama conceitual para um sistema de captura de vídeo e áudio
Figura 1. Diagrama conceitual para um sistema de captura de vídeo e áudio.

No CameraX, a solução para captura de vídeo é o caso de uso de VideoCapture:

diagrama conceitual que mostra como o CameraX lida com o
         caso de uso de captura de vídeo
Figura 2. Diagrama conceitual que mostra como o CameraX lida com o caso de uso VideoCapture.

Como mostrado na figura 2, a captura de vídeo do CameraX inclui alguns componentes de arquitetura de alto nível:

  • SurfaceProvider para a origem do vídeo.
  • AudioSource para a fonte de áudio.
  • Dois codificadores para codificar e compactar vídeo/áudio.
  • Um multiplexador de mídia para combinar os dois streams.
  • Um protetor de arquivo para gravar o resultado.

A API VideoCapture abstrai o mecanismo de captura complexo e fornece aplicativos com uma API muito mais simples e direta.

Visão geral da API VideoCapture

A VideoCapture é um caso de uso do CameraX que funciona bem sozinho ou quando combinado com outros casos de uso. O suporte a combinações específicas depende dos recursos de hardware da câmera, mas Preview e VideoCapture são uma combinação de casos de uso válida em todos os dispositivos.

A API VideoCapture consiste nos objetos abaixo que se comunicam com aplicativos:

  • A VideoCapture é a classe do caso de uso de nível mais alto. Ela se vincula a uma interface LifecycleOwner com uma classe CameraSelector e outros UseCases da CameraX. Para ver mais informações sobre esses conceitos e usos, consulte a Arquitetura da CameraX.
  • Um objeto Recorder é uma implementação de VideoOutput que está rigidamente acoplada à VideoCapture. O Recorder é usado para executar a captura de vídeo e de áudio. Um aplicativo cria gravações usando um Recorder.
  • Uma PendingRecording configura uma gravação, fornecendo opções como ativação de áudio e definição de um listener de eventos. É necessário usar um Recorder para criar uma PendingRecording. A PendingRecording não grava nada.
  • Uma Recording realiza a gravação em si. É necessário usar uma PendingRecording para criar uma Recording.

A figura 3 mostra as relações entre esses objetos:

diagrama mostrando as interações que ocorrem em um caso de uso de
         captura de vídeo
Figura 3. Diagrama mostrando as interações que ocorrem em um caso de uso de VideoCapture.

Legenda:

  1. Crie um objeto Recorder com o QualitySelector.
  2. Configure o Recorder com uma das OutputOptions.
  3. Ative o áudio com o método withAudioEnabled(), se necessário.
  4. Chame start() com um listener VideoRecordEvent para começar a gravar.
  5. Use pause()/resume()/stop() na Recording para controlar a gravação.
  6. Responda a VideoRecordEvents no listener de eventos.

A lista detalhada da API está no current.txt dentro do código-fonte (link em inglês).

Como usar a API VideoCapture

Para integrar o caso de uso da VideoCapture do CameraX ao seu app, siga estas etapas:

  1. Vincule a VideoCapture.
  2. Prepare e configure a gravação.
  3. Inicie e controle a gravação em execução.

Veja nas seções abaixo o que você pode fazer em cada etapa para ter uma sessão de gravação completa.

Vincular a VideoCapture

Para vincular o caso de uso da VideoCapture, siga estas etapas:

  1. Crie um objeto Recorder.
  2. Crie um objeto VideoCapture.
  3. Vincule um Lifecycle.

A API VideoCapture do CameraX segue o padrão de design do builder. Os aplicativos usam o Recorder.Builder para criar um Recorder. Você também pode configurar a resolução de vídeo para o Recorder usando um objeto QualitySelector.

O Recorder da CameraX oferece suporte a estas Qualities predefinidas para resoluções de vídeo:

  • Quality.UHD para vídeo em 4K Ultra HD (2160p)
  • Quality.QHD para vídeo em Quad HD (1440p), introduzido no CameraX 1.7.0-alpha02
  • Quality.FHD para vídeo em Full HD (1080p)
  • Quality.HD para vídeo em HD (720p)
  • Quality.SD para vídeo em SD (480p)

O CameraX também pode escolher outras resoluções quando permitido pelo app.

O tamanho exato do vídeo de cada seleção depende dos recursos da câmera e do codificador. Para mais informações, consulte a documentação do CamcorderProfile.

Os aplicativos podem criar um QualitySelector para configurar a resolução. Você pode criar um QualitySelector usando um destes métodos:

  • Use fromOrderedList() para fornecer algumas resoluções preferenciais e inclua uma estratégia substituta para ser usada caso não haja suporte a nenhuma delas.

    A CameraX pode decidir a melhor correspondência substituta com base na capacidade da câmera selecionada. Consulte a FallbackStrategy specification do QualitySelector para ver mais detalhes. Por exemplo, o código abaixo solicita a resolução mais alta com suporte à gravação e, se nenhuma das resoluções da solicitação tiver suporte, autoriza a CameraX a escolher uma que seja a mais próxima da qualidade Quality.SD:

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, Quality.QHD, Quality.FHD, Quality.HD, Quality.SD),
             FallbackStrategy.lowerQualityOrHigherThan(Quality.SD))
    
  • Primeiro, consulte os recursos da câmera e escolha uma das resoluções com suporte usando o método 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()
        }
    }
    
    

    O recurso retornado de QualitySelector.getSupportedQualities() tem garantia de funcionar para o caso de uso VideoCapture ou para a combinação de casos de uso VideoCapture e Preview. A vinculação na CameraX com os casos de uso ImageCapture ou ImageAnalysis ainda pode falhar quando a combinação necessária não tiver suporte à câmera solicitada.

Quando você tiver um QualitySelector, o aplicativo poderá criar um objeto VideoCapture e realizar a vinculação. Observe que essa vinculação é igual a outros 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)
}

Observe que bindToLifecycle() retorna um objeto Camera. Consulte este guia para ver mais informações sobre como controlar a saída da câmera, por exemplo, zoom e exposição.

O Recorder seleciona o formato mais adequado para o sistema. O codec de vídeo mais comum é o H.264 AVC, com formato de contêiner MPEG-4.

Configurar e criar uma gravação

Usando um Recorder, o aplicativo pode criar objetos de gravação para realizar a captura de vídeo e áudio. Para criar gravações em aplicativos siga estas etapas:

  1. Configure a classe OutputOptions usando o método prepareRecording().
  2. (Opcional) Ative a gravação de áudio.
  3. Use a função start() para registrar um listener VideoRecordEvent e inicie a captura de vídeo.

O Recorder retorna um objeto Recording quando você chama a função start(). O aplicativo pode usar esse objeto Recording para concluir a captura ou realizar outras ações, como pausar ou retomar.

Um Recorder oferece suporte a um objeto Recording por vez. Você pode iniciar uma nova gravação depois de chamar o método Recording.stop() ou Recording.close() no objeto Recording anterior.

Vamos analisar as etapas com mais detalhes. Primeiro, o aplicativo configura as OutputOptions para um Gravador usando o método Recorder.prepareRecording(). Um Recorder oferece suporte aos tipos de OutputOptions abaixo:

  • FileDescriptorOutputOptions para capturar um FileDescriptor.
  • FileOutputOptions para capturar um File.
  • MediaStoreOutputOptions para capturar um MediaStore.

Todos os tipos de OutputOptions permitem definir um tamanho máximo de arquivo usando o método setFileSizeLimit(). Outras opções são específicas do tipo de saída individual, como o ParcelFileDescriptor para FileDescriptorOutputOptions.

O método prepareRecording() retorna um objeto PendingRecording, que é um objeto intermediário usado para criar o objeto Recording correspondente. A PendingRecording é uma classe temporária que precisa ficar invisível na maioria dos casos e raramente é armazenada em cache pelo app.

Os aplicativos podem fazer mais configurações para a gravação, como:

  • ativar o áudio usando o método withAudioEnabled();
  • registrar um listener para receber eventos de gravação de vídeo usando start(Executor, Consumer<VideoRecordEvent>).
  • Permitir que uma gravação continue sendo feita enquanto o VideoCapture ao qual ela está anexada é vinculado a outra câmera, com PendingRecording.asPersistentRecording().

Para iniciar a gravação, chame PendingRecording.start(). A CameraX transforma a PendingRecording em uma Recording, coloca a solicitação de gravação em fila e retorna o objeto Recording recém-criado ao aplicativo. Quando a gravação é iniciada no dispositivo de câmera correspondente, a CameraX envia um evento VideoRecordEvent.EVENT_TYPE_START.

O exemplo abaixo mostra como gravar vídeos e áudio em um arquivo 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)

Enquanto a visualização da câmera é espelhada na câmera frontal por padrão, os vídeos gravados pela VideoCapture não são espelhados por padrão. Com a CameraX 1.3, agora é possível espelhar gravações de vídeo para que a visualização da câmera frontal e o vídeo gravado correspondam.

Há três opções de MirrorMode: MIRROR_MODE_OFF, MIRROR_MODE_ON e MIRROR_MODE_ON_FRONT_ONLY. Para alinhar com a visualização da câmera, o Google recomenda usar MIROR_MODE_ON_FRONT_ONLY, o que significa que o espelhamento não está ativado para a câmera traseira, mas está ativado para a câmera frontal. Para mais informações sobre o MirrorMode, consulte MirrorMode constants.

Este snippet de código mostra como chamar VideoCapture.Builder.setMirrorMode() usando MIRROR_MODE_ON_FRONT_ONLY. Para mais informações, consulte 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);

Intervalo dinâmico (vídeo HDR)

O CameraX oferece suporte à gravação de vídeo em High Dynamic Range (HDR) de 10 bits, como HLG10, HDR10, HDR10+ ou Dolby Vision, usando DynamicRange.

Para usar a gravação de vídeo em HDR, siga estas etapas:

  1. Consulte os intervalos dinâmicos compatíveis com a câmera usando VideoCapabilities.getSupportedDynamicRanges().
  2. Opcional: se você configurar um QualitySelector personalizado, verifique quais qualidades são compatíveis com esse intervalo dinâmico usando getSupportedQualities(DynamicRange).
  3. Configure VideoCapture.Builder com setDynamicRange().

O exemplo a seguir ativa a gravação HDR de 10 bits HLG quando a câmera oferece suporte a ela:

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

Estabilização de vídeo

A CameraX oferece suporte à estabilização de vídeo para reduzir a trepidação e o jitter durante a gravação de vídeos.

Para ativar a estabilização de vídeo, siga estas etapas:

  1. Verifique se o dispositivo de câmera oferece suporte à estabilização usando VideoCapabilities.isStabilizationSupported().
  2. Ative-o em VideoCapture.Builder usando setVideoStabilizationEnabled(true).

O exemplo a seguir ativa a estabilização quando a câmera é compatível com ela:

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

Controlar uma gravação ativa

Você pode pausar, retomar e parar uma Recording em andamento usando estes métodos:

  • pause para pausar a gravação ativa atual.
  • resume() para retomar uma gravação ativa pausada.
  • stop() para concluir a gravação e transferir todos os objetos de gravação associados.
  • mute() para ativar ou desativar o som da gravação atual.

É possível chamar o método stop() para encerrar uma Recording, independente de a gravação estar ou não pausada.

Se você registrou um EventListener com PendingRecording.start(), a Recording vai se comunicar usando um listener VideoRecordEvent.

  • O VideoRecordEvent.EVENT_TYPE_STATUS é usado para gravar estatísticas como o tamanho de arquivo atual e o período gravado.
  • O VideoRecordEvent.EVENT_TYPE_FINALIZE é usado para o resultado da gravação e inclui informações como o URI do arquivo final, além de qualquer erro relacionado.

Depois que o app receber um EVENT_TYPE_FINALIZE, que indica uma sessão de gravação bem-sucedida, você poderá acessar o vídeo capturado no local especificado nas OutputOptions.

Amplitude e estado do áudio em tempo real

Durante a gravação, os eventos VideoRecordEvent.Status fornecem estatísticas de áudio em tempo real usando AudioStats:

  • AudioStats.getAudioAmplitude() retorna a amplitude de pico de áudio atual, que varia de 0.0 a 1.0. É possível usar esse valor para controlar medidores de IU ou visualizadores de forma de onda de áudio.
  • AudioStats.getAudioState() indica o status atual do stream de áudio, como AUDIO_STATE_ACTIVE, AUDIO_STATE_MUTED ou AUDIO_STATE_SOURCE_SILENCED.

O exemplo a seguir lê a amplitude de áudio de um evento de status:

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

Vídeo em alta velocidade e câmera lenta

A CameraX oferece suporte à gravação de vídeo em alta velocidade, como 120, 240 ou 480 qps, o que permite dois casos de uso distintos:

  • Vídeo com frame rate alto (câmera lenta processada pelo player): a CameraX captura e salva frames com frame rate alto (por exemplo, 120 QPS). O player de vídeo controla a velocidade do vídeo, permitindo que os usuários alternem entre a velocidade normal e a câmera lenta ou realizem análises quadro a quadro.
  • Vídeo em câmera lenta pronto para reprodução: o CameraX processa o stream de alta velocidade e gera um arquivo de vídeo padrão de 30 QPS com marcações de tempo de frame ajustadas. O efeito de câmera lenta é "incorporado", então o vídeo é reproduzido em câmera lenta automaticamente em qualquer player de vídeo padrão sem suporte especial. Por exemplo, uma captura de 120 qps reproduzida a 30 qps produz um vídeo em câmera lenta de 1/4x.

Verificar os recursos de alta velocidade

A captura de vídeo em alta velocidade requer suporte de hardware e as definições CamcorderProfile correspondentes dos fabricantes de dispositivos, como CamcorderProfile.QUALITY_HIGH_SPEED_1080P ou QUALITY_HIGH_SPEED_720P.

Antes de configurar a gravação em alta velocidade, consulte os recursos de alta velocidade usando 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);

Configurar HighSpeedVideoSessionConfig

Para usar a captura de alta velocidade, combine os casos de uso VideoCapture e Preview em um HighSpeedVideoSessionConfig. Para configurar a sessão, siga estas etapas:

  1. Crie um caso de uso de VideoCapture e um caso de uso de Preview.
  2. Inicialize HighSpeedVideoSessionConfig.Builder com o caso de uso VideoCapture e anexe o Preview.
  3. Consulte os intervalos de frame rate compatíveis usando CameraInfo.getSupportedFrameRateRanges(SessionConfig) e defina o intervalo selecionado.
  4. Escolha entre vídeo com alta taxa de quadros ou vídeo em câmera lenta pronto para reprodução:
    • Para criar um vídeo com frame rate alto, deixe setSlowMotionEnabled(false) (padrão).
    • Para criar um vídeo em câmera lenta pronto para reprodução, chame setSlowMotionEnabled(true).
  5. Vincule a configuração da sessão usando cameraProvider.bindToLifecycle().

O exemplo a seguir configura e vincula uma sessão de câmera lenta pronta para reprodução:

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

Também é possível usar a DSL do Kotlin, introduzida no CameraX 1.7.0-alpha02, para configurar sessões de alta velocidade de forma concisa:

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

Gravar vídeos em alta velocidade ou câmera lenta

Depois de vincular o HighSpeedVideoSessionConfig, comece a gravar usando VideoCapture da mesma forma que as gravações de vídeo padrão:

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

Outros recursos

Para saber mais sobre o CameraX, consulte os recursos abaixo: