Архитектура видеозаписи CameraX

Система захвата обычно записывает видео- и аудиопотоки, сжимает их, мультиплексирует и записывает полученный поток на диск.

концептуальная схема системы захвата видео и аудио
Рисунок 1. Концептуальная диаграмма для системы записи видео и аудио.

В CameraX для записи видео используется класс VideoCapture.

Концептуальная диаграмма, показывающая, как CameraX обрабатывает сценарий использования видеозаписи.
Рисунок 2. Концептуальная диаграмма, показывающая, как CameraX обрабатывает вариант использования VideoCapture.

Как показано на рисунке 2, видеозапись CameraX включает несколько высокоуровневых архитектурных компонентов:

  • SurfaceProvider для источника видео.
  • AudioSource для источника аудио.
  • Два видеокодера для кодирования и сжатия видео и аудио.
  • Мультиплексор для объединения двух потоков.
  • Средство сохранения файлов для записи результата.

VideoCapture API абстрагирует сложный механизм захвата и предоставляет приложениям гораздо более простой и понятный API.

Обзор VideoCapture API

VideoCapture – это вариант использования CameraX, который хорошо работает сам по себе или в сочетании с другими вариантами использования. Поддерживаемые комбинации зависят от аппаратных возможностей камеры, но Preview и VideoCapture – это допустимая комбинация для всех устройств.

VideoCapture API состоит из следующих объектов, которые взаимодействуют с приложениями:

  • VideoCapture – это класс вариантов использования верхнего уровня. VideoCapture привязывается к LifecycleOwner с помощью CameraSelector и других UseCase CameraX. Подробнее об этих концепциях и их использовании рассказывается в статье Архитектура CameraX.
  • Recorder – это реализация VideoOutput, которая тесно связана с VideoCapture. Recorder используется для записи видео и аудио. Приложение создает записи из Recorder.
  • Объект PendingRecording настраивает запись, предоставляя такие параметры, как включение звука и настройка прослушивателя событий. Чтобы создать PendingRecording, необходимо использовать Recorder. PendingRecording ничего не записывает.
  • Recording выполняет запись. Чтобы создать Recording, необходимо использовать PendingRecording.

На рисунке 3 показаны связи между этими объектами.

Диаграмма, показывающая взаимодействия, которые происходят в случае использования видеозахвата
Рисунок 3. Диаграмма, показывающая взаимодействия, которые происходят в сценарии использования VideoCapture.

Условные обозначения:

  1. Создайте Recorder с помощью QualitySelector.
  2. Настройте Recorder с помощью одного из OutputOptions.
  3. При необходимости включите аудио с помощью withAudioEnabled().
  4. Чтобы начать запись, позвоните start() с помощью слушателя VideoRecordEvent.
  5. Для управления записью используйте кнопки pause(), resume() и stop() на Recording.
  6. Реагируйте на VideoRecordEvents в прослушивателе событий.

Подробный список API приведен в файле current.txt в исходном коде.

Использование VideoCapture API

Чтобы интегрировать вариант использования CameraX VideoCapture в приложение, выполните следующие действия:

  1. Привязать VideoCapture.
  2. Подготовьте и настройте запись.
  3. Запустите запись и управляйте ею.

В следующих разделах описано, что можно сделать на каждом шаге, чтобы получить полную запись сеанса.

Привязать VideoCapture

Чтобы связать вариант использования VideoCapture, выполните следующие действия:

  1. Создайте объект Recorder.
  2. Создайте объект VideoCapture.
  3. Привязка к Lifecycle.

CameraX VideoCapture API использует шаблон проектирования Builder. Приложения используют Recorder.Builder для создания Recorder. Вы также можете настроить разрешение видео для Recorder с помощью объекта QualitySelector.

CameraX Recorder поддерживает следующие предопределенные Qualities для разрешений видео:

  • Quality.UHD для видео в формате 4K Ultra HD (2160p)
  • Quality.QHD для видео в формате Quad HD (1440p), представленного в CameraX. 1.7.0-alpha02
  • Quality.FHD для видео в формате Full HD (1080p)
  • Quality.HD для HD-видео (720p)
  • Quality.SD для видео в стандартном разрешении (480p)

Обратите внимание, что CameraX также может выбирать другие разрешения, если это разрешено приложением.

Точный размер видео зависит от возможностей камеры и видеокодера. Подробную информацию вы найдете в документации по CamcorderProfile.

Приложения могут настраивать разрешение, создавая QualitySelector. Создать объект QualitySelector можно одним из следующих способов:

  • Укажите несколько предпочтительных разрешений с помощью fromOrderedList() и добавьте резервную стратегию на случай, если ни одно из них не поддерживается.

    CameraX может выбрать наилучшее резервное соответствие на основе возможностей выбранной камеры. Подробную информацию можно найти в FallbackStrategy specification класса QualitySelector. Например, следующий код запрашивает максимально возможное разрешение для записи. Если ни одно из запрошенных разрешений не поддерживается, CameraX выбирает разрешение, наиболее близкое к Quality.SD:

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, Quality.QHD, Quality.FHD, Quality.HD, Quality.SD),
             FallbackStrategy.lowerQualityOrHigherThan(Quality.SD))
    
  • Сначала запросите возможности камеры и выберите одно из поддерживаемых разрешений с помощью 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()
        }
    }
    
    

    Обратите внимание, что функция, возвращаемая из QualitySelector.getSupportedQualities(), гарантированно работает либо для варианта использования VideoCapture, либо для комбинации вариантов использования VideoCapture и Preview. При связывании с вариантом использования ImageCapture или ImageAnalysis CameraX может не выполнить связывание, если нужная комбинация не поддерживается на запрашиваемой камере.

Получив QualitySelector, приложение может создать объект VideoCapture и выполнить привязку. Обратите внимание, что эта привязка такая же, как и в других случаях использования:

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

Обратите внимание, что bindToLifecycle() возвращает объект Camera. Подробнее о том, как управлять выводом камеры, например масштабированием и экспозицией…

Recorder выбирает наиболее подходящий формат для системы. Самый распространенный видеокодек – H.264 AVC с форматом контейнера MPEG-4.

Настройте и создайте запись

Из Recorder приложение может создавать объекты записи для захвата видео и аудио. Приложения создают записи следующим образом:

  1. Настройте OutputOptions с помощью prepareRecording().
  2. Включите запись аудио (необязательно).
  3. Используйте start(), чтобы зарегистрировать прослушиватель VideoRecordEvent и начать запись видео.

При вызове функции start() Recorder возвращает объект Recording. Приложение может использовать этот объект Recording, чтобы завершить запись или выполнить другие действия, например приостановить или возобновить ее.

Recorder поддерживает только один объект Recording за раз. Вы можете начать новую запись, вызвав метод Recording.stop() или Recording.close() для предыдущего объекта Recording.

Рассмотрим эти шаги подробнее. Сначала приложение настраивает OutputOptions для Recorder с помощью Recorder.prepareRecording(). Recorder поддерживает следующие типы OutputOptions:

  • FileDescriptorOutputOptions для записи в FileDescriptor.
  • FileOutputOptions для записи в File.
  • MediaStoreOutputOptions для записи в MediaStore.

Для всех типов OutputOptions можно задать максимальный размер файла с помощью setFileSizeLimit(). Другие параметры зависят от типа выходных данных, например ParcelFileDescriptor для FileDescriptorOutputOptions.

prepareRecording() возвращает объект PendingRecording, который является промежуточным объектом, используемым для создания соответствующего объекта Recording. PendingRecording – это временный класс, который в большинстве случаев должен быть невидимым и редко кешируется приложением.

Приложения могут дополнительно настраивать запись, например:

  • Включите аудио с помощью withAudioEnabled().
  • Зарегистрируйте прослушиватель, чтобы получать события записи видео с помощью start(Executor, Consumer<VideoRecordEvent>).
  • Разрешить непрерывную запись, пока VideoCapture, к которому она прикреплена, привязывается к другой камере, с помощью PendingRecording.asPersistentRecording().

Чтобы начать запись, позвоните по номеру PendingRecording.start(). CameraX преобразует PendingRecording в Recording, ставит запрос на запись в очередь и возвращает созданный объект Recording в приложение. Как только начнется запись на соответствующем устройстве камеры, CameraX отправит событие VideoRecordEvent.EVENT_TYPE_START.

В примере ниже показано, как записать видео и аудио в файл 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)

Хотя по умолчанию изображение с фронтальной камеры зеркально отражается, видео, записанные с помощью VideoCapture, не отражаются. В CameraX 1.3 теперь можно зеркально отражать видеозаписи, чтобы изображение с фронтальной камеры и записанное видео совпадали.

Доступны три варианта: MIRROR_MODE_OFF, MIRROR_MODE_ON и MIRROR_MODE_ON_FRONT_ONLY. Чтобы выровнять изображение по предварительному просмотру камеры, Google рекомендует использовать MIROR_MODE_ON_FRONT_ONLY. Это означает, что зеркальное отображение не включено для основной камеры, но включено для фронтальной. Подробнее о MirrorModeMirrorMode constants…

В этом фрагменте кода показано, как вызвать VideoCapture.Builder.setMirrorMode() с помощью MIRROR_MODE_ON_FRONT_ONLY. Подробнее 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);

Динамический диапазон (видео HDR)

CameraX поддерживает запись 10-битного HDR-видео, например HLG10, HDR10, HDR10+ или Dolby Vision, с помощью DynamicRange.

Чтобы использовать HDR-видеозапись, выполните следующие действия:

  1. Запросите поддерживаемые динамические диапазоны камеры с помощью VideoCapabilities.getSupportedDynamicRanges().
  2. Если вы настраиваете собственный динамический диапазон (QualitySelector), проверьте, какие качества поддерживаются для него, с помощью getSupportedQualities(DynamicRange).
  3. Настройте VideoCapture.Builder с помощью setDynamicRange().

В следующем примере показано, как включить запись 10-битного HDR-видео в формате HLG, если камера поддерживает его:

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

Стабилизация видео

CameraX поддерживает стабилизацию видео, чтобы уменьшить дрожание камеры при съемке с рук.

Чтобы включить стабилизацию видео, выполните следующие действия:

  1. Проверьте, поддерживает ли камера стабилизацию, используя VideoCapabilities.isStabilizationSupported().
  2. Включите ее на VideoCapture.Builder с помощью setVideoStabilizationEnabled(true).

В следующем примере показано, как включить стабилизацию, если она поддерживается камерой:

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

Как управлять активной записью

Вы можете приостановить, возобновить или остановить текущий Recording следующими способами:

  • pause, чтобы приостановить текущую запись.
  • resume() чтобы возобновить приостановленную запись.
  • stop() чтобы завершить запись и удалить все связанные с ней объекты.
  • mute() – включить или отключить звук в текущей записи.

Обратите внимание, что вы можете вызвать stop(), чтобы завершить Recording, независимо от того, приостановлена запись или активна.

Если вы зарегистрировали EventListener в PendingRecording.start(), Recording будет обмениваться данными с помощью VideoRecordEvent.

  • VideoRecordEvent.EVENT_TYPE_STATUS используется для записи статистики, например текущего размера файла и записанного промежутка времени.
  • VideoRecordEvent.EVENT_TYPE_FINALIZE используется для записи результата и включает такую информацию, как URI конечного файла, а также любые связанные с ним ошибки.

Когда приложение получит EVENT_TYPE_FINALIZE, указывающий на успешную запись, вы сможете получить доступ к видео по пути, указанному в OutputOptions.

Амплитуда и состояние звука в реальном времени

Во время записи события VideoRecordEvent.Status предоставляют статистику аудио в реальном времени через AudioStats:

  • AudioStats.getAudioAmplitude() возвращает текущую пиковую амплитуду звука в диапазоне от 0.0 до 1.0. Вы можете использовать это значение для управления индикаторами пользовательского интерфейса или визуализаторами звуковых сигналов.
  • AudioStats.getAudioState() указывает на текущий статус аудиопотока, например AUDIO_STATE_ACTIVE, AUDIO_STATE_MUTED или AUDIO_STATE_SOURCE_SILENCED.

В следующем примере показано, как считать амплитуду звука из события статуса:

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

Ускоренное и замедленное видео

CameraX поддерживает высокоскоростную видеозапись, например 120, 240 или 480 кадров в секунду, что позволяет использовать два разных варианта использования:

  • Видео с высокой частотой кадров (замедленная съемка, обрабатываемая проигрывателем). CameraX записывает и сохраняет кадры с высокой частотой (например, 120 кадров/с). Видеопроигрыватель позволяет управлять скоростью воспроизведения, переключаясь между обычной скоростью и замедленным воспроизведением, или выполнять покадровый анализ.
  • Готовое к воспроизведению замедленное видео. CameraX обрабатывает высокоскоростной поток и выводит стандартный видеофайл с частотой 30 кадров/с и скорректированными временными метками кадров. Эффект замедленной съемки "встроен" в видео, поэтому оно автоматически воспроизводится в замедленном режиме в любом стандартном видеопроигрывателе без специальной поддержки. Например, если видео, снятое с частотой 120 кадров/с, воспроизводится с частотой 30 кадров/с, то скорость воспроизведения будет в четыре раза ниже обычной.

Проверьте возможности высокоскоростного подключения

Для высокоскоростной видеозаписи требуется аппаратная поддержка и соответствующие определения CamcorderProfile от производителей устройств, например CamcorderProfile.QUALITY_HIGH_SPEED_1080P или QUALITY_HIGH_SPEED_720P.

Прежде чем настраивать высокоскоростную запись, запросите информацию о возможностях высокоскоростной съемки с помощью 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);

Настройка HighSpeedVideoSessionConfig

Чтобы использовать высокоскоростную съемку, объедините варианты использования VideoCapture и Preview в HighSpeedVideoSessionConfig. Чтобы настроить сеанс, выполните следующие действия:

  1. Создайте вариант использования VideoCapture и вариант использования Preview.
  2. Инициализируйте HighSpeedVideoSessionConfig.Builder с помощью варианта использования VideoCapture и прикрепите Preview.
  3. Запросите поддерживаемые диапазоны частоты кадров с помощью CameraInfo.getSupportedFrameRateRanges(SessionConfig) и задайте выбранный диапазон.
  4. Выберите видео с высокой частотой кадров или готовое замедленное видео:
    • Чтобы создать видео с высокой частотой кадров, оставьте значение setSlowMotionEnabled(false) (по умолчанию).
    • Чтобы создать готовое к воспроизведению замедленное видео, вызовите setSlowMotionEnabled(true).
  5. Привяжите конфигурацию сеанса с помощью cameraProvider.bindToLifecycle().

В примере ниже настраивается и связывается готовый к воспроизведению сеанс замедленной съемки.

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

Вы также можете использовать Kotlin DSL, представленный в CameraX 1.7.0-alpha02, чтобы быстро настраивать высокоскоростные сеансы:

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

Как записать видео с ускорением или замедлением

После привязки HighSpeedVideoSessionConfig начните запись с помощью VideoCapture так же, как и при стандартной видеозаписи:

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

Дополнительные ресурсы

Чтобы узнать больше о CameraX, ознакомьтесь со следующими дополнительными ресурсами: