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

Примечание. На этой странице речь идет о пакете Camera2. Если вашему приложению не требуются определенные низкоуровневые функции Camera2, рекомендуем использовать CameraX. CameraX и Camera2 поддерживают 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: охватывает все существующие варианты поведения приложения. Это равносильно тому, что вы не задали вариант использования потока.

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

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

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

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

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

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

Создайте объект CameraCaptureSession

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

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

Kotlin

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

Java

// 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. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// 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
private void configureSession(CameraDevice device, List<Surface> targets){
    ArrayList<OutputConfiguration> configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        configs.add(config)
}

device.createCaptureSession(session)
}

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

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

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

Single CaptureRequests

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

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

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

Kotlin

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

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

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

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // 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)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // 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, активируются при обработке фрейма, в котором они содержатся. Именно в этот момент можно извлечь данные изображения из указанного буфера.

Repeat CaptureRequests

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

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // 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)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // 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.

Interleave CaptureRequests

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

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

Kotlin

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)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().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
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);

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

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

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

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

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