Hinweis: Diese Seite bezieht sich auf das Camera2-Paket. Sofern Ihre App keine speziellen, Low-Level-Funktionen von Camera2 benötigt, empfehlen wir die Verwendung von CameraX. Sowohl CameraX als auch Camera2 unterstützen Android 5.0 (API-Level 21) und höher.
Die Unterstützung mehrerer Kameras wurde mit Android 9 (API-Level 28) eingeführt. Seit der Veröffentlichung sind Geräte auf den Markt gekommen, die die API unterstützen. Viele Anwendungsfälle mit mehreren Kameras sind eng mit einer bestimmten Hardwarekonfiguration verknüpft. Mit anderen Worten: Nicht alle Anwendungsfälle sind mit jedem Gerät kompatibel. Daher sind Funktionen mit mehreren Kameras ein guter Kandidat für die Play Feature Delivery.
Typische Anwendungsfälle:
- Zoom: Je nach Bildausschnitt oder gewählter Brennweite wird zwischen den Kameras gewechselt.
- Tiefe: Mit mehreren Kameras wird eine Tiefenkarte erstellt.
- Bokeh: Mithilfe von abgeleiteten Tiefeninformationen wird eine geringe Schärfentiefe wie bei einer DSLR-Kamera simuliert.
Unterschied zwischen logischen und physischen Kameras
Um die Multi-Kamera API zu verstehen, muss man den Unterschied zwischen logischen und physischen Kameras kennen. Betrachten wir als Referenz ein Gerät mit drei rückwärtsgerichteten Kameras. In diesem Beispiel gilt jede der drei Rückkameras als physische Kamera. Eine logische Kamera ist dann eine Gruppierung von zwei oder mehr dieser physischen Kameras. Die Ausgabe der logischen Kamera kann ein Stream sein, der von einer der zugrunde liegenden physischen Kameras stammt, oder ein zusammengeführter Stream, der von mehreren zugrunde liegenden physischen Kameras gleichzeitig stammt. In beiden Fällen wird der Stream von der Hardwareabstraktionsschicht (Hardware Abstraction Layer, HAL) der Kamera verarbeitet.
Viele Smartphone-Hersteller entwickeln eigene Kamera-Apps, die in der Regel auf ihren Geräten vorinstalliert sind. Um alle Funktionen der Hardware zu nutzen, können sie private oder versteckte APIs verwenden oder eine spezielle Behandlung durch die Treiberimplementierung erhalten, auf die andere Anwendungen keinen Zugriff haben. Einige Geräte implementieren das Konzept logischer Kameras, indem sie einen zusammengeführten Stream von Frames von den verschiedenen physischen Kameras bereitstellen, aber nur für bestimmte privilegierte Anwendungen. Häufig wird nur eine der physischen Kameras für das Framework verfügbar gemacht. Die Situation für Drittanbieter-Entwickler vor Android 9 wird im folgenden Diagramm dargestellt:
Ab Android 9 sind private APIs in Android-Apps nicht mehr zulässig. Da das Framework die Multi-Kamera-Unterstützung enthält, wird in den Best Practices für Android dringend empfohlen, dass Smartphone-Hersteller für alle physischen Kameras, die in dieselbe Richtung zeigen, eine logische Kamera bereitstellen. Folgendes können Drittanbieter-Entwickler auf Geräten mit Android 9 und höher erwarten:
Was die logische Kamera bietet, hängt vollständig von der OEM-Implementierung der Kamera-HAL ab. Beispielsweise implementiert ein Gerät wie Pixel 3 seine logische Kamera so, dass eine der physischen Kameras basierend auf der angeforderten Brennweite und dem Zuschneidebereich ausgewählt wird.
Die Multi-Kamera-API
Die neue API fügt die folgenden neuen Konstanten, Klassen und Methoden hinzu:
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERACameraCharacteristics.getPhysicalCameraIds()CameraCharacteristics.getAvailablePhysicalCameraRequestKeys()CameraDevice.createCaptureSession(SessionConfiguration config)CameraCharacteritics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPEOutputConfigurationundSessionConfiguration
Aufgrund von Änderungen am Android Compatibility Definition Document (CDD) haben Entwickler auch bestimmte Erwartungen an die Multi-Kamera API. Geräte mit Dual-Kameras gab es schon vor Android 9, aber das gleichzeitige Öffnen mehrerer Kameras war mit Ausprobieren verbunden. Unter Android 9 und höher gibt es für die Mehrfachkamera eine Reihe von Regeln, die festlegen, wann ein Paar physischer Kameras geöffnet werden kann, die Teil derselben logischen Kamera sind.
In den meisten Fällen werden auf Geräten mit Android 9 und höher alle physischen Kameras (mit Ausnahme von weniger gängigen Sensortypen wie Infrarot) zusammen mit einer einfacher zu verwendenden logischen Kamera verfügbar gemacht. Für jede Kombination von Streams, die garantiert funktionieren, kann ein Stream, der zu einer logischen Kamera gehört, durch zwei Streams von den zugrunde liegenden physischen Kameras ersetzt werden.
Mehrere Streams gleichzeitig
Unter Mehrere Kamerastreams gleichzeitig verwenden werden die Regeln für die gleichzeitige Verwendung mehrerer Streams in einer einzelnen Kamera beschrieben. Mit einer wichtigen Ausnahme gelten für mehrere Kameras dieselben Regeln.
In CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA wird erläutert, wie ein logischer YUV_420_888- oder RAW-Stream durch zwei physische Streams ersetzt wird. Das bedeutet, dass jeder Stream vom Typ YUV oder RAW durch zwei Streams mit identischem Typ und identischer Größe ersetzt werden kann. Du kannst mit einem Kamerastream der folgenden garantierten Konfiguration für Geräte mit einer Kamera beginnen:
- Stream 1: YUV-Typ,
MAXIMUM-Größe von logischer Kameraid = 0
Auf einem Gerät mit Multi-Kamera-Unterstützung kannst du dann eine Sitzung erstellen, in der dieser logische YUV-Stream durch zwei physische Streams ersetzt wird:
- Stream 1: YUV-Typ, Größe
MAXIMUMvon physischer Kameraid = 1 - Stream 2: YUV-Typ,
MAXIMUM-Größe von physischer Kameraid = 2
Du kannst einen YUV- oder RAW-Stream nur dann durch zwei gleichwertige Streams ersetzen, wenn diese beiden Kameras Teil einer logischen Kameragruppierung sind, die unter CameraCharacteristics.getPhysicalCameraIds() aufgeführt ist.
Die vom Framework bereitgestellten Garantien sind nur das absolute Minimum, das erforderlich ist, um Frames von mehr als einer physischen Kamera gleichzeitig zu erhalten. Zusätzliche Streams werden auf den meisten Geräten unterstützt. Manchmal können sogar mehrere physische Kamerageräte unabhängig voneinander geöffnet werden. Da dies vom Framework nicht garantiert wird, sind dafür Tests und Anpassungen pro Gerät erforderlich.
Sitzung mit mehreren physischen Kameras erstellen
Wenn du physische Kameras auf einem Gerät mit mehreren Kameras verwendest, öffne eine einzelne CameraDevice (die logische Kamera) und interagiere mit ihr innerhalb einer einzelnen Sitzung. Erstelle die einzelne Sitzung mit der API CameraDevice.createCaptureSession(SessionConfiguration config), die in API-Level 28 hinzugefügt wurde. Die Sitzungskonfiguration hat eine Reihe von Ausgabekonfigurationen, die jeweils eine Reihe von Ausgabezielen und optional eine ausgewählte physische Kamera-ID haben.
Erfassungsanfragen ist ein Ausgabeziel zugeordnet. Das Framework bestimmt anhand des angehängten Ausgabeziels, an welche physische (oder logische) Kamera die Anfragen gesendet werden. Wenn das Ausgabeziel einem der Ausgabeziele entspricht, die als Ausgabekonfiguration zusammen mit einer physischen Kamera-ID gesendet wurden, empfängt und verarbeitet diese physische Kamera die Anfrage.
Mit zwei physischen Kameras
Eine weitere Ergänzung der Kamera-APIs für mehrere Kameras ist die Möglichkeit, logische Kameras zu identifizieren und die physischen Kameras dahinter zu finden. Sie können eine Funktion definieren, mit der Sie potenzielle Paare physischer Kameras identifizieren können, die Sie verwenden können, um einen der logischen Kamerastreams zu ersetzen:
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;
}
Die Statusbehandlung der physischen Kameras wird von der logischen Kamera gesteuert. Wenn du eine „Dual-Kamera“ öffnen möchtest, öffne die logische Kamera, die den physischen Kameras entspricht:
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);
}
});
}
Abgesehen von der Auswahl der zu öffnenden Kamera ist der Vorgang derselbe wie beim Öffnen einer Kamera in früheren Android-Versionen. Wenn Sie eine Aufnahmesitzung mit der neuen API für die Sitzungskonfiguration erstellen, wird das Framework angewiesen, bestimmte Ziele mit bestimmten physischen Kamera-IDs zu verknüpfen:
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));
}
Informationen dazu, welche Kombination von Streams unterstützt wird, finden Sie unter createCaptureSession. Das Kombinieren von Streams ist für mehrere Streams auf einer einzelnen logischen Kamera vorgesehen. Die Kompatibilität erstreckt sich auch auf die Verwendung derselben Konfiguration und das Ersetzen eines dieser Streams durch zwei Streams von zwei physischen Kameras, die Teil derselben logischen Kamera sind.
Wenn die Kamerasitzung bereit ist, senden Sie die ausgewählten Aufnahmeanfragen. Jedes Ziel der Erfassungsanfrage empfängt seine Daten von der zugehörigen physischen Kamera, sofern eine verwendet wird, oder greift auf die logische Kamera zurück.
Beispiel für einen Anwendungsfall mit Zoom
Es ist möglich, physische Kameras in einem einzigen Stream zusammenzuführen, sodass Nutzer zwischen den verschiedenen physischen Kameras wechseln können, um ein anderes Sichtfeld zu erhalten und so effektiv eine andere „Zoomstufe“ zu erfassen.
Wählen Sie zuerst das Paar physischer Kameras aus, zwischen denen Nutzer wechseln können sollen. Für eine maximale Wirkung kannst du das Kamerapaar auswählen, das die minimale und maximale verfügbare Brennweite bietet.
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;
}
Eine sinnvolle Architektur dafür wären zwei SurfaceViews, also eine für jeden Stream. Diese SurfaceViews werden je nach Nutzerinteraktion ausgetauscht, sodass immer nur eines sichtbar ist.
Der folgende Code zeigt, wie Sie die logische Kamera öffnen, die Kameraausgaben konfigurieren, eine Kamerasitzung erstellen und zwei Vorschaustreams starten:
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();
}
});
Jetzt musst du nur noch eine Benutzeroberfläche bereitstellen, über die der Nutzer zwischen den beiden Oberflächen wechseln kann, z. B. eine Schaltfläche oder ein Doppeltippen auf das SurfaceView. Sie könnten sogar eine Art Szenenanalyse durchführen und automatisch zwischen den beiden Streams wechseln.
Objektivverzerrung
Alle Objektive erzeugen ein gewisses Maß an Verzerrung. In Android können Sie die durch Objektive verursachte Verzerrung mit CameraCharacteristics.LENS_DISTORTION abfragen. Dadurch wird das jetzt verworfene CameraCharacteristics.LENS_RADIAL_DISTORTION ersetzt. Bei logischen Kameras ist die Verzerrung minimal und deine Anwendung kann die Frames mehr oder weniger so verwenden, wie sie von der Kamera kommen. Bei physischen Kameras gibt es potenziell sehr unterschiedliche Objektivkonfigurationen, insbesondere bei Weitwinkelobjektiven.
Einige Geräte implementieren möglicherweise eine automatische Verzeichnungskorrektur mit CaptureRequest.DISTORTION_CORRECTION_MODE.
Die Verzeichnungskorrektur ist auf den meisten Geräten standardmäßig aktiviert.
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(), ...);
Wenn du in diesem Modus eine Aufnahme anforderst, kann sich das auf die Framerate auswirken, die von der Kamera erzeugt werden kann. Sie können die Verzeichnungskorrektur auch nur für Standbilder festlegen.