Сеансы захвата камеры и запросы

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

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

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

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

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

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

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

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

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

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

Используйте варианты использования Stream для повышения производительности

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

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

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

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

  • DEFAULT : Охватывает все существующее поведение приложения. Это эквивалентно отсутствию установки какого-либо варианта использования потока.

  • 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 = findViewByIdS<urfaceView(>...)
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: ListS<urface)>{
    val configs = mutableListOfO<utputConfiguration(>)
    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)
}

Ява

// 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 = findViewByIdS<urfaceView(>...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
ListS<urface >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, ListS<urface >targets){
    ArrayListO<utputConfiguration >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() предоставляет дополнительную информацию.

Одиночные запросы захвата

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

Ява

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

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

Котлин

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)

Ява

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

Повторить запросы захвата

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

Котлин

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)

Ява

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 . API Camera2 также позволяет пользователям захватывать видео с камеры, отправляя повторяющиеся CaptureRequests , как показано в этом примере репозитория Camera2 на GitHub. Он также может визуализировать замедленное видео, захватывая высокоскоростное (замедленное) видео с помощью повторяющихся пакетных запросов CaptureRequests , как показано в примере приложения замедленного видео Camera2 на GitHub.

Чередование запросов захвата

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

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

Котлин

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)

Ява

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 снова используется, поэтому вы можете столкнуться с пропущенными кадрами. Есть несколько вещей, которые вы можете сделать, чтобы смягчить эту проблему:

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

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