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:
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
CaptureRequest
s,
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:
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".