Pular para o conteúdo principal

Capítulo 11: Tipos de Captura

11.1 Três Maneiras de Alimentar o Pipeline

No Capítulo 10, você viu como as solicitações viajam pelo pipeline do Camera2: da Fila Pendente para a Fila Em Voo, para o HAL, para o seu callback e superfícies de saída. Mas como você envia essas solicitações importa enormemente. O Camera2 oferece três mecanismos de envio, cada um com comportamento fundamentalmente diferente:

  1. Disparo único (capture()) — executa uma única solicitação uma vez
  2. Sequencial / Burst (captureBurst()) — executa uma lista de solicitações de forma contígua, uma após a outra
  3. Repetida (setRepeatingRequest()) — executa a mesma solicitação continuamente para sempre (ou até ser interrompida)

Além desses três modos de envio, o framework fornece seis modelos (templates) de captura que pré-configuram um CaptureRequest.Builder com padrões sensatos para casos de uso comuns (pré-visualização, captura de foto, gravação de vídeo, zero-shutter-lag, controle manual, etc.).

Ao final deste capítulo, você saberá exatamente quando usar cada tipo e modelo de captura — incluindo por que a pré-visualização sempre usa solicitações repetidas, por que a captura sequencial é a única maneira de fazer bracketing de exposição e por que as fotos estáticas usam disparo único mesmo quando uma pré-visualização está rodando.

O aplicativo Android Camera Parameters (GitHub, Play Store) demonstra todos os três tipos de captura em sua aba Capture Demo. Alterne entre os modos "Preview (Repetida)", "Foto Única (Disparo Único)" e "Burst (3 Quadros)" para ver as diferenças de comportamento de callback e tempo ao vivo no seu dispositivo.

11.2 Disparo Único: capture()

O modo de envio mais simples é a captura de disparo único via CameraCaptureSession.capture(). Ele faz exatamente o que promete: envia uma única CaptureRequest ao pipeline, executa-a exatamente uma vez e termina.

// Disparo único: capturar um único quadro estático para o ImageReader JPEG
fun captureStillPhoto() {
val builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
builder.addTarget(jpegReader.surface)
builder.set(CaptureRequest.JPEG_QUALITY, 100)
builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)

val request = builder.build()

captureSession.capture(request, object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult
) {
super.onCaptureCompleted(session, request, result)
Log.d("Capture", "Foto de disparo único concluída. Quadro #${result.frameNumber}")
}
}, backgroundHandler)
}

Quando Usar Disparo Único

Caso de UsoPor que Disparo Único?
Única fotografia estáticaExecutar exatamente uma vez por clique no obturador
Gatilho único de AF/AEDisparar CONTROL_AF_TRIGGER_START para um evento de toque para focar
Instantâneo durante vídeoCapturar um quadro de alta resolução enquanto uma solicitação de vídeo repetida está ativa
Capturar um único quadro RAWCaptura dupla RAW + JPEG para uma foto

Como o Disparo Único Interage com a Pré-visualização Repetida

Um padrão de design crítico no Camera2 é: a pré-visualização roda como uma solicitação repetida e as fotos estáticas são injetadas como solicitações de disparo único. O disparo único salta para a frente da solicitação repetida na Fila Pendente (como discutimos no modelo de fila do Capítulo 10), portanto, ele é executado imediatamente. Após a conclusão do disparo único, o framework retoma automaticamente a solicitação de pré-visualização repetida — você não precisa reenviá-la.

dica

Esse comportamento de retomada automática está incorporado ao framework do Camera2. Você nunca precisa "reiniciar a pré-visualização" manualmente após uma captura de disparo único — o framework reencaminha a solicitação repetida para você.

Fluxo de Execução de Disparo Único

11.3 Sequencial / Burst: captureBurst()

Onde o capture() envia uma solicitação, o captureBurst() envia uma List<CaptureRequest> e garante que todos os N quadros na lista sejam executados de forma contígua e em ordem, sem quadros intercalados de outras fontes (incluindo a solicitação repetida).

Essa garantia atômica e sem lacunas é o que torna a captura burst essencial para:

  • Bracketing de exposição — Capturar de 3 a 5 quadros a ±1EV, ±2EV e, em seguida, mesclá-los em HDR.
  • Bracketing de foco — Varre as distâncias de foco e, em seguida, empilha para efeitos de profundidade de campo.
  • Captura de ação / movimento — Tira de 10 a 30 quadros de um assunto em movimento rápido e, em seguida, escolhe o mais nítido.
  • Vídeo em câmera lenta (alta velocidade)createHighSpeedRequestList() + burst de captura de alta velocidade restrita.
  • Amostragem de convergência 3A — Dispara o gatilho de AF/AE e, em seguida, faz o burst até convergir.
// Burst: bracketing de exposição de 3 quadros (-2EV, 0EV, +2EV)
fun captureExposureBracket() {
val baseIso = 100
val baseExposure = 10_000_000L // 10ms = linha de base "0EV"

val burstList: MutableList<CaptureRequest> = mutableListOf()

// Quadro 0: -2EV (exposição 4x mais curta = mais escura)
burstList += cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE).apply {
addTarget(jpegReader.surface)
set(CaptureRequest.SENSOR_SENSITIVITY, baseIso)
set(CaptureRequest.SENSOR_EXPOSURE_TIME, baseExposure / 4)
set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF)
}.build()

// Quadro 1: 0EV (exposição correta)
burstList += cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE).apply {
addTarget(jpegReader.surface)
set(CaptureRequest.SENSOR_SENSITIVITY, baseIso)
set(CaptureRequest.SENSOR_EXPOSURE_TIME, baseExposure)
set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF)
}.build()

// Quadro 2: +2EV (exposição 4x mais longa = mais brilhante)
burstList += cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE).apply {
addTarget(jpegReader.surface)
set(CaptureRequest.SENSOR_SENSITIVITY, baseIso)
set(CaptureRequest.SENSOR_EXPOSURE_TIME, baseExposure * 4)
set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF)
}.build()

captureSession.captureBurst(burstList, object : CameraCaptureSession.CaptureCallback() {
private var completedCount = 0

override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult
) {
super.onCaptureCompleted(session, request, result)
completedCount++
Log.d("Burst", "Quadro burst $completedCount/${burstList.size} concluído. Quadro #${result.frameNumber}")

if (completedCount == burstList.size) {
Log.d("Burst", "Todos os ${burstList.size} quadros do bracketing foram capturados!")
// TODO: Mesclar HDR, empilhar foco ou deixar o usuário escolher o melhor quadro
}
}
}, backgroundHandler)
}

A Garantia Contígua em Ação

A propriedade principal do burst é que toda a lista é enfileirada atomicamente — mesmo que um setRepeatingRequest() esteja ativo, todos os N quadros do burst serão executados em sequência antes que a solicitação repetida seja retomada. A solicitação repetida não é intercalada entre os quadros do burst.

Limites de Tamanho do Burst

O tamanho máximo do burst que você pode enviar em uma única chamada captureBurst() é determinado por:

  • CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW — para saídas RAW
  • CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC — para saídas processadas (YUV/JPEG)
  • Largura de banda prática do hardware (bursts em 4K serão mais curtos que bursts em 1080p)

Para dispositivos FULL típicos, bursts de JPEG processado de 10 a 50 quadros funcionam bem. Bursts em RAW podem ser limitados a 5-10 quadros, dependendo do sensor e da memória.

Burst de Alta Velocidade para Câmera Lenta

Para vídeo em câmera lenta, o Camera2 fornece CameraDevice.createHighSpeedRequestList(), que converte uma CaptureRequest normal em uma lista de solicitações burst adequadas para vídeo de captura restrita e alta velocidade (ex: 120fps ou 240fps). Isso é emparelhado com CameraCaptureSession.captureBurst() e requer a capacidade CONSTRAINED_HIGH_SPEED_VIDEO:

// Burst: Câmera lenta de alta velocidade (120fps)
fun captureHighSpeedSlowMo() {
val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
val supportsHighSpeed = capabilities?.contains(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
) ?: false

if (!supportsHighSpeed) {
Log.w("HighSpeed", "O dispositivo não suporta vídeo de alta velocidade restrita")
return
}

// Criar uma única solicitação base (visando a superfície do MediaRecorder)
val baseBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
baseBuilder.addTarget(mediaRecorderSurface)
val baseRequest = baseBuilder.build()

// Expandir para lista burst de alta velocidade — o framework otimiza para 120fps
val highSpeedBurst = cameraDevice.createHighSpeedRequestList(listOf(baseRequest))

// Enviar a lista burst otimizada via captureBurst
captureSession.captureBurst(highSpeedBurst, highSpeedCallback, backgroundHandler)
}

11.4 Repetida: setRepeatingRequest()

A base de trabalho do Camera2 é a solicitação repetida, enviada via CameraCaptureSession.setRepeatingRequest(). Em vez de ser executada uma vez, o framework reencaminha a mesma solicitação após cada quadro, para sempre — produzindo um fluxo contínuo de quadros na taxa de quadros nativa do hardware.

// Repetida: Iniciar pré-visualização da câmera (fluxo contínuo de 30fps)
fun startPreview() {
val builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
builder.addTarget(previewSurface)
builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
builder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO)

val repeatingRequest = builder.build()

captureSession.setRepeatingRequest(
repeatingRequest,
object : CameraCaptureSession.CaptureCallback() {
private var frameCount = 0
private var lastFpsLogMs = 0L

override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult
) {
super.onCaptureCompleted(session, request, result)
frameCount++
val now = System.currentTimeMillis()
if (now - lastFpsLogMs > 1000) {
Log.d("Preview", "FPS da Pré-visualização: $frameCount")
frameCount = 0
lastFpsLogMs = now
}
}
},
backgroundHandler
)
}

Por que a Pré-visualização Deve Usar Solicitações Repetidas

Se você tentasse implementar uma pré-visualização de 30fps usando o capture() chamado 30 vezes por segundo a partir de um timer, você:

  1. Desperdiçaria CPU reenviando solicitações idênticas a cada 33ms.
  2. Acumularia atraso se seu timer estivesse atrasado.
  3. Teria lacunas entre os quadros se os callbacks de disparo único bloqueassem.
  4. Lutaria com o gerenciamento de fila do framework.

A solicitação repetida é gerenciada inteiramente dentro do framework/HAL. Após a conclusão de cada quadro, o HAL agenda automaticamente a próxima exposição — sem envolvimento da thread do app. Isso produz uma pré-visualização suave e sem lacunas com zero sobrecarga de CPU do app.

Parando Solicitações Repetidas

Para parar o fluxo repetido, chame stopRepeating(). Isso remove a solicitação repetida da fila, mas não descarta os quadros que já estão "em voo". Chame abortCaptures() para descartar tudo à força (e disparar onCaptureFailed com REASON_FLUSHED para os quadros em voo).

// Pausar temporariamente a pré-visualização
fun pausePreview() {
captureSession.stopRepeating()
Log.d("Preview", "Repetição parada. Quadros em voo ainda serão concluídos.")
}

// Parada de emergência — descarte tudo agora mesmo
fun emergencyStopAllCaptures() {
captureSession.stopRepeating()
captureSession.abortCaptures()
// Todos os quadros em voo falharão com REASON_FLUSHED
}

Solicitações Repetidas Também São para Gravação de Vídeo

Além da pré-visualização, as solicitações repetidas são usadas para gravação de vídeo (visando uma Surface de MediaRecorder ou MediaCodec) e análise contínua de imagem (visando um ImageReader YUV de baixa resolução para detecção de rostos, inferência de ML, etc.).

O padrão é sempre o mesmo: configure uma vez, deixe fluir, atualize os parâmetros da solicitação quando desejar alterar as configurações (ex: alterar o zoom digital no meio do fluxo atualizando a região de corte em uma nova solicitação repetida).

// Repetida: Atualizar o nível de zoom ao vivo durante a pré-visualização/vídeo
fun updateDigitalZoom(cropRegion: Rect) {
val builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
builder.addTarget(previewSurface)
builder.addTarget(mediaRecorderSurface)
builder.set(CaptureRequest.SCALER_CROP_REGION, cropRegion)
builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)

// Substituir a solicitação repetida antiga por uma nova (mesmos alvos, novo corte)
captureSession.setRepeatingRequest(builder.build(), currentCallback, backgroundHandler)
Log.d("Zoom", "Solicitação repetida atualizada com corte ${cropRegion.width()}x${cropRegion.height()}")
}

11.5 Modelos de Captura (Templates)

Cada CaptureRequest.Builder começa a partir de um modelo (template): você chama cameraDevice.createCaptureRequest(TEMPLATE_XXX) e o framework preenche o construtor com padrões otimizados de hardware para esse caso de uso. Você então substitui apenas as configurações específicas que precisa.

Os modelos existem porque o pipeline de câmera de um telefone possui dezenas de controles (intensidade da redução de ruído, aprimoramento de bordas, curva de tom, modo anti-banding, faixa de taxa de quadros, ...). Os modelos definem linhas de base sensatas para que você não precise configurar cada uma delas do zero.

Os Seis Modelos, O Que Eles Pré-configuram e Quando Usá-los

ModeloCaso de UsoPrincipais Configurações Pré-configuradas
TEMPLATE_PREVIEWViewfinder / pré-visualização ao vivoPrioridade para baixa latência, 3A (AF/AE/AWB) em automático contínuo, NR/nitidez modestos, alta taxa de quadros (30fps). Sacrifica qualidade mínima por suavidade.
TEMPLATE_STILL_CAPTUREFoto de disparo únicoPrioridade para qualidade máxima, AF em modo de foto, NR/nitidez totais, codificação JPEG de alta qualidade, pode baixar a taxa de quadros para melhorar a qualidade daquele quadro único.
TEMPLATE_RECORDGravação de vídeoTaxa de quadros estável (corresponde à saída do MediaRecorder), AF contínuo, timestamps de sincronização de áudio-vídeo, anti-banding ativado, NR médio — ajustado para movimento + compressão.
TEMPLATE_VIDEO_SNAPSHOTFoto de alta resolução durante gravação de vídeoComo o STILL_CAPTURE, mas preserva as configurações do quadro de vídeo — captura uma foto de alta resolução sem parar o fluxo de gravação de vídeo.
TEMPLATE_ZERO_SHUTTER_LAGCaptura ZSL (Capítulo 14)Constrói um buffer circular de quadros recentes. Quando o obturador é pressionado, um quadro passado é retornado para atraso zero. Requer capacidade de burst e reprocessamento privado.
TEMPLATE_MANUALControles manuais / proTodos os modos 3A definidos como OFF por padrão para que você possa definir manualmente a exposição do sensor, ISO, foco da lente e ganhos de correção de cor sem interferência. Base para uma UI de câmera pro.
// Exemplos de modelos — veja o que acontece ao começar com cada um

// TEMPLATE_PREVIEW — suave, baixa latência
val previewBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
val previewRequest = previewBuilder.build()
Log.d("Template", "PREVIEW AF_MODE = ${previewRequest.get(CaptureRequest.CONTROL_AF_MODE)}")
// ^ Normalmente CONTROL_AF_MODE_CONTINUOUS_PICTURE (sempre refocando)

// TEMPLATE_STILL_CAPTURE — qualidade máxima por quadro
val stillBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
val stillRequest = stillBuilder.build()
Log.d("Template", "STILL_CAPTURE JPEG_QUALITY = ${stillRequest.get(CaptureRequest.JPEG_QUALITY)}")
// ^ Normalmente 100 (codificação de qualidade máxima)

// TEMPLATE_MANUAL — todos os controles automáticos desativados
val manualBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_MANUAL)
val manualRequest = manualBuilder.build()
Log.d("Template", "MANUAL AE_MODE = ${manualRequest.get(CaptureRequest.CONTROL_AE_MODE)}")
Log.d("Template", "MANUAL AF_MODE = ${manualRequest.get(CaptureRequest.CONTROL_AF_MODE)}")
Log.d("Template", "MANUAL AWB_MODE = ${manualRequest.get(CaptureRequest.CONTROL_AWB_MODE)}")
// ^ Normalmente AE_MODE_OFF, AF_MODE_OFF, AWB_MODE_OFF — manual desde o início
dica

Sempre comece com um modelo e substitua campos específicos. Começar com TEMPLATE_PREVIEW e então substituir de 2 a 3 configurações (ex: região de corte para zoom, viés de AE para compensação de exposição) é massivamente menos propenso a erros do que criar uma solicitação a partir de um modelo vazio (o que nem é possível — cada createCaptureRequest requer um modelo).

11.6 Comparando os Três Tipos de Captura

DimensãoDisparo Único capture()Burst captureBurst()Repetida setRepeatingRequest()
ExecuçãoUma única solicitação roda uma vezUma lista de N solicitações roda de forma contíguaA mesma solicitação roda a cada quadro (reencaminhada automaticamente)
PrioridadeMais alta — salta para a FRENTE da Fila PendenteAlta — todos os N quadros inseridos atomicamente na FRENTEMais baixa — disparo único/burst interrompem a repetição, que retoma depois
InterrupçãoInterrompe a repetição; a repetição retoma depoisInterrompe a repetição; todo o burst termina antes que a repetição retomeInterrompida por qualquer disparo único ou burst; retoma automaticamente depois
Comportamento da FilaUma única solicitação enfileiradaN solicitações enfileiradas de forma contígua (sem lacunas)Uma solicitação conceitual reencaminhada a cada ciclo
Usos TípicosFoto única, gatilho único de AF, foto com flashBracketing de exposição, empilhamento de foco, sequência de ação, câmera lenta, HDRPré-visualização, gravação de vídeo, análise de ML contínua, detecção facial ao vivo
Callback de ResultadoonCaptureCompleted dispara exatamente uma vezonCaptureCompleted dispara N vezes (uma vez por quadro do burst)onCaptureCompleted dispara continuamente para cada quadro (30-60x/seg)

11.7 Vendo os Tipos de Captura no App Android Camera Parameters

Abra o aplicativo Android Camera Parameters (GitHub, Play Store) e navegue até a aba Capture Demo. O aplicativo expõe os três tipos de captura lado a lado:

  • Toque em Start Preview para chamar setRepeatingRequest(TEMPLATE_PREVIEW) e ver um log do CaptureCallback ao vivo (quadros 1, 2, 3, ... rolando a cada ~33ms).
  • Toque em Take Photo para injetar uma solicitação única de capture(TEMPLATE_STILL_CAPTURE) enquanto a pré-visualização está rodando. Você verá a contagem do callback pausar brevemente para o quadro de alta qualidade e retomar perfeitamente assim que a repetição for reiniciada automaticamente.
  • Toque em Burst 5 Frames para chamar captureBurst(List<CaptureRequest(5)>). Observe que exatamente 5 quadros são concluídos um após o outro antes que a rolagem da pré-visualização continue — provando a garantia contígua.

Você também pode inspecionar REQUEST_MAX_NUM_OUTPUT_RAW e REQUEST_MAX_NUM_OUTPUT_PROC na aba Raw JSON para ver os limites de tamanho de burst do seu dispositivo.

11.8 Resumo

ConceitoConclusão Principal
Disparo único capture()Solicitação única, roda uma vez, prioridade mais alta. Para fotos estáticas, gatilhos de AF. Retoma a repetição automaticamente depois.
Burst captureBurst()Lista de solicitações roda de forma contígua, sem intercalação. Para bracketing, movimento, câmera lenta. Toda a lista salta na fila atomicamente.
Repetida setRepeatingRequest()Uma solicitação flui continuamente. O framework reencaminha automaticamente. Para pré-visualização, vídeo, análise. Prioridade mais baixa.
Modelos (Templates)Seis linhas de base preenchem o Builder com padrões. Comece com PREVIEW/STILL_CAPTURE/RECORD/ZSL/MANUAL e substitua apenas o que for necessário.
Regras de interrupçãoO disparo único e o burst sempre têm precedência sobre a repetição. A repetição retoma automaticamente depois. Quadros de burst nunca são divididos.

O Que Vem a Seguir

No Capítulo 12: Mergulho Profundo no CameraCharacteristics, exploraremos o objeto de metadados estáticos que descreve o que sua câmera pode fazer antes mesmo de você abri-la. Analisaremos os níveis de hardware (LEGACY → LIMITED → FULL → LEVEL_3 → EXTERNAL), o sistema de flags de capacidade (MANUAL_SENSOR, RAW, DEPTH_OUTPUT, etc.) e como consultar tudo isso em tempo de execução para escrever aplicativos que funcionem em mais de 10.000 modelos de dispositivos Android.