Observação: esta página se refere ao pacote Camera2. A menos que seu app exija recursos específicos 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.
A multicâmera foi introduzida no Android 9 (nível 28 da API). Desde o lançamento, dispositivos que oferecem suporte à API chegaram ao mercado. Muitos casos de uso de várias câmeras estão com acoplamento rígido a uma configuração de hardware específica. Em outras palavras, nem todos os casos de uso são compatíveis com todos os dispositivos, o que torna os recursos de várias câmeras um bom candidato para o Play Feature Delivery.
Veja alguns casos de uso comuns:
- Zoom: alternar entre câmeras dependendo da região de corte ou da distância focal escolhida.
- Profundidade: usa várias câmeras para criar um mapa de profundidade.
- Bokeh: usa informações de profundidade inferidas para simular um alcance de foco estreito, como o de uma DSLR.
A diferença entre câmeras lógicas e físicas
Para entender a API de várias câmeras, é preciso entender a diferença entre câmeras lógicas e físicas. Como 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 duas ou mais dessas câmeras físicas. A saída da câmera lógica pode ser um stream que vem de uma das câmeras físicas subjacentes ou um stream combinado que vem de mais de uma câmera física subjacente simultaneamente. De qualquer forma, o stream é processado pela camada de abstração de hardware (HAL) da câmera.
Muitos fabricantes de smartphones desenvolvem aplicativos de câmera próprios, que geralmente vêm pré-instalados nos dispositivos. Para usar todos os recursos do hardware, eles podem usar APIs particulares ou ocultas ou receber tratamento especial da implementação do driver, que outros aplicativos não podem acessar. Alguns dispositivos implementam o conceito de câmeras lógicas fornecendo um stream de frames combinados das diferentes câmeras físicas, mas apenas para determinados aplicativos privilegiados. Muitas vezes, apenas uma das câmeras físicas é exposta ao framework. A situação dos desenvolvedores terceirizados antes do Android 9 é ilustrada no diagrama a seguir:
A partir do Android 9, as APIs particulares não são mais permitidas em apps Android. Com a inclusão do suporte a várias câmeras no framework, as práticas recomendadas do Android sugerem 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 o que os desenvolvedores terceirizados podem esperar em dispositivos com o Android 9 e versões mais recentes:
O que a câmera lógica oferece depende inteiramente da implementação do OEM da HAL da câmera. Por exemplo, um dispositivo como o Pixel 3 implementa a câmera lógica de modo que ela escolhe uma das câmeras físicas com base na distância focal e na região de corte solicitadas.
A API Multi-Camera
A nova API adiciona as seguintes constantes, classes e métodos:
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERACameraCharacteristics.getPhysicalCameraIds()CameraCharacteristics.getAvailablePhysicalCameraRequestKeys()CameraDevice.createCaptureSession(SessionConfiguration config)CameraCharacteritics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPEOutputConfigurationeSessionConfiguration
Devido a mudanças no Documento de definição de compatibilidade do Android (CDD, na sigla em inglês), a API de várias câmeras também vem com certas expectativas dos desenvolvedores. Dispositivos com câmeras duplas já existiam antes do Android 9, mas abrir mais de uma câmera simultaneamente envolvia tentativa e erro. No Android 9 e versões mais recentes, o recurso de várias câmeras oferece um conjunto de regras para especificar quando é possível abrir um par de câmeras físicas que fazem parte da mesma câmera lógica.
Na maioria dos casos, os dispositivos com o Android 9 e versões mais recentes expõem todas as câmeras físicas (exceto, possivelmente, para tipos de sensor menos comuns, como infravermelho) com uma câmera lógica mais fácil de usar. Para cada combinação de streams que funcionam, um stream pertencente a uma câmera lógica pode ser substituído por dois streams das câmeras físicas subjacentes.
Várias transmissões simultâneas
Como usar vários streams da câmera simultaneamente aborda as regras para usar vários streams simultaneamente em uma única câmera. Com uma
adição importante, as mesmas regras se aplicam a várias câmeras.
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
explica como substituir um stream lógico YUV_420_888 ou bruto por dois streams
físicos. Ou seja, cada stream do tipo YUV ou RAW pode ser substituído por dois
streams de tipo e tamanho idênticos. Você pode começar com um stream da câmera da
seguinte configuração garantida para dispositivos de câmera única:
- Stream 1: tipo YUV, tamanho
MAXIMUMda câmera lógicaid = 0
Em seguida, um dispositivo com suporte a várias câmeras permite criar uma sessão substituindo esse stream YUV lógico por dois streams físicos:
- Stream 1: tipo YUV, tamanho
MAXIMUMda câmera físicaid = 1 - Stream 2: tipo YUV, tamanho
MAXIMUMda câmera físicaid = 2
É possível substituir um stream YUV ou RAW por dois streams equivalentes se, e somente se,
essas duas câmeras fizerem parte de um agrupamento de câmeras lógicas, listado em
CameraCharacteristics.getPhysicalCameraIds().
As garantias fornecidas pelo framework são apenas o mínimo necessário para receber frames de mais de uma câmera física simultaneamente. A maioria dos dispositivos é compatível com outros streams, às vezes até permitindo a abertura de vários dispositivos de câmera física de forma independente. Como não é uma garantia absoluta do framework, isso exige testes e ajustes por dispositivo usando tentativa e erro.
Como criar uma sessão com várias câmeras físicas
Ao usar câmeras físicas em um dispositivo com várias câmeras, abra uma única
CameraDevice (a câmera lógica) e interaja com ela em uma única
sessão. Crie a sessão única usando a API
CameraDevice.createCaptureSession(SessionConfiguration config), que foi
adicionada no nível da API 28. A configuração da sessão tem várias configurações de saída, cada uma com um conjunto de destinos de saída e, opcionalmente, um
ID de câmera física escolhido.
As solicitações de captura têm um destino de saída associado a elas. O framework determina para qual câmera física (ou lógica) as solicitações são enviadas com base no destino de saída anexado. Se o destino de saída corresponder a um dos destinos de saída enviados como uma configuração de saída com um ID de câmera física, essa câmera física vai receber e processar 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 por trás delas. É possível definir uma função para ajudar a identificar possíveis pares de câmeras físicas que podem ser usados para substituir um dos streams da câmera lógica:
Kotlin
/**
* 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<DualCamera> {
val dualCameras = MutableList<DualCamera>()
// Iterate over all the available camera characteristics
manager.cameraIdList.map {
Pair(manager.getCameraCharacteristics(it), it)
}.filter {
// Filter by cameras facing the requested direction
facing == null || it.first.get(CameraCharacteristics.LENS_FACING) == facing
}.filter {
// Filter by logical cameras
// CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
it.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)!!.contains(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)
}.forEach {
// All possible pairs from the list of physical cameras are valid results
// NOTE: There could be N physical cameras as part of a logical camera grouping
// getPhysicalCameraIds() requires API >= 28
val physicalCameras = it.first.physicalCameraIds.toTypedArray()
for (idx1 in 0 until physicalCameras.size) {
for (idx2 in (idx1 + 1) until physicalCameras.size) {
dualCameras.add(DualCamera(
it.second, physicalCameras[idx1], physicalCameras[idx2]))
}
}
}
return dualCameras
}
Java
/**
* 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<DualCamera> findDualCameras(CameraManager manager, Integer facing) {
List<DualCamera> dualCameras = new ArrayList<>();
List<String> cameraIdList;
try {
cameraIdList = Arrays.asList(manager.getCameraIdList());
} catch (CameraAccessException e) {
e.printStackTrace();
cameraIdList = new ArrayList<>();
}
// Iterate over all the available camera characteristics
cameraIdList.stream()
.map(id -> {
try {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
return new Pair<>(characteristics, id);
} catch (CameraAccessException e) {
e.printStackTrace();
return null;
}
})
.filter(pair -> {
// Filter by cameras facing the requested direction
return (pair != null) &&
(facing == null || pair.first.get(CameraCharacteristics.LENS_FACING).equals(facing));
})
.filter(pair -> {
// Filter by logical cameras
// CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
IntPredicate logicalMultiCameraPred =
arg -> arg == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
return Arrays.stream(pair.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES))
.anyMatch(logicalMultiCameraPred);
})
.forEach(pair -> {
// All possible pairs from the list of physical cameras are valid results
// NOTE: There could be N physical cameras as part of a logical camera grouping
// getPhysicalCameraIds() requires API >= 28
String[] physicalCameras = pair.first.getPhysicalCameraIds().toArray(new String[0]);
for (int idx1 = 0; idx1 < physicalCameras.length; idx1++) {
for (int idx2 = idx1 + 1; idx2 < physicalCameras.length; idx2++) {
dualCameras.add(
new DualCamera(pair.second, physicalCameras[idx1], physicalCameras[idx2]));
}
}
});
return dualCameras;
}
O processamento de estado das câmeras físicas é controlado pela câmera lógica. Para abrir uma "câmera dupla", abra a câmera lógica correspondente às câmeras físicas:
Kotlin
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()
})
}
Java
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. A criação de uma sessão de captura usando a nova API de configuração de sessão informa ao framework para associar determinados destinos a IDs de câmera física específicos:
Kotlin
/**
* Helper type definition that encapsulates 3 sets of output targets:
*
* 1. Logical camera
* 2. First physical camera
* 3. Second physical camera
*/
typealias DualCameraOutputs =
Triple<MutableList<Surface>?, MutableList<Surface>?, MutableList<Surface>?>
fun createDualCameraSession(cameraManager: CameraManager,
dualCamera: DualCamera,
targets: DualCameraOutputs,
// AsyncTask is deprecated beginning API 30
executor: Executor = AsyncTask.SERIAL_EXECUTOR,
callback: (CameraCaptureSession) -> Unit) {
// Create 3 sets of output configurations: one for the logical camera, and
// one for each of the physical cameras.
val outputConfigsLogical = targets.first?.map { OutputConfiguration(it) }
val outputConfigsPhysical1 = targets.second?.map {
OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId1) } }
val outputConfigsPhysical2 = targets.third?.map {
OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId2) } }
// Put all the output configurations into a single flat array
val outputConfigsAll = arrayOf(
outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2)
.filterNotNull().flatMap { it }
// Instantiate a session configuration that can be used to create a session
val sessionConfiguration = SessionConfiguration(
SessionConfiguration.SESSION_REGULAR,
outputConfigsAll, executor, object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) = callback(session)
// Omitting for brevity...
override fun onConfigureFailed(session: CameraCaptureSession) = session.device.close()
})
// Open the logical camera using the previously defined function
openDualCamera(cameraManager, dualCamera, executor = executor) {
// Finally create the session and return using the callback
it.createCaptureSession(sessionConfiguration)
}
}
Java
/**
* Helper class definition that encapsulates 3 sets of output targets:
* <p>
* 1. Logical camera
* 2. First physical camera
* 3. Second physical camera
*/
final class DualCameraOutputs {
private final List<Surface> logicalCamera;
private final List<Surface> firstPhysicalCamera;
private final List<Surface> secondPhysicalCamera;
public DualCameraOutputs(List<Surface> logicalCamera, List<Surface> firstPhysicalCamera, List<Surface> third) {
this.logicalCamera = logicalCamera;
this.firstPhysicalCamera = firstPhysicalCamera;
this.secondPhysicalCamera = third;
}
public List<Surface> getLogicalCamera() {
return logicalCamera;
}
public List<Surface> getFirstPhysicalCamera() {
return firstPhysicalCamera;
}
public List<Surface> getSecondPhysicalCamera() {
return secondPhysicalCamera;
}
}
interface CameraCaptureSessionCallback {
void callback(CameraCaptureSession cameraCaptureSession);
}
void createDualCameraSession(CameraManager cameraManager,
DualCamera dualCamera,
DualCameraOutputs targets,
Executor executor,
CameraCaptureSessionCallback cameraCaptureSessionCallback) {
// Create 3 sets of output configurations: one for the logical camera, and
// one for each of the physical cameras.
List<OutputConfiguration> outputConfigsLogical = targets.getLogicalCamera().stream()
.map(OutputConfiguration::new)
.collect(Collectors.toList());
List<OutputConfiguration> outputConfigsPhysical1 = targets.getFirstPhysicalCamera().stream()
.map(s -> {
OutputConfiguration outputConfiguration = new OutputConfiguration(s);
outputConfiguration.setPhysicalCameraId(dualCamera.physicalId1);
return outputConfiguration;
})
.collect(Collectors.toList());
List<OutputConfiguration> outputConfigsPhysical2 = targets.getSecondPhysicalCamera().stream()
.map(s -> {
OutputConfiguration outputConfiguration = new OutputConfiguration(s);
outputConfiguration.setPhysicalCameraId(dualCamera.physicalId2);
return outputConfiguration;
})
.collect(Collectors.toList());
// Put all the output configurations into a single flat array
List<OutputConfiguration> outputConfigsAll = Stream.of(
outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2
)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toList());
// Instantiate a session configuration that can be used to create a session
SessionConfiguration sessionConfiguration = new SessionConfiguration(
SessionConfiguration.SESSION_REGULAR,
outputConfigsAll, executor, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
cameraCaptureSessionCallback.callback(cameraCaptureSession);
}
// Omitting for brevity...
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
cameraCaptureSession.getDevice().close();
}
});
// Open the logical camera using the previously defined function
openDualCamera(cameraManager, dualCamera, executor, (CameraDevice c) ->
// Finally create the session and return using the callback
c.createCaptureSession(sessionConfiguration));
}
Consulte createCaptureSession para saber quais
combinações de streams são aceitas. A combinação de streams é para vários streams
em uma única câmera lógica. A compatibilidade se estende ao uso da mesma
configuração e à substituição de um desses streams por dois streams de duas
câmeras físicas que fazem parte da mesma câmera lógica.
Com a sessão da câmera pronta, envie as solicitações de captura escolhidas. Cada destino da solicitação de captura recebe os dados da câmera física associada, se houver alguma em uso, ou volta para a câmera lógica.
Exemplo de caso de uso de zoom
É possível mesclar as câmeras físicas em um único stream para que os usuários possam alternar entre elas e ter um campo de visão diferente, capturando um "nível de zoom" diferente.
Comece selecionando o par de câmeras físicas que os usuários poderão alternar. Para um efeito máximo, escolha o par de câmeras que oferece a distância focal mínima e máxima disponível.
Kotlin
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
}
Java
// 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, um para cada stream. Essas SurfaceViews são
trocadas com base na interação do usuário para que apenas uma fique visível por vez.
O código a seguir mostra como abrir a câmera lógica, configurar as saídas, criar uma sessão de câmera e iniciar dois streams de pré-lançamento:
Kotlin
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)
}
Java
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();
}
});
Agora, basta fornecer uma interface para o usuário alternar entre as duas
superfícies, como um botão ou tocar duas vezes no SurfaceView. Você pode até mesmo
realizar algum tipo de análise de cena e alternar entre os dois streams
automaticamente.
Distorção da lente
Todas as lentes produzem uma certa quantidade de distorção. No Android, é possível consultar a
distorção criada pelas lentes usando
CameraCharacteristics.LENS_DISTORTION, que substitui o
CameraCharacteristics.LENS_RADIAL_DISTORTION,
agora descontinuado. Para
câmeras lógicas, a distorção é mínima, e o aplicativo pode usar os
frames mais ou menos como eles vêm da câmera. Para câmeras físicas, pode
haver configurações de lente muito diferentes, especialmente em lentes grande angular.
Alguns dispositivos podem implementar a correção da distorção automática usando
CaptureRequest.DISTORTION_CORRECTION_MODE.
A correção da distorção fica ativada por padrão na maioria dos dispositivos.
Kotlin
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(), ...)
Java
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 a taxa de frames que pode ser produzida pela câmera. Você pode definir a correção da distorção apenas para capturas de imagens estáticas.