Sessões e solicitações de captura da câmera

Observação:esta página se refere ao pacote Camera2. A menos que seu app exija recursos específicos e de baixo nível do Camera2, recomendamos o uso do CameraX. CameraX e Camera2 oferecem suporte ao Android 5.0 (nível 21 da API) e versões mais recentes.

Um único dispositivo Android pode ter várias câmeras. Cada câmera é um CameraDevice, e uma CameraDevice pode gerar mais de um stream simultaneamente.

Um motivo para fazer isso é que um stream, frames de câmera sequenciais provenientes de uma CameraDevice, seja otimizado para uma tarefa específica, como mostrar um visor, enquanto outros podem ser usados para tirar uma foto ou fazer uma gravação de vídeo.Os streams funcionam como pipelines paralelos que processam frames brutos saídos da câmera,um frame por vez:

Figura 1. Ilustração do "Como criar um app de câmera universal" (Google I/O 2018)

O processamento paralelo sugere que pode haver limites de desempenho, dependendo da capacidade de processamento disponível da CPU, GPU ou de outro processador. Se um pipeline não consegue acompanhar os frames recebidos, ele começa a descartá-los.

Cada pipeline tem o próprio formato de saída. Os dados brutos que chegam são transformados automaticamente no formato de saída apropriado pela lógica implícita associada a cada pipeline. O CameraDevice usado em todos os exemplos de código desta página não é específico. Portanto, primeiro enumera todas as câmeras disponíveis antes de continuar.

Você pode usar o CameraDevice para criar um CameraCaptureSession, que é específico para esse CameraDevice. Um CameraDevice precisa receber uma configuração de frame para cada frame bruto usando o CameraCaptureSession. A configuração especifica atributos da câmera, como foco automático, abertura, efeitos e exposição. Devido a restrições de hardware, apenas uma configuração fica ativa no sensor da câmera a qualquer momento, o que é chamado de configuração ativa.

No entanto, os casos de uso de streaming melhoram e estendem maneiras anteriores de usar CameraDevice para sessões de captura de streaming, o que permite otimizar o stream da câmera para seu caso de uso específico. Por exemplo, ele pode melhorar a duração da bateria ao otimizar videochamadas.

Um CameraCaptureSession descreve todos os pipelines possíveis vinculados à CameraDevice. Quando uma sessão é criada, não é possível adicionar ou remover pipelines. O CameraCaptureSession mantém uma fila de CaptureRequests, que se tornam a configuração ativa.

Um CaptureRequest adiciona uma configuração à fila e seleciona um, mais de um ou todos os pipelines disponíveis para receber um frame do CameraDevice. É possível enviar muitas solicitações de captura durante a vida útil de uma sessão de captura. Cada solicitação pode alterar a configuração ativa e o conjunto de pipelines de saída que recebem a imagem bruta.

Use casos de uso de streaming para melhorar a performance

Os casos de uso de streaming são uma maneira de melhorar o desempenho das sessões de captura do Camera2. Elas fornecem ao dispositivo de hardware mais informações para ajustar parâmetros, o que proporciona uma experiência de câmera melhor para sua tarefa específica.

Isso permite que o dispositivo de câmera otimize pipelines de hardware e software da câmera com base nos cenários do usuário para cada stream. Para mais informações sobre os casos de uso de stream, consulte setStreamUseCase.

Os casos de uso de streaming permitem especificar com mais detalhes como um stream da câmera específico é usado, além de definir um modelo em CameraDevice.createCaptureRequest(). Isso permite que o hardware da câmera otimize parâmetros, como ajuste, modo ou configurações do sensor da câmera, com base em compensações de qualidade ou latência adequadas para casos de uso específicos.

Os casos de uso de streaming incluem:

  • DEFAULT: abrange todo o comportamento existente do aplicativo. Isso equivale a não definir nenhum caso de uso de stream.

  • PREVIEW: recomendado para análise de imagens no visor ou no app.

  • STILL_CAPTURE: otimizado para captura de alta resolução em alta qualidade e não esperado para manter frame rates semelhantes a visualizações.

  • VIDEO_RECORD: otimizada para captura de vídeo de alta qualidade, incluindo estabilização de imagem de alta qualidade, se compatível com o dispositivo e ativada pelo aplicativo. Essa opção pode produzir frames de saída com um atraso substancial em relação ao tempo real, para permitir uma estabilização de alta qualidade ou outro processamento.

  • VIDEO_CALL: recomendado para usos de câmeras de longa duração em que o consumo de energia é um problema.

  • PREVIEW_VIDEO_STILL: recomendado para apps de mídias sociais ou casos de uso de fluxo único. É um stream multiuso.

  • VENDOR_START: usado para casos de uso definidos pelo OEM.

Criar uma CameraCaptureSession

Para criar uma sessão de câmera, forneça a ela um ou mais buffers de saída em que o app pode gravar frames de saída. Cada buffer representa um canal. Faça isso antes de começar a usar a câmera para que o framework possa configurar os pipelines internos do dispositivo e alocar buffers de memória para enviar frames aos destinos de saída necessários.

O snippet de código abaixo mostra como preparar uma sessão de câmera com dois buffers de saída, um pertencente a SurfaceView e outro a ImageReader. Adicionar o caso de uso de streaming PREVIEW a previewSurface e STILL_CAPTURE caso de uso de streaming a imReaderSurface permite que o hardware do dispositivo otimize esses streams ainda mais.

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

Você ainda não definiu a configuração ativa da câmera. Quando a sessão estiver configurada, será possível criar e enviar solicitações de captura para fazer isso.

A transformação aplicada às entradas à medida que são gravadas no buffer é determinada pelo tipo de cada destino, que precisa ser um Surface. O framework do Android sabe como converter uma imagem bruta na configuração ativa em um formato apropriado para cada destino. A conversão é controlada pelo formato e pelo tamanho do pixel do Surface específico.

O framework tenta fazer o melhor possível, mas algumas combinações de configuração Surface podem não funcionar, causando problemas como a não criação da sessão, geração de um erro no ambiente de execução ao enviar uma solicitação ou degradação do desempenho. O framework oferece garantias para combinações específicas de parâmetros de dispositivo, superfície e solicitação. A documentação de createCaptureSession() fornece mais informações.

CaptureRequests únicos

A configuração usada para cada frame é codificada em um CaptureRequest, que é enviado à câmera. Para criar uma solicitação de captura, use um dos modelos predefinidos ou use TEMPLATE_MANUAL para ter controle total. Ao escolher um modelo, é preciso fornecer um ou mais buffers de saída a serem usados com a solicitação. Só é possível usar buffers que já foram definidos na sessão de captura que você pretende usar.

As solicitações de captura usam um padrão de criador e oferecem aos desenvolvedores a oportunidade de definir muitas opções diferentes, incluindo exposição automática, foco automático e abertura da lente. Antes de definir um campo, confira se a opção específica está disponível para o dispositivo chamando CameraCharacteristics.getAvailableCaptureRequestKeys() e se o valor desejado tem suporte verificando a característica da câmera adequada, como os modos de exposição automática disponíveis.

Para criar uma solicitação de captura para uma SurfaceView usando o modelo projetado para visualização sem modificações, use 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);

Com uma solicitação de captura definida, agora você pode enviá-la para a sessão da câmera:

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

Quando um frame de saída é colocado no buffer específico, um callback de captura é acionado. Em muitos casos, callbacks extras, como ImageReader.OnImageAvailableListener, são acionados quando o frame que ele contém é processado. É nesse momento que você pode extrair dados de imagem do buffer especificado.

Repetir CaptureRequests

As solicitações de câmera única são simples de fazer, mas não são muito úteis para mostrar uma visualização em tempo real ou um vídeo. Nesse caso, você precisa receber um fluxo contínuo de frames, não apenas um. O snippet de código a seguir mostra como adicionar uma solicitação repetida à sessão:

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

Uma solicitação de captura repetida faz com que a câmera capture continuamente imagens usando as configurações no CaptureRequest fornecido. A API Camera2 também permite que os usuários capturem vídeo da câmera enviando CaptureRequests repetidos, conforme mostrado neste repositório de exemplo do Camera2 (link em inglês) no GitHub. Ela também pode renderizar um vídeo em câmera lenta capturando um vídeo de alta velocidade (câmera lenta) usando o burst CaptureRequests repetido, conforme mostrado no app de exemplo de vídeo em câmera lenta da Camera2 no GitHub (link em inglês).

Intercalar CaptureRequests

Para enviar uma segunda solicitação de captura enquanto a solicitação de captura repetida está ativa, por exemplo, para exibir um visor e permitir que os usuários capturem uma foto, não é necessário interromper a solicitação de repetição em andamento. Em vez disso, você emite uma solicitação de captura não repetida enquanto a solicitação repetida continua sendo executada.

Qualquer buffer de saída usado precisa ser configurado como parte da sessão da câmera quando a sessão é criada pela primeira vez. As solicitações de repetição têm menor prioridade do que as solicitações de frame único ou burst, o que permite que o exemplo abaixo funcione:

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

No entanto, essa abordagem tem uma desvantagem: você não sabe exatamente quando a solicitação única ocorre. Na figura a seguir, se A for a solicitação de captura repetida e B for a solicitação de captura de frame único, a sessão vai processar a fila de solicitações desta maneira:

Figura 2. Ilustração de uma fila de solicitações para a sessão de câmera em andamento

Não há garantias de latência entre a última solicitação repetida de A antes da ativação da solicitação B e a próxima vez que A for usada novamente. Por isso, alguns frames podem ser ignorados. Há algumas coisas que podem ser feitas para atenuar esse problema:

  • Adicione os destinos de saída da solicitação A à solicitação B. Dessa forma, quando o frame de B estiver pronto, ele será copiado para os destinos de saída de A. Por exemplo, isso é essencial ao fazer snapshots de vídeo para manter um frame rate estável. No código anterior, você adiciona singleRequest.addTarget(previewSurface) antes de criar a solicitação.

  • Use uma combinação de modelos projetados para funcionar nessa situação específica, como "zero-shutter-lag".