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

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

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

В CameraX для захвата видео используется сценарий VideoCapture :

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

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

  • В качестве источника видео используется SurfaceProvider .
  • AudioSource — источник звука.
  • Два кодировщика для кодирования и сжатия видео/аудио.
  • Мультиплексор для объединения двух потоков.
  • Программа для сохранения файла и записи результата.

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

Обзор API захвата видео

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

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

  • VideoCapture — это класс верхнего уровня для реализации вариантов использования. VideoCapture привязывается к LifecycleOwner с CameraSelector и другими вариантами использования 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 внутри исходного кода .

Использование API захвата видео

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

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

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

Привязать VideoCapture

Для привязки сценария использования VideoCapture выполните следующие действия:

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

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

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

  • Quality.UHD для видео в формате 4K Ultra HD (2160p)
  • Quality.FHD — видео в формате Full HD (1080p)
  • Quality.HD для видео в формате HD (720p)
  • Quality.SD для видео в формате SD (480p)

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

Точный размер видеоряда каждого фрагмента зависит от возможностей камеры и кодировщика. Для получения дополнительной информации см. документацию по CamcorderProfile .

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

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

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

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, 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.FHD, Quality.HD, Quality.SD)
                           .filter { supportedQualities.contains(it) }
    
    // Use a simple ListView with the id of simple_quality_list_view
    viewBinding.simpleQualityListView.apply {
        adapter = ArrayAdapter(context,
                               android.R.layout.simple_list_item_1,
                               filteredQualities.map { it.qualityToString() })
    
        // Set up the user interaction to manually show or hide the system UI.
        setOnItemClickListener { _, _, position, _ ->
            // Inside View.OnClickListener,
            // convert Quality.* constant to QualitySelector
            val qualitySelector = QualitySelector.from(filteredQualities[position])
    
            // Create a new Recorder/VideoCapture for the new quality
            // and bind to lifecycle
            val recorder = Recorder.Builder()
                .setQualitySelector(qualitySelector).build()
    
             // ...
        }
    }
    
    // A helper function to translate Quality to a string
    fun Quality.qualityToString() : String {
        return when (this) {
            Quality.UHD -> "UHD"
            Quality.FHD -> "FHD"
            Quality.HD -> "HD"
            Quality.SD -> "SD"
            else -> throw IllegalArgumentException()
        }
    }
    
    

    Обратите внимание, что возвращаемая функция 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 . Как только запись на соответствующем устройстве Camera начнётся, 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 появилась возможность зеркального отображения видеозаписей таким образом, чтобы предварительный просмотр с фронтальной камеры и записанное видео совпадали.

Доступны три параметра MirrorMode: MIRROR_MODE_OFF, MIRROR_MODE_ON и MIRROR_MODE_ON_FRONT_ONLY. Для синхронизации с предварительным просмотром камеры Google рекомендует использовать MIROR_MODE_ON_FRONT_ONLY, что означает, что зеркальное отображение не включено для задней камеры, но включено для передней. Для получения дополнительной информации о MirrorMode см. 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);

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

Управление активной записью

Вы можете приостановить, возобновить и остановить текущую 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 .

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

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