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 ao mesmo tempo.

Uma razão para fazer isso é que um stream, frames sequenciais da câmera, de um CameraDevice, é otimizado para uma tarefa específica, como mostrar um visor, enquanto outros podem ser usados para tirar uma foto ou fazer um vídeo gravação.Os streams funcionam como pipelines paralelos que processam frames brutos saindo da câmera,um quadro de cada vez:

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

O processamento paralelo sugere que pode haver limites de desempenho dependendo a capacidade de processamento disponível na CPU, GPU ou em outro processador. Se um 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 automaticamente nos modelos apropriados formato de saída por lógica implícita associados a cada pipeline. O CameraDevice usado na atualização desta página os exemplos de código não são específicos, então você primeiro enumera todas as câmeras disponíveis antes de continuar.

É possível usar o CameraDevice para criar um CameraCaptureSession, que é específico para esse CameraDevice. Um CameraDevice precisa receber um configuração de frame para cada frame bruto usando CameraCaptureSession. A especifica atributos da câmera, como foco automático, abertura, efeitos, e exposição. Devido a restrições de hardware, só é possível fazer ativo no sensor da câmera a qualquer momento, chamado de active.

No entanto, os casos de uso de streaming aprimoram e estendem as formas anteriores de usar CameraDevice. para transmitir sessões de captura, o que permite otimizar o fluxo da câmera para seus para um caso de uso específico. Por exemplo, ele pode aumentar a duração da bateria ao otimizar videochamadas.

Um CameraCaptureSession descreve todos os possíveis pipelines 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 uma, mais de um ou todos os pipelines disponíveis para receber um frame da CameraDevice. É possível enviar muitas solicitações de captura durante a vida útil de uma captura. sessão. Cada solicitação pode mudar a configuração ativa e o conjunto de resultados pipelines que recebem a imagem bruta.

Use casos de uso de streaming para melhorar a performance

Os casos de uso de stream são uma maneira de melhorar o desempenho da captura da Camera2 de conteúdo. Elas dão ao dispositivo de hardware mais informações para ajustar parâmetros, que oferece uma melhor experiência de câmera para sua tarefa específica.

Isso Permite que o dispositivo de câmera otimize o hardware e os pipelines de software da câmera com base nos cenários dos usuários para cada fluxo. Para mais informações sobre o uso de streams Casos, consulte setStreamUseCase.

Os casos de uso de streaming permitem especificar como um determinado stream da câmera é usado em mais detalhes, além de definir um modelo em CameraDevice.createCaptureRequest(): Isso permite que o hardware da câmera otimize como ajuste, modo de sensor ou configurações do sensor da câmera, com base no as 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 do aplicativo atual. É equivalente a não configurando qualquer caso de uso de stream.

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

  • STILL_CAPTURE: otimizada para captura de alta resolução e alta qualidade. mantenham os frame rates como as visualizações.

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

  • VIDEO_CALL: recomendado para usos de câmera de longa duração em que o consumo de energia seja uma preocupante.

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

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

Criar uma CameraCaptureSession

Para criar uma sessão de câmera, forneça um ou mais buffers de saída em que o app pode gravar frames de saída. Cada buffer representa um canal. Você deve 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 a seguir mostra como preparar uma sessão de câmera com dois buffers de saída, um que pertence SurfaceView e outro para um ImageReader Adicionando o caso de uso do stream PREVIEW a previewSurface e Uso de stream do STILL_CAPTURE O uso de caso para imReaderSurface permite que o hardware do dispositivo otimize esses streams ainda mais adiante.

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

Até aqui, você não definiu a configuração ativa da câmera. Quando a sessão é configurada, é possível criar e enviar imagens solicitações para fazer isso.

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

O framework tenta fazer o possível, mas algumas Surface de configuração do Terraform podem não funcionar, causando problemas como não está sendo criada, gerando um erro de tempo de execução quando você envia uma solicitação ou degradação do desempenho. O framework oferece garantias para soluções combinações de dispositivo, superfície e parâmetros de solicitação. A documentação createCaptureSession() fornece mais informações.

CaptureRequests únicos

A configuração usada para cada frame é codificada em um CaptureRequest, que é enviados para a câmera. Para criar uma solicitação de captura, é possível usar um dos predefinido modelos, ou use TEMPLATE_MANUAL para ter controle total. Quando você escolhe é necessário fornecer um ou mais buffers de saída para serem usados com da solicitação. Só é possível usar buffers já definidos na captura que você pretende usar.

As solicitações de captura usam um padrão de builder e dar aos desenvolvedores a oportunidade de definir muitos incluindo exposição automática, foco automático, e abertura da lente. Antes de definir um campo, verifique se a opção específica está disponível para o do dispositivo chamando CameraCharacteristics.getAvailableCaptureRequestKeys() e se há suporte para o valor desejado verificando a câmera apropriada como a exposição automática disponível dois modos.

Para criar uma solicitação de captura para um SurfaceView usando o modelo projetada para visualização sem qualquer modificação, 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 é possível enviar 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 em um buffer específico, um objeto de captura callback é acionada. Em muitos casos, callbacks adicionais, como ImageReader.OnImageAvailableListener, é acionado quando o frame que ele contém é processado. Está em nesse ponto, é possível recuperar dados de imagem do buffer especificado.

Repetir CaptureRequests

As solicitações de câmera única são simples de fazer, mas para exibir uma visualização ou vídeo, elas não são muito úteis. Nesse caso, você precisa receber uma stream contínuo de frames, não apenas um único. O snippet de código a seguir mostra como adicionar solicitação recorrente à 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 recorrente faz com que o dispositivo de câmera capture continuamente imagens usando as configurações no CaptureRequest fornecido. A API Camera2 também permite que os usuários capturem vídeos da câmera enviando repetindo CaptureRequests como nesta Amostra Camera2 no GitHub. Ele também pode renderizar vídeos em câmera lenta capturando uma vídeo de alta velocidade (câmera lenta) usando sequência repetida CaptureRequests conforme mostrado no app de exemplo de vídeo em câmera lenta do Camera2 no GitHub.

Intercalar CaptureRequests

Para enviar uma segunda solicitação de captura enquanto a solicitação de captura recorrente estiver ativa, faça o seguinte: como para exibir um visor e permitir que os usuários capturem uma foto, você não precisa interromper o pedido recorrente. Em vez disso, você emite uma captura não repetida enquanto a repetição é executada.

Qualquer buffer de saída usado precisa ser configurado como parte da sessão da câmera quando a sessão é criada. Solicitações repetidas têm prioridade menor que solicitações de frame único ou burst, que permitem que o exemplo a seguir 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 quando ocorre a solicitação única. Na figura a seguir, se A for a sequência captura de tela e B é a solicitação de captura de frame único. É assim que o processa a fila de solicitações:

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

Não há garantias de latência entre a última solicitação repetida da A antes que a solicitação B seja ativada e na próxima vez que A for usada novamente, o que pode resultar em alguns frames pulados. Há algumas coisas que você pode fazer para atenuar esse problema:

  • Adicione os destinos de saída da solicitação A à solicitação B. Dessa forma, quando O frame de B está pronto, ele é copiado para os destinos de saída de A. Isso é essencial, por exemplo, ao fazer snapshots de vídeos para manter 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 criados para esse cenário específico como o tempo de renderização zero.