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.
O recurso de várias câmeras foi lançado no Android 9 (nível 28 da API). Desde o lançamento, foram lançados no mercado com suporte para essa API. Muitos casos de uso com várias câmeras estão fortemente acoplados a uma configuração de hardware específica. Em outras palavras, não todos os casos de uso são compatíveis com todos os dispositivos, o que permite usar várias câmeras apresenta um bom candidato para o recurso do Google Play Exibição.
Veja alguns casos de uso comuns:
- Zoom: alterna entre as câmeras, dependendo da região de corte ou do foco desejado. comprimento.
- Profundidade: usar várias câmeras para criar um mapa de profundidade.
- Bokeh: uso de informações de profundidade inferidas para simular uma conexão estreita semelhante a DSLR intervalo de foco.
A diferença entre as câmeras lógicas e físicas
Entender a API de várias câmeras exige entender a diferença entre câmeras lógicas e físicas. Para referência, considere um dispositivo com três câmeras traseiras. Neste exemplo, cada uma das três câmeras traseiras considerada uma câmera física. Uma câmera lógica é um agrupamento de dois ou mais dessas câmeras físicas. A saída do módulo uma câmera pode ser um fluxo que vem de uma das câmeras físicas subjacentes, ou fluxo fundido emitido por mais de uma câmera física ao mesmo tempo. De qualquer forma, a transmissão é processada pelo hardware da câmera. camada de abstração (HAL).
Muitos fabricantes de smartphones desenvolvem aplicativos próprios para câmeras, que geralmente vêm pré-instalados nos dispositivos. Para usar todos os recursos do hardware, eles podem usar APIs privadas ou ocultas ou receber tratamento especial de a implementação do driver a que outros aplicativos não têm acesso. Algumas dispositivos implementam o conceito de câmeras lógicas, fornecendo um fluxo mesclado de frames das diferentes câmeras físicas, mas somente para alguns aplicativos conteinerizados. Com frequência, apenas uma das câmeras físicas fica exposta ao de análise de dados em nuvem. A situação dos desenvolvedores terceirizados anteriores ao Android 9 é ilustrado no diagrama a seguir:

A partir do Android 9, APIs privadas não são mais permitidas em apps Android. Com a inclusão de suporte a várias câmeras no framework, o Android melhor recomendam que os fabricantes de smartphones exponham uma câmera lógica para todas as câmeras físicas voltadas para a mesma direção. Veja a seguir que os desenvolvedores de terceiros esperam ver em dispositivos com Android 9 e mais alta:

O que a câmera lógica oferece depende totalmente da implementação do OEM da HAL da câmera. Por exemplo, um dispositivo como o Pixel 3 implementa a lógica de tal forma que escolha uma de suas câmeras físicas com base no a distância focal e a região de corte solicitadas.
A API de várias câmeras
A nova API adiciona as novas constantes, classes e métodos a seguir:
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
CameraCharacteristics.getPhysicalCameraIds()
CameraCharacteristics.getAvailablePhysicalCameraRequestKeys()
CameraDevice.createCaptureSession(SessionConfiguration config)
CameraCharacteritics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE
OutputConfiguration
eSessionConfiguration
Devido a mudanças no Documento de definição de compatibilidade (CDD) do Android, os a API multicâmera também tem algumas expectativas dos desenvolvedores. Dispositivos com duas câmeras existiam antes do Android 9, mas abrindo mais de uma câmera envolvia tentativa e erro ao mesmo tempo. No Android 9 e versões mais recentes, várias câmeras fornece um conjunto de regras para especificar quando é possível abrir um par de instâncias que fazem parte da mesma câmera lógica.
Na maioria dos casos, dispositivos com o Android 9 e versões mais recentes expõem todos os componentes (exceto possivelmente para tipos de sensores menos comuns, como infravermelho), além de uma câmera lógica mais fácil de usar. Para cada combinação de streams um fluxo de uma câmera lógica pode ser substituído pelo dois streams das câmeras físicas subjacentes.
Várias transmissões simultâneas
Como usar simultaneamente vários streams de câmera
aborda as regras para usar várias transmissões simultâneas com uma única câmera.
Com uma adição notável, as mesmas regras se aplicam a várias câmeras.
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
explica como substituir um YUV_420_888 lógico ou stream bruto por dois
streams físicos. Ou seja, cada fluxo do tipo YUV ou RAW pode ser substituído por
dois streams de tipo e tamanho idênticos. Comece com um stream da câmera
a seguinte configuração garantida para dispositivos com uma única câmera:
- Stream 1: tipo YUV, tamanho
MAXIMUM
da câmera lógicaid = 0
Assim, um dispositivo compatível com várias câmeras permite que você crie uma sessão substituindo o fluxo YUV lógico por dois físicos:
- Stream 1: tipo YUV, tamanho
MAXIMUM
da câmera físicaid = 1
- Stream 2: tipo YUV, tamanho
MAXIMUM
da câmera físicaid = 2
Só será possível substituir um stream YUV ou RAW por dois equivalentes se
Essas duas câmeras são parte de um agrupamento lógico de câmeras, que está listado em
CameraCharacteristics.getPhysicalCameraIds()
As garantias fornecidas pelo framework são o mínimo necessário para ver frames de mais de uma câmera física simultaneamente. Streams adicionais são suportados na maioria dos dispositivos, às vezes permitindo a abertura de vários e dispositivos de câmera de forma independente. Como não é uma garantia rígida da Isso requer a realização de testes e ajustes por dispositivo usando tentativa e erro.
Criar uma sessão com várias câmeras físicas
Ao usar câmeras físicas em um dispositivo compatível com várias câmeras, abra uma única
CameraDevice
(a câmera lógica) e interagem com ela em um único
sessão. Criar a única sessão usando a API
CameraDevice.createCaptureSession(SessionConfiguration config)
, que foi
adicionados no nível 28 da API. A configuração da sessão tem diversas saídas
do Terraform, cada uma com um conjunto de destinos de saída e, opcionalmente, um
ID desejado da câmera física.

As solicitações de captura têm um destino de saída associado a elas. A estrutura determina para qual câmera física (ou lógica) as solicitações são enviadas com base qual destino de saída está anexado. Se o destino de saída corresponder a um dos de saída que foram enviados como uma configuração de saída junto com uma ID da câmera, essa câmera física recebe e processa a solicitação.
Usar um par de câmeras físicas
Outra adição às APIs de câmera para várias câmeras é a capacidade de identificar câmeras lógicas e encontrar as câmeras físicas atrás delas. Você pode definir para ajudar a identificar possíveis pares de câmeras físicas que você pode usar para substituir um dos streams lógicos de câmera:
/**
* Helper class used to encapsulate a logical camera and two underlying
* physical cameras
*/
data class DualCamera(val logicalId: String, val physicalId1: String, val physicalId2: String)
fun findDualCameras(manager: CameraManager, facing: Int? = null): List
/**
* Helper class used to encapsulate a logical camera and two underlying
* physical cameras
*/
final class DualCamera {
final String logicalId;
final String physicalId1;
final String physicalId2;
DualCamera(String logicalId, String physicalId1, String physicalId2) {
this.logicalId = logicalId;
this.physicalId1 = physicalId1;
this.physicalId2 = physicalId2;
}
}
List
O manuseio de estado das câmeras físicas é controlado pela câmera lógica. Para abrir uma "câmera dupla". abrir a câmera lógica correspondente ao câmeras:
fun openDualCamera(cameraManager: CameraManager,
dualCamera: DualCamera,
// AsyncTask is deprecated beginning API 30
executor: Executor = AsyncTask.SERIAL_EXECUTOR,
callback: (CameraDevice) -> Unit) {
// openCamera() requires API >= 28
cameraManager.openCamera(
dualCamera.logicalId, executor, object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) = callback(device)
// Omitting for brevity...
override fun onError(device: CameraDevice, error: Int) = onDisconnected(device)
override fun onDisconnected(device: CameraDevice) = device.close()
})
}
void openDualCamera(CameraManager cameraManager,
DualCamera dualCamera,
Executor executor,
CameraDeviceCallback cameraDeviceCallback
) {
// openCamera() requires API >= 28
cameraManager.openCamera(dualCamera.logicalId, executor, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
cameraDeviceCallback.callback(cameraDevice);
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int i) {
onDisconnected(cameraDevice);
}
});
}
Além de selecionar qual câmera abrir, o processo é o mesmo que abrir uma câmera em versões anteriores do Android. Criar uma sessão de captura usando o novo A API de configuração da sessão pede que o framework associe determinados destinos IDs específicos de câmeras físicas:
/**
* Helper type definition that encapsulates 3 sets of output targets:
*
* 1. Logical camera
* 2. First physical camera
* 3. Second physical camera
*/
typealias DualCameraOutputs =
Triple
/**
* Helper class definition that encapsulates 3 sets of output targets:
*
* 1. Logical camera
* 2. First physical camera
* 3. Second physical camera
*/
final class DualCameraOutputs {
private final List
Consulte
createCaptureSession
para saber qual combinação de streams é compatível. Combinar streams
serve para vários streams em uma única câmera lógica. A compatibilidade se estende à
usando a mesma configuração e substituindo um desses streams por dois
de duas câmeras físicas que fazem parte da mesma câmera lógica.
Com o sessão da câmera pronto, envie o pedido capturar solicitações. Cada o destino da solicitação de captura recebe os dados da rede física associada se houver alguma em uso, ou recorrer à câmera lógica.
Exemplo de caso de uso do Zoom
É possível usar a fusão de câmeras físicas em um único stream para que que os usuários possam alternar entre as diferentes câmeras físicas um campo de visão diferente, capturando efetivamente um "nível de zoom" diferente.

Comece selecionando o par de câmeras físicas para permitir que os usuários alternem entre eles. Para obter o efeito máximo, você pode escolher o par de câmeras que fornecem distância focal mínima e máxima disponíveis.
fun findShortLongCameraPair(manager: CameraManager, facing: Int? = null): DualCamera? {
return findDualCameras(manager, facing).map {
val characteristics1 = manager.getCameraCharacteristics(it.physicalId1)
val characteristics2 = manager.getCameraCharacteristics(it.physicalId2)
// Query the focal lengths advertised by each physical camera
val focalLengths1 = characteristics1.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
val focalLengths2 = characteristics2.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
// Compute the largest difference between min and max focal lengths between cameras
val focalLengthsDiff1 = focalLengths2.maxOrNull()!! - focalLengths1.minOrNull()!!
val focalLengthsDiff2 = focalLengths1.maxOrNull()!! - focalLengths2.minOrNull()!!
// Return the pair of camera IDs and the difference between min and max focal lengths
if (focalLengthsDiff1 < focalLengthsDiff2) {
Pair(DualCamera(it.logicalId, it.physicalId1, it.physicalId2), focalLengthsDiff1)
} else {
Pair(DualCamera(it.logicalId, it.physicalId2, it.physicalId1), focalLengthsDiff2)
}
// Return only the pair with the largest difference, or null if no pairs are found
}.maxByOrNull { it.second }?.first
}
// Utility functions to find min/max value in float[]
float findMax(float[] array) {
float max = Float.NEGATIVE_INFINITY;
for(float cur: array)
max = Math.max(max, cur);
return max;
}
float findMin(float[] array) {
float min = Float.NEGATIVE_INFINITY;
for(float cur: array)
min = Math.min(min, cur);
return min;
}
DualCamera findShortLongCameraPair(CameraManager manager, Integer facing) {
return findDualCameras(manager, facing).stream()
.map(c -> {
CameraCharacteristics characteristics1;
CameraCharacteristics characteristics2;
try {
characteristics1 = manager.getCameraCharacteristics(c.physicalId1);
characteristics2 = manager.getCameraCharacteristics(c.physicalId2);
} catch (CameraAccessException e) {
e.printStackTrace();
return null;
}
// Query the focal lengths advertised by each physical camera
float[] focalLengths1 = characteristics1.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
float[] focalLengths2 = characteristics2.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
// Compute the largest difference between min and max focal lengths between cameras
Float focalLengthsDiff1 = findMax(focalLengths2) - findMin(focalLengths1);
Float focalLengthsDiff2 = findMax(focalLengths1) - findMin(focalLengths2);
// Return the pair of camera IDs and the difference between min and max focal lengths
if (focalLengthsDiff1 < focalLengthsDiff2) {
return new Pair<>(new DualCamera(c.logicalId, c.physicalId1, c.physicalId2), focalLengthsDiff1);
} else {
return new Pair<>(new DualCamera(c.logicalId, c.physicalId2, c.physicalId1), focalLengthsDiff2);
}
}) // Return only the pair with the largest difference, or null if no pairs are found
.max(Comparator.comparing(pair -> pair.second)).get().first;
}
Uma arquitetura sensata para isso seria ter dois
SurfaceViews
: uma para cada transmissão.
Esses SurfaceViews
são trocados com base na interação do usuário, de modo que apenas um seja
visíveis a qualquer momento.
O código a seguir mostra como abrir a câmera lógica e configurar a câmera de saída, criar uma sessão de câmera e iniciar dois streams de visualização:
val cameraManager: CameraManager = ...
// Get the two output targets from the activity / fragment
val surface1 = ... // from SurfaceView
val surface2 = ... // from SurfaceView
val dualCamera = findShortLongCameraPair(manager)!!
val outputTargets = DualCameraOutputs(
null, mutableListOf(surface1), mutableListOf(surface2))
// Here you open the logical camera, configure the outputs and create a session
createDualCameraSession(manager, dualCamera, targets = outputTargets) { session ->
// Create a single request which has one target for each physical camera
// NOTE: Each target receive frames from only its associated physical camera
val requestTemplate = CameraDevice.TEMPLATE_PREVIEW
val captureRequest = session.device.createCaptureRequest(requestTemplate).apply {
arrayOf(surface1, surface2).forEach { addTarget(it) }
}.build()
// Set the sticky request for the session and you are done
session.setRepeatingRequest(captureRequest, null, null)
}
CameraManager manager = ...;
// Get the two output targets from the activity / fragment
Surface surface1 = ...; // from SurfaceView
Surface surface2 = ...; // from SurfaceView
DualCamera dualCamera = findShortLongCameraPair(manager, null);
DualCameraOutputs outputTargets = new DualCameraOutputs(
null, Collections.singletonList(surface1), Collections.singletonList(surface2));
// Here you open the logical camera, configure the outputs and create a session
createDualCameraSession(manager, dualCamera, outputTargets, null, (session) -> {
// Create a single request which has one target for each physical camera
// NOTE: Each target receive frames from only its associated physical camera
CaptureRequest.Builder captureRequestBuilder;
try {
captureRequestBuilder = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
Arrays.asList(surface1, surface2).forEach(captureRequestBuilder::addTarget);
// Set the sticky request for the session and you are done
session.setRepeatingRequest(captureRequestBuilder.build(), null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
});
Tudo o que resta a fazer é fornecer uma interface para o usuário alternar entre os dois
superfícies, como um botão ou toque duplo no SurfaceView
. Você pode até
realizar algum tipo de análise de cena e alternar entre os dois fluxos
automaticamente.
Distorção da lente
Todas as lentes produzem uma certa distorção. No Android, é possível consultar
distorção gerada pelas lentes com o uso
CameraCharacteristics.LENS_DISTORTION
,
que substitui a versão descontinuada
CameraCharacteristics.LENS_RADIAL_DISTORTION
Para câmeras lógicas, a distorção é mínima, e o aplicativo pode usar
mais ou menos quadros que vêm da câmera. Para câmeras físicas,
há configurações de lente muito diferentes, especialmente em dispositivos
lentes.
Alguns dispositivos podem implementar a correção automática da distorção por meio de
CaptureRequest.DISTORTION_CORRECTION_MODE
A correção de distorção é ativada por padrão na maioria dos dispositivos.
val cameraSession: CameraCaptureSession = ...
// Use still capture template to build the capture request
val captureRequest = cameraSession.device.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE
)
// Determine if this device supports distortion correction
val characteristics: CameraCharacteristics = ...
val supportsDistortionCorrection = characteristics.get(
CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
)?.contains(
CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
) ?: false
if (supportsDistortionCorrection) {
captureRequest.set(
CaptureRequest.DISTORTION_CORRECTION_MODE,
CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
)
}
// Add output target, set other capture request parameters...
// Dispatch the capture request
cameraSession.capture(captureRequest.build(), ...)
CameraCaptureSession cameraSession = ...;
// Use still capture template to build the capture request
CaptureRequest.Builder captureRequestBuilder = null;
try {
captureRequestBuilder = cameraSession.getDevice().createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
// Determine if this device supports distortion correction
CameraCharacteristics characteristics = ...;
boolean supportsDistortionCorrection = Arrays.stream(
characteristics.get(
CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
))
.anyMatch(i -> i == CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY);
if (supportsDistortionCorrection) {
captureRequestBuilder.set(
CaptureRequest.DISTORTION_CORRECTION_MODE,
CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
);
}
// Add output target, set other capture request parameters...
// Dispatch the capture request
cameraSession.capture(captureRequestBuilder.build(), ...);
Definir uma solicitação de captura nesse modo pode afetar o frame rate que pode ser produzidos pela câmera. Você pode optar por configurar a correção da distorção apenas em de imagens estáticas.