Архитектура видеозаписи 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 {
        it.cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) ==
            CameraMetadata.LENS_FACING_BACK
    }
    
    // Filter to SDR resolutions supported by both the camera sensor and Recorder
    // encoder so the UI picker only displays valid options.
    val supportedQualities =
        Recorder.getVideoCapabilities(cameraInfo[0]).getSupportedQualities(DynamicRange.SDR)
    val filteredQualities = arrayListOf (Quality.UHD, Quality.QHD, Quality.FHD, Quality.HD, Quality.SD)
                           .filter { supportedQualities.contains(it) }
    
    viewBinding.simpleQualityListView.apply {
        adapter = ArrayAdapter(context,
                               android.R.layout.simple_list_item_1,
                               filteredQualities.map { it.qualityToString() })
    
        setOnItemClickListener { _, _, position, _ ->
            // QualitySelector is configured at Recorder build time, so selecting
            // a new resolution requires building a new Recorder instance.
            val qualitySelector = QualitySelector.from(filteredQualities[position])
    
            val recorder = Recorder.Builder()
                .setQualitySelector(qualitySelector).build()
    
             // ...
        }
    }
    
    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()
        }
    }
    
    

    Каждый объект Quality, возвращаемый методом VideoCapabilities.getSupportedQualities (из Recorder.getVideoCapabilities для DynamicRange.SDR), работает для варианта использования 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. Это означает, что зеркальное отображение не включено для основной камеры, но включено для фронтальной камеры. Подробнее о режиме зеркалированияMirrorMode constants…

В этом фрагменте кода показано, как вызвать VideoCapture.Builder.setMirrorMode() с помощью MIRROR_MODE_ON_FRONT_ONLY. Подробнее setMirrorMode()…

val recorder = Recorder.Builder().build()

val videoCapture = VideoCapture.Builder(recorder)
    .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, ознакомьтесь со следующими дополнительными ресурсами: