Solicitudes y sesiones de capturas de la cámara

Nota: Esta página hace referencia al paquete Camera2. A menos que la app requiera funciones específicas y de bajo nivel de Camera2, te recomendamos que uses CameraX. CameraX y Camera2 admiten Android 5.0 (nivel de API 21) y versiones posteriores.

Un solo dispositivo Android puede tener varias cámaras. Cada cámara es un CameraDevice: y una CameraDevice puede generar más de una transmisión de forma simultánea.

Un motivo es que una transmisión, los fotogramas de cámara secuenciales de un objeto CameraDevice, se optimiza para una tarea específica, como mostrar un visor, mientras que otros se pueden usar para tomar una foto o hacer un video registro. Las transmisiones actúan como canalizaciones paralelas que procesan fotogramas sin procesar. saliendo de la cámara,de a un fotograma a la vez:

Figura 1: Ilustración de la creación de una app de cámara universal (Google I/O 2018)

El procesamiento paralelo sugiere que puede haber límites de rendimiento la potencia de procesamiento disponible de la CPU, la GPU o algún otro procesador. Si un no puede seguir el ritmo de los fotogramas entrantes, comienza a descartarlos.

Cada canalización tiene su propio formato de salida. Los datos sin procesar que ingresan transformarse automáticamente en la solución formato de salida por lógica implícita asociados con cada canalización. El CameraDevice que se usa en todas las las muestras de código no son específicas, por lo que primero debes enumerar todas las cámaras disponibles antes de continuar.

Puedes usar el CameraDevice para crear un CameraCaptureSession, que es específico de ese CameraDevice. Un CameraDevice debe recibir un de fotogramas para cada fotograma sin procesar usando CameraCaptureSession. El especifica los atributos de la cámara, como enfoque automático, apertura, efectos, y la exposición. Debido a restricciones de hardware, solo se puede configurar activo en el sensor de la cámara en cualquier momento, lo que se denomina active.

Sin embargo, los casos de uso de transmisión mejoran y extienden las formas anteriores de usar CameraDevice. para transmitir sesiones de captura, lo que te permite optimizar la transmisión de la cámara para tu caso de uso particular. Por ejemplo, puede mejorar la duración de batería al optimizar videollamadas.

Un CameraCaptureSession describe todas las posibles canalizaciones vinculadas al CameraDevice Cuando se crea una sesión, no puedes agregar ni quitar canalizaciones. CameraCaptureSession mantiene una cola de CaptureRequest, que se convierten en la configuración activa.

Un CaptureRequest agrega una configuración a la cola y selecciona una, más de una o todas las canalizaciones disponibles para recibir una trama del CameraDevice Puedes enviar muchas solicitudes de captura durante la vida útil de una captura sesión. Cada solicitud puede cambiar la configuración activa y el conjunto de resultados de datos que reciben la imagen sin procesar.

Usa casos de uso de transmisión para obtener un mejor rendimiento

Los casos de uso de transmisión permiten mejorar el rendimiento de las capturas de Camera2 sesiones. Le dan al dispositivo de hardware más información para ajustar los parámetros lo que brinda una mejor experiencia con la cámara para realizar una tarea específica.

Esta Permite que el dispositivo de cámara optimice el hardware de la cámara y las canalizaciones de software. según las situaciones de los usuarios para cada transmisión. Para obtener más información sobre el uso de las Novedades Casos, consulta setStreamUseCase.

Los casos de uso de transmisión te permiten especificar cómo se usa una transmisión de cámara en particular más detalles, además de configurar una plantilla en CameraDevice.createCaptureRequest() Esto permite que el hardware de la cámara optimice parámetros, como el ajuste, el modo del sensor o la configuración del sensor de la cámara, según entre calidad y latencia, adecuados para casos de uso específicos.

Los casos de uso de transmisión incluyen los siguientes:

  • DEFAULT: Abarca todo el comportamiento existente de la aplicación. Es lo mismo que no establecer cualquier caso de uso de Stream.

  • PREVIEW: Se recomienda para el visor o el análisis de imágenes en la app.

  • STILL_CAPTURE: Optimizada para la captura de alta calidad y alta resolución, pero no que mantenga velocidades de fotogramas similares a las de la vista previa.

  • VIDEO_RECORD: Optimizada para la captura de video de alta calidad, incluidas las de alta calidad la estabilización de imagen, si es compatible con el dispositivo y la aplicación la habilita. Esta opción puede producir fotogramas de salida con un retraso considerable a partir del tiempo real para permitir una estabilización de más alta calidad u otro procesamiento.

  • VIDEO_CALL: Se recomienda para usos de cámara de larga duración en los que el consumo de energía sea un problema.

  • PREVIEW_VIDEO_STILL: Se recomienda para las apps de redes sociales o el uso de transmisión única. diferentes. Es una transmisión multipropósito.

  • VENDOR_START: Se usa para los casos de uso definidos por el OEM.

Cómo crear una CameraCaptureSession

Para crear una sesión de cámara, proporciónale uno o más búferes de salida en las que tu app pueda escribir marcos de salida. Cada búfer representa una canalización. Debes hazlo antes de empezar a usar la cámara para que el framework pueda configurar las canalizaciones internas del dispositivo y asignan los búferes de memoria para enviar tramas a los objetivos de salida necesarios.

En el siguiente fragmento de código, se muestra cómo puedes preparar una sesión de la cámara con dos búferes de salida, uno que pertenece a un SurfaceView y otro a un ImageReader Agregando el caso de uso de transmisión de PREVIEW a previewSurface y STILL_CAPTURE Uso de transmisión La sentencia case hasta imReaderSurface permite que el hardware del dispositivo optimice estas transmisiones incluso un poco más lejos.

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

En este punto, no definiste la configuración activa de la cámara. Cuando se configura la sesión, puedes crear y enviar capturas para hacer eso.

Se aplica la transformación que se aplica a las entradas tal como se escriben en el búfer. determinados por el tipo de cada objetivo, que debe ser una Surface El framework de Android sabe cómo convertir una imagen sin procesar en la configuración activa a un formato adecuado para cada objetivo. La conversión se controla según el formato de píxeles y el tamaño de la Surface particular.

El framework intenta dar lo mejor de sí, pero algunas Surface es posible que las combinaciones de configuración no funcionen y que provoquen problemas o un error de entorno de ejecución cuando envías una solicitud la disminución del rendimiento. El framework proporciona garantías de dispositivos, de superficie y de solicitud. La documentación para createCaptureSession() proporciona más información.

Capturas únicas

La configuración que se usa para cada fotograma está codificada en un CaptureRequest, que se enviados a la cámara. Para crear una solicitud de captura, puedes usar uno de los predefinidos plantillas, o puedes usar TEMPLATE_MANUAL para tener el control total. Cuando eliges un debes proporcionar uno o más búferes de salida para usar con la solicitud. Solo puedes usar los búferes que ya se definieron en la captura de usuario que quieres usar.

Las solicitudes de captura usan un patrón de compilador y brindarles a los desarrolladores la oportunidad de configurar muchos tipos de varias opciones, como exposición automática, enfoque automático, y Apertura del lente. Antes de configurar un campo, asegúrate de que la opción específica esté disponible para el el dispositivo llamando CameraCharacteristics.getAvailableCaptureRequestKeys() y de que se admita el valor deseado verificando la cámara adecuada característica, como la exposición automática disponible modos.

Cómo crear una solicitud de captura para un SurfaceView con la plantilla diseñada para ofrecer vistas previas sin modificaciones, usa 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);

Con una solicitud de captura definida, ahora puedes enviar a la sesión de la cámara:

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

Cuando una trama de salida se coloca en el búfer específico, una captura devolución de llamada . En muchos casos, devoluciones de llamada adicionales, como ImageReader.OnImageAvailableListener: se activa cuando se procesa la trama que contiene. Se encuentra a la(s) en este punto, puedes recuperar datos de imagen del búfer especificado.

Repetir CaptureRequests

Las solicitudes de una sola cámara son fáciles de hacer, pero se usan para mostrar una transmisión en vivo. de vista previa o video, no son muy útiles. En ese caso, debes recibir un de fotogramas, no solo uno. El siguiente fragmento de código muestra cómo agregar un repetición de la solicitud a la sesión:

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

Una solicitud de captura recurrente hace que el dispositivo de la cámara capture imágenes de forma continua con la configuración del CaptureRequest proporcionado. La API de Camera2 permite capturar videos con la cámara enviando se repite CaptureRequests, como se muestra en este Ejemplo de Camera2 en un repositorio de GitHub. También puede renderizar videos en cámara lenta capturando una video de alta velocidad (cámara lenta) con ráfaga repetida CaptureRequests como se muestra en la app de ejemplo de video en cámara lenta Camera2 en GitHub.

Cómo intercalar CaptureRequests

Para enviar una segunda solicitud de captura mientras la solicitud de captura recurrente está activa, haz lo siguiente: como mostrar un visor y permitir que los usuarios tomen una foto, no necesitas detener la solicitud recurrente en curso. En cambio, se emitirá una captura no repetitiva mientras se ejecuta la solicitud recurrente.

Cualquier búfer de salida utilizado se debe configurar como parte de la sesión de la cámara. cuando se crea la sesión por primera vez. Las solicitudes repetidas tienen menor prioridad que solicitudes de un solo fotograma o de ráfaga, que permiten que funcione el siguiente ejemplo:

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

Sin embargo, este enfoque presenta una desventaja: no se sabe exactamente cuándo se produce la única solicitud. En la siguiente figura, si A es la columna solicitud de captura y B es la solicitud de captura de un solo fotograma. Así es como la sesión procesa la cola de solicitudes:

Figura 2: Ilustración de una fila de solicitudes para la sesión de la cámara en curso

No hay garantías para la latencia entre la última solicitud recurrente desde A antes de que se active la solicitud B y la próxima vez que se use A por lo que es posible que se omitan algunos fotogramas. Hay algunas cosas que puedes hacer para mitigar este problema:

  • Agrega los destinos de salida de la solicitud A a la solicitud B. De esa manera, cuando El marco de B está listo y se copia en los destinos de salida de A. Esto es esencial cuando se hacen instantáneas de video para mantener una velocidad de fotogramas constante. En el código anterior, agregas singleRequest.addTarget(previewSurface) antes de crear la solicitud.

  • Usar una combinación de plantillas diseñadas para esta situación en particular como un retraso sin obturador.