Nota: questa pagina si riferisce al pacchetto Camera2. A meno che la tua app non richieda funzionalità specifiche di basso livello di Camera2, ti consigliamo di usare CameraX. Sia CameraX che Camera2 supportano Android 5.0 (livello API 21) e versioni successive.
La funzionalità multicamera è stata introdotta con Android 9 (livello API 28). Dalla sua uscita, sul mercato vengono lanciati dispositivi che supportano l'API. Molti casi d'uso multicamera sono strettamente legate a una specifica configurazione hardware. In altre parole, non tutti i casi d'uso sono compatibili con tutti i dispositivi, il che rende la fotocamera multicamera presenta un buon candidato per la funzionalità di Google Play Consegna.
Alcuni casi d'uso tipici includono:
- Zoom: passaggio da una fotocamera all'altra in base all'area ritagliata o alla focale desiderata lunghezza.
- Profondità: utilizzo di più fotocamere per creare una mappa di profondità.
- Bokeh: uso di informazioni sulla profondità dedotti per simulare un corpus restringente simile a una fotocamera DSLR intervallo di messa a fuoco.
La differenza tra fotocamere logiche e fisiche
Comprendere l'API multi-camera richiede di comprendere la differenza tra fotocamere logiche e fisiche. Come riferimento, consideriamo un dispositivo con tre fotocamere posteriori. In questo esempio, ciascuna delle tre fotocamere posteriori considerata una fotocamera fisica. Una videocamera logica è quindi un raggruppamento di due o più di queste fotocamere fisiche. L'output del comando videocamera può essere uno stream proveniente da una delle fotocamere fisiche sottostanti o uno stream confuso proveniente da più di una videocamera fisica sottostante contemporaneamente. In ogni caso, lo stream viene gestito dall'hardware della videocamera Abstraction Layer (HAL).
Molti produttori di telefoni sviluppano applicazioni proprietarie per le fotocamere, che in genere sono preinstallati sui loro dispositivi. Per utilizzare tutte le funzionalità dell'hardware, possono utilizzare API private o nascoste o ricevere un trattamento speciale l'implementazione del driver a cui le altre applicazioni non hanno accesso. Alcune dispositivi implementano il concetto di telecamere logiche fornendo un flusso confuso fotogrammi da diverse fotocamere fisiche, ma solo per alcuni privilegi diverse applicazioni. Spesso, solo una delle fotocamere fisiche è esposta al il modello di machine learning. La situazione per gli sviluppatori di terze parti precedenti ad Android 9 è illustrato nel seguente diagramma:

A partire da Android 9, le API private non sono più consentite nelle app per Android. Con l'inclusione del supporto multicamera nel framework, Android ha consigliamo vivamente ai produttori di telefoni di esporre una fotocamera logica di tutte le fotocamere fisiche rivolte nella stessa direzione. Di seguito viene illustrato cosa gli sviluppatori di terze parti dovrebbero aspettarsi di trovare dispositivi con Android 9 e superiore:

Ciò che fornisce la fotocamera logica dipende interamente dall'implementazione dell'OEM di Camera HAL. Ad esempio, un dispositivo come Pixel 3 implementa la sua logica videocamera in modo tale da scegliere una delle proprie fotocamere fisiche in base alla la lunghezza focale e l'area di ritaglio richieste.
API multi-camera
La nuova API aggiunge le nuove costanti, classi e metodi seguenti:
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
CameraCharacteristics.getPhysicalCameraIds()
CameraCharacteristics.getAvailablePhysicalCameraRequestKeys()
CameraDevice.createCaptureSession(SessionConfiguration config)
CameraCharacteritics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE
OutputConfiguration
eSessionConfiguration
A causa delle modifiche al Compatibility Definition Document (CDD) di Android, la l'API multi-camera soddisfa anche alcune aspettative degli sviluppatori. Dispositivi con doppia fotocamera esisteva prima di Android 9, ma l'apertura di più di una fotocamera contemporaneamente tentativi ed errori. Su Android 9 e versioni successive, modalità multicamera offre un insieme di regole per specificare quando è possibile aprire che fanno parte della stessa videocamera logica.
Nella maggior parte dei casi, i dispositivi con Android 9 e versioni successive espongono tutte le informazioni fisiche (ad eccezione dei sensori meno comuni come gli infrarossi) insieme una fotocamera logica più facile da usare. Per ogni combinazione di stream funzionare, uno stream appartenente a una videocamera logica può essere sostituito due stream provenienti dalle videocamere fisiche sottostanti.
Stream multipli contemporaneamente
Utilizzare più videocamere in streaming contemporaneamente
copre le regole per l'utilizzo di più stream contemporaneamente in una singola videocamera.
Con un'aggiunta degna di nota, le stesse regole si applicano a più videocamere.
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
spiega come sostituire uno stream logico YUV_420_888 o uno stream non elaborato con due
in streaming fisici. Ciò significa che ogni flusso di tipo YUV o RAW può essere sostituito con
due flussi di dimensioni e tipo identici. Puoi iniziare con uno stream della videocamera
la seguente configurazione garantita per i dispositivi con una sola videocamera:
- Stream 1: tipo YUV,
MAXIMUM
dimensione dalla fotocamera logicaid = 0
Quindi, un dispositivo con supporto multicamera ti consente di creare una sessione sostituendo lo stream logico YUV con due flussi fisici:
- Stream 1: tipo YUV,
MAXIMUM
dimensione dalla videocamera fisicaid = 1
- Stream 2: tipo YUV,
MAXIMUM
dimensione dalla videocamera fisicaid = 2
È possibile sostituire uno stream YUV o RAW con due stream equivalenti se e solo se
queste due videocamere fanno parte di un raggruppamento logico delle videocamere,
CameraCharacteristics.getPhysicalCameraIds()
Le garanzie fornite dal framework sono solo il minimo indispensabile per ottenere fotogrammi da più di una fotocamera fisica contemporaneamente. Stream aggiuntivi sono supportati nella maggior parte dei dispositivi, a volte consente anche l'apertura di più fotocamere in modo indipendente. Poiché non è una garanzia rigida dal di Google Cloud, la procedura richiede l'esecuzione di test e ottimizzazioni per dispositivo utilizzando per tentativi.
Creazione di una sessione con più videocamere fisiche
Se utilizzi fotocamere fisiche su un dispositivo con più fotocamere, apri un singolo
CameraDevice
(la fotocamera logica) e interagire con quest'ultima all'interno di una singola
durante la sessione. crea la sessione singola utilizzando l'API
CameraDevice.createCaptureSession(SessionConfiguration config)
, che è
aggiunta al livello API 28. La configurazione della sessione ha una serie
configurazioni, ciascuna delle quali ha un insieme di target di output e, facoltativamente, un
l'ID fisico desiderato della fotocamera.

Alle richieste di acquisizione è associata una destinazione di output. Il framework determina la videocamera fisica (o logica) a cui vengono inviate le richieste in base la destinazione di output collegata. Se il target di output corrisponde a uno dei target di output inviati come configurazione di output insieme a una dell'ID videocamera, la videocamera fisica riceve ed elabora la richiesta.
Con un paio di fotocamere fisiche
Un'altra aggiunta alle API per videocamere per la modalità multicamera è la capacità di identificare fotocamere logiche e trovare quelle fisiche alle loro spalle. Puoi definire per identificare le potenziali coppie di fotocamere fisiche che puoi utilizzare per sostituire uno degli stream logici della videocamera:
/**
* 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
La gestione dello stato delle telecamere fisiche è controllata dalla fotocamera logica. A apri una "doppia fotocamera" aprire la fotocamera logica corrispondente al videocamere:
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);
}
});
}
Oltre a selezionare la videocamera da aprire, la procedura è la stessa dell'apertura una videocamera nelle versioni precedenti di Android. Creazione di una sessione di acquisizione utilizzando il nuovo l'API di configurazione della sessione indica al framework a cui associare determinati target ID fotocamera fisica specifici:
/**
* 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
Consulta
createCaptureSession
per informazioni sulla combinazione di stream supportata. Combinazione di flussi
è per più flussi su una singola videocamera logica. La compatibilità si estende a
utilizzando la stessa configurazione sostituendo uno di questi stream con due stream
da due fotocamere fisiche che fanno parte della stessa fotocamera logica.
Con sessione con la videocamera pronto, invia richieste di acquisizione. Ciascuna della richiesta di acquisizione riceve i dati dal suo indirizzo fisico videocamera logica, se sono in uso.
Esempio di caso d'uso di Zoom
È possibile utilizzare l'unione di videocamere fisiche in un unico stream in modo che che gli utenti possono passare da una fotocamera fisica all'altra per sperimentare un campo visivo diverso, consentendo di acquisire in modo efficace un diverso "livello di zoom".

Inizia selezionando la coppia di fotocamere fisiche per consentire agli utenti di effettuare il passaggio tra di loro. Per ottenere il massimo effetto, puoi scegliere la coppia di videocamere che forniscono la lunghezza focale minima e massima disponibile.
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;
}
Un'architettura sensata per farlo sarebbe avere due
SurfaceViews
: uno per ogni stream.
Questi SurfaceViews
vengono scambiati in base all'interazione dell'utente, in modo che solo uno sia
visibili in qualsiasi momento.
Il seguente codice mostra come aprire la fotocamera logica e configurare la fotocamera crea una sessione di videocamera e avvia due stream di anteprima:
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();
}
});
Tutto ciò che devi fare è fornire all'utente un'interfaccia utente per passare da uno all'altro
come un pulsante o il doppio tocco di SurfaceView
. Potresti persino
eseguire un'analisi della scena e passare da un flusso all'altro
automaticamente.
Distorsione dell'obiettivo
Tutti gli obiettivi producono una certa quantità di distorsione. In Android, puoi eseguire query
distorsione creata da obiettivi utilizzando
CameraCharacteristics.LENS_DISTORTION
,
che sostituisce l'ormai deprecato
CameraCharacteristics.LENS_RADIAL_DISTORTION
.
Per le fotocamere logiche, la distorsione è minima e l'applicazione può utilizzare
i fotogrammi più o meno come provengono dalla fotocamera. Per le fotocamere fisiche
ci sono configurazioni di obiettivi potenzialmente molto diverse, soprattutto per i modelli grandangolare
lenti.
Alcuni dispositivi possono implementare la correzione automatica delle distorsioni tramite
CaptureRequest.DISTORTION_CORRECTION_MODE
Per impostazione predefinita, la correzione della distorsione è attiva per la maggior parte dei dispositivi.
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(), ...);
L'impostazione di una richiesta di acquisizione in questa modalità può influire sulla frequenza fotogrammi prodotto dalla fotocamera. Puoi scegliere di impostare la correzione delle distorsioni solo su acquisizioni di immagini statiche.