Сеансы и запросы на съемку камерой

Примечание. На этой странице рассматривается пакет Camera2. Если вашему приложению не требуются определенные низкоуровневые функции Camera2, рекомендуем использовать CameraX. Обе библиотеки поддерживают Android 5.0 (API уровня 21) и более поздние версии.

На одном устройстве Android может быть несколько камер. Каждая камера – это CameraDevice, а CameraDevice может выводить несколько потоков одновременно.

Это нужно, например, чтобы оптимизировать один поток (последовательность кадров, поступающих с CameraDevice) для определенной задачи, такой как отображение видоискателя, а другие использовать для съемки фото или видео.Потоки действуют как параллельные конвейеры, которые обрабатывают необработанные кадры, поступающие с камеры, по одному кадру за раз:

Рисунок 1. Иллюстрация из презентации "Создание универсального приложения камеры" (Google I/O, 2018 г.)

Параллельная обработка предполагает, что могут существовать ограничения производительности в зависимости от доступной вычислительной мощности ЦП, графического процессора или другого процессора. Если конвейер не справляется с обработкой входящих кадров, он начинает их пропускать.

У каждого конвейера свой формат выходных данных. Поступающие первичные данные автоматически преобразуются в соответствующий выходной формат с помощью неявной логики, связанной с каждым конвейером. Значение CameraDevice, используемое в примерах кода на этой странице, не является конкретным, поэтому сначала необходимо перечислить все доступные камеры.

С помощью CameraDevice можно создать CameraCaptureSession, который будет относиться к этому CameraDevice. Для каждого необработанного кадра CameraDevice должен получить конфигурацию кадра с помощью CameraCaptureSession. В конфигурации указываются атрибуты камеры, такие как автофокус, диафрагма, эффекты и экспозиция. Из-за аппаратных ограничений в датчике камеры в любой момент времени активна только одна конфигурация, которая называется активной.

Однако варианты использования потока расширяют и улучшают предыдущие способы использования CameraDevice для потоковой передачи сеансов захвата, что позволяет оптимизировать видео с камеры для конкретного варианта использования. Например, при оптимизации видеовызовов можно продлить время работы от батареи.

CameraCaptureSession описывает все возможные конвейеры, связанные с CameraDevice. После создания сеанса вы не сможете добавлять или удалять конвейеры. CameraCaptureSession поддерживает очередь объектов CaptureRequest, которые становятся активной конфигурацией.

CaptureRequest добавляет конфигурацию в очередь и выбирает один, несколько или все доступные конвейеры для получения кадра из CameraDevice. В течение одного сеанса можно отправить несколько запросов на списание средств. Каждый запрос может изменить активную конфигурацию и набор выходных конвейеров, которые получают необработанное изображение.

Используйте варианты использования потоков, чтобы повысить эффективность

Варианты использования потока позволяют повысить производительность сеансов захвата Camera2. Они предоставляют устройству больше информации для настройки параметров, что позволяет улучшить работу камеры для конкретной задачи.

Это позволяет оптимизировать аппаратные и программные конвейеры камеры на основе пользовательских сценариев для каждого потока. Подробнее setStreamUseCase…

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

Примеры использования Stream:

  • DEFAULT: охватывает все существующие варианты поведения приложения. Это эквивалентно отсутствию настройки Stream Use Case.

  • PREVIEW: рекомендуется для видоискателя или анализа изображений в приложении.

  • STILL_CAPTURE – оптимизировано для съемки в высоком качестве и разрешении. Частота кадров может быть ниже, чем в режиме предварительного просмотра.

  • VIDEO_RECORD: оптимизировано для записи видео высокого качества, включая высококачественную стабилизацию изображения, если она поддерживается устройством и включена в приложении. При выборе этого варианта кадры могут выводиться со значительной задержкой, чтобы обеспечить наилучшее качество стабилизации или другой обработки.

  • VIDEO_CALL: рекомендуется для длительного использования камеры, когда важно экономить заряд батареи.

  • PREVIEW_VIDEO_STILL – рекомендуется для приложений социальных сетей или сценариев с одним потоком. Это многоцелевой поток.

  • VENDOR_START – используется для сценариев, определенных OEM.

Создание CameraCaptureSession

Чтобы создать сеанс камеры, предоставьте один или несколько выходных буферов, в которые приложение сможет записывать выходные кадры. Каждый буфер представляет собой конвейер. Это нужно сделать до начала использования камеры, чтобы фреймворк мог настроить внутренние конвейеры устройства и выделить буферы памяти для отправки кадров в нужные целевые объекты вывода.

В следующем фрагменте кода показано, как подготовить сеанс камеры с двумя выходными буферами, один из которых принадлежит SurfaceView, а другой – ImageReader. Добавление варианта использования потока PREVIEW в previewSurface и варианта использования потока STILL_CAPTURE в imReaderSurface позволяет аппаратным средствам устройства оптимизировать эти потоки ещё больше.

// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
// analysis
// 3. OpenGL Texture or TextureView, although discouraged for maintainability
// reasons
// 4. RenderScript.Allocation, if you want to do parallel processing
val surfaceView = findViewById<SurfaceView>(
    // ...
)
val imageReader = ImageReader.newInstance(
    // ...
)

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated().
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)

// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready.
// Setup Stream Use Case while setting up your Output Configuration.
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun configureSession(device: CameraDevice, targets: List<Surface>) {
    val configs = mutableListOf<OutputConfiguration>()
    val streamUseCase = CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    targets.forEach {
        val config = OutputConfiguration(it)
        config.streamUseCase = streamUseCase.toLong()
        configs.add(config)
    }
    // ...
    device.createCaptureSession(session)
}

На этом этапе вы ещё не определили активную конфигурацию камеры. После настройки сеанса вы можете создавать и отправлять запросы на захват.

Преобразование, применяемое к входным данным при их записи в буфер, определяется типом каждой цели, которая должна быть Surface. Фреймворк Android умеет преобразовывать необработанное изображение в активной конфигурации в формат, подходящий для каждой цели. Преобразование зависит от формата пикселей и размера конкретного объекта Surface.

Фреймворк пытается сделать все возможное, но некоторые Surface комбинации конфигураций могут не работать, вызывая такие проблемы, как несоздание сеанса, возникновение ошибки выполнения при отправке запроса или снижение производительности. Фреймворк гарантирует поддержку определенных комбинаций устройств, платформ и параметров запросов. Дополнительную информацию можно найти в документации по createCaptureSession().

Одиночные запросы CaptureRequest

Конфигурация, используемая для каждого кадра, кодируется в CaptureRequest, который отправляется на камеру. Чтобы создать запрос на съемку, можно использовать один из стандартных шаблонов или TEMPLATE_MANUAL, чтобы полностью контролировать процесс. При выборе шаблона необходимо указать один или несколько выходных буферов, которые будут использоваться с запросом. Вы можете использовать только те буферы, которые уже были определены в сеансе захвата.

Запросы на съемку используют шаблон проектирования "Строитель" и позволяют разработчикам задавать множество различных параметров, включая автоэкспозицию, автофокус и диафрагму объектива. Прежде чем задавать значение поля, убедитесь, что оно доступно для устройства, вызвав метод CameraCharacteristics.getAvailableCaptureRequestKeys(), и что нужное значение поддерживается, проверив соответствующую характеристику камеры, например доступные режимы автоэкспозиции.

Чтобы создать запрос на захват для SurfaceView с помощью шаблона, предназначенного для предварительного просмотра без каких-либо изменений, используйте CameraDevice.TEMPLATE_PREVIEW:

val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

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

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest.Builder = ...  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null.
session.capture(captureRequest.build(), null, null)

Когда выходной кадр помещается в определенный буфер, запускается обратный вызов захвата. Во многих случаях при обработке кадра, содержащегося в объекте, активируются дополнительные обратные вызовы, например ImageReader.OnImageAvailableListener. В этот момент вы можете получить данные изображения из указанного буфера.

Повторяющиеся запросы CaptureRequest

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

val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest.Builder = ... // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until
// the session is torn down or session.stopRepeating() is called.
session.setRepeatingRequest(captureRequest.build(), null, null)

Повторяющийся запрос на съемку заставляет камеру непрерывно делать снимки, используя настройки, заданные в CaptureRequest. Camera2 API также позволяет пользователям записывать видео с камеры, отправляя повторяющиеся запросы CaptureRequests, как показано в этом примере Camera2 в репозитории на GitHub. Кроме того, оно может создавать замедленные видео, снимая высокоскоростные (замедленные) видео с помощью повторяющейся серийной съемки CaptureRequests, как показано в примере приложения Camera2 для замедленной съемки на GitHub.

Чередование CaptureRequest

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

Любой используемый выходной буфер должен быть настроен как часть сеанса камеры при первом создании сеанса. Повторяющиеся запросы имеют более низкий приоритет, чем запросы на один кадр или серию кадров, что позволяет работать со следующим примером:

val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it.
val repeatingRequest = session.device.createCaptureRequest(
    CameraDevice.TEMPLATE_PREVIEW
)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)

// Some time later...

// Create the single request and dispatch it.
// NOTE: This can disrupt the ongoing repeating request momentarily.
val singleRequest = session.device.createCaptureRequest(
    CameraDevice.TEMPLATE_STILL_CAPTURE
)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)

Однако у этого подхода есть недостаток: вы не знаете, когда именно происходит запрос. На рисунке ниже показано, как сеанс обрабатывает очередь запросов, если A – повторяющийся запрос на съемку, а B – запрос на съемку одного кадра.

Рисунок 2. Иллюстрация очереди запросов для текущего сеанса камеры

Задержка между последним повторяющимся запросом из A до активации запроса B и следующим использованием A не гарантируется, поэтому некоторые кадры могут быть пропущены. Вот что можно сделать, чтобы решить эту проблему:

  • Добавьте целевые объекты из запроса A в запрос B. Таким образом, когда кадр B будет готов, он будет скопирован в целевые объекты вывода A. Например, это важно при создании снимков видео, чтобы поддерживать стабильную частоту кадров. В приведенном выше коде вы добавляете singleRequest.addTarget(previewSurface) перед созданием запроса.

  • Используйте комбинацию шаблонов, разработанных для конкретного сценария, например для нулевой задержки затвора.