API für mehrere Kameras

Hinweis:Diese Seite bezieht sich auf das Camera2-Paket. Sofern für Ihre App keine spezifischen Low-Level-Funktionen von Camera2 erforderlich sind, empfehlen wir die Verwendung von CameraX. Sowohl CameraX als auch Camera2 unterstützen Android 5.0 (API-Level 21) und höher.

Die Verwendung mehrerer Kameras wurde mit Android 9 (API-Level 28) eingeführt. Seit der Veröffentlichung Geräte auf den Markt gebracht werden, die die API unterstützen. Viele Anwendungsfälle mit mehreren Kameras eng mit einer bestimmten Hardwarekonfiguration gekoppelt. Mit anderen Worten: Alle Anwendungsfälle sind mit jedem Gerät kompatibel,  sodass mehrere Kameras eine gute Wahl für die Wiedergabefunktion ist. Auslieferung:

Hier einige typische Anwendungsfälle:

  • Zoom: Schaltet je nach Zuschnittbereich oder gewünschtem Fokus zwischen den Kameras um. Länge.
  • Tiefe: Erstellung einer Tiefenkarte durch mehrere Kameras
  • Bokeh: Die Verwendung abgeleiteter Tiefeninformationen zur Simulation einer schmalen Spiegelreflexkamera (Spiegelreflexkamera) Fokusbereich

Der Unterschied zwischen logischen und physischen Kameras

Um das API für mehrere Kameras zu verstehen, müssen Sie den Unterschied zwischen logische und physische Kameras. Betrachten wir ein Gerät mit drei rückseitigen Kameras. In diesem Beispiel ist jede der drei Rückkameras als physische Kamera betrachtet. Eine logische Kamera ist eine Gruppierung aus zwei oder mehr dieser physischen Kameras. Die Ausgabe der logischen Kamera kann ein Stream von einer der zugrunde liegenden physischen Kameras sein. oder ein zusammengeführter Strom von mehr als einer zugrunde liegenden physischen Kamera gleichzeitig. In beiden Fällen wird der Stream von der Kamera-Hardware verarbeitet. Abstraktionsschicht (HAL).

Viele Smartphone-Hersteller entwickeln eigene Kamera-Apps, auf ihren Geräten vorinstalliert sind. Um alle Funktionen der Hardware zu nutzen, private oder versteckte APIs nutzen oder gesondert behandelt werden, der Treiberimplementierung, auf die andere Anwendungen keinen Zugriff haben. Einige Geräte implementieren das Konzept logischer Kameras, indem sie den verschiedenen physischen Kameras, sondern nur von bestimmten privilegierten Geräten Anwendungen. Häufig ist nur eine der physischen Kameras mit dem Framework. Die Situation für Drittanbieter-Entwickler vor Android 9 wie in der folgenden Abbildung dargestellt:

<ph type="x-smartling-placeholder">
</ph>
Abbildung 1. Die Kamerafunktionen sind normalerweise nur privilegierte Anwendungen

Ab Android 9 sind private APIs in Android-Apps nicht mehr zulässig. Durch die Unterstützung mehrerer Kameras im Framework ist Android Smartphone-Herstellern unbedingt eine logische Kamera für alle physischen Kameras, die in die gleiche Richtung zeigen. Im Folgenden finden Sie Drittanbieter-Entwickler auf Geräten mit Android 9 und höher:

<ph type="x-smartling-placeholder">
</ph>
Abbildung 2. Vollständiger Entwicklerzugriff auf alle Kamerageräte ab Android 9

Was die logische Kamera bietet, hängt vollständig von der OEM-Implementierung ab. der Kamera-HAL. Beispielsweise implementiert ein Gerät wie Pixel 3 seine logische Kamera so konfigurieren, dass eine ihrer physischen Kameras anhand der die angeforderte Brennweite und Zuschneidebereich.

API für mehrere Kameras

Mit der neuen API werden die folgenden neuen Konstanten, Klassen und Methoden hinzugefügt:

Aufgrund von Änderungen am CDD (Android Compatibility Definition Document) Die Multi-Kamera-API bringt auch bestimmte Erwartungen von Entwicklern mit sich. Geräte mit Dual-Kameras gab es schon vor Android 9. Es wurden aber mehr als eine Kamera geöffnet. gleichzeitig Trial-and-Error-Methode. Unter Android 9 und höher: mehrere Kameras gibt eine Reihe von Regeln an, mit denen angegeben wird, wann ein Paar physischer Kameras, die zur selben logischen Kamera gehören.

In den meisten Fällen sind Geräte mit Android 9 und höher Kameras (mit Ausnahme weniger üblicher Sensortypen wie Infrarot) und eine benutzerfreundlichere logische Kamera. Für alle Arten von Streams, die funktioniert, kann ein Stream einer logischen Kamera durch einen zwei Streams von den zugrunde liegenden physischen Kameras.

Mehrere Streams gleichzeitig

Mehrere Kamerastreams gleichzeitig verwenden behandelt die Regeln für die gleichzeitige Verwendung mehrerer Streams in einer einzelnen Kamera. Es gibt jedoch eine weitere wichtige Neuerung: Für mehrere Kameras gelten dieselben Regeln. CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA wird erläutert, wie ein logischer YUV_420_888 oder ein Rohstream durch zwei in physischen Streams. Das heißt, jeder Stream des Typs YUV oder RAW kann durch zwei Streams identischem Typ und identischer Größe. Sie können mit einem Kamerastream von folgende garantierte Konfiguration für Geräte mit einer Kamera:

  • Stream 1: YUV-Typ, MAXIMUM-Größe von logischer Kamera id = 0

Dann können Sie mit einem Gerät, das mehrere Kameras unterstützt, eine Sitzung erstellen. Dabei wird der logische YUV-Stream durch zwei physische Streams ersetzt:

  • Stream 1: YUV-Typ, MAXIMUM-Größe der physischen Kamera id = 1
  • Stream 2: YUV-Typ, MAXIMUM-Größe der physischen Kamera id = 2

Du kannst einen YUV- oder RAW-Stream nur dann durch zwei gleichwertige Streams ersetzen, wenn sind diese beiden Kameras Teil einer logischen Kameragruppe,  die unter CameraCharacteristics.getPhysicalCameraIds()

Die vom Framework gebotenen Garantien sind nur das absolute Minimum, das für Bilder von mehr als einer physischen Kamera gleichzeitig erhalten. Zusätzliche Streams werden auf den meisten Geräten unterstützt, manchmal können sogar mehrere physische Kamerageräte unabhängig voneinander ab. Da dies keine feste Garantie Framework. Dazu müssen Sie gerätespezifische Tests und Feinabstimmungen mit durch das Ausprobieren von Versuch und Irrtum.

Sitzung mit mehreren physischen Kameras erstellen

Wenn Sie physische Kameras an einem Gerät verwenden, das mehrere Kameras unterstützt, öffnen Sie ein einzelnes CameraDevice (logische Kamera) und interagieren mit ihr innerhalb eines Sitzung. Einzelne Sitzung mithilfe der API erstellen CameraDevice.createCaptureSession(SessionConfiguration config), was in API-Ebene 28 hinzugefügt. Die Sitzungskonfiguration enthält eine Reihe von Konfigurationen, die jeweils eine Reihe von Ausgabezielen und optional die gewünschte ID der physischen Kamera.

<ph type="x-smartling-placeholder">
</ph>
Abbildung 3. SessionConfiguration- und OutputConfiguration-Modell

Erfassungsanfragen ist ein Ausgabeziel zugeordnet. Das Framework bestimmt, an welche physische (oder logische) Kamera die Anfragen gesendet werden. welches Ausgabeziel angehängt wird. Wenn das Ausgabeziel einem der Ausgabeziele, die als Ausgabekonfiguration zusammen mit einem physischen Kamera-ID enthält, empfängt und verarbeitet die physische Kamera die Anfrage.

Verwendung von zwei physischen Kameras

Eine weitere Ergänzung der Kamera-APIs für mehrere Kameras ist die Möglichkeit, logische Kameras und ermitteln Sie die physischen Kameras dahinter. Sie können eine um potenzielle Kamerapaare zu identifizieren, die Sie nutzen 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 {
        val dualCameras = MutableList()

        // 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 findDualCameras(CameraManager manager, Integer facing) {
        List dualCameras = new ArrayList<>();

        List 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 Statusverarbeitung der physischen Kameras wird von der logischen Kamera gesteuert. Bis eine „Dual-Kamera“ öffnen, die logische Kamera öffnen, die der physischen Kamera entspricht Kameras:

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);
            }
        });
    }

Der Vorgang zum Öffnen der Kamera ist der gleiche wie beim Öffnen. eine Kamera in früheren Android-Versionen. Erstellen einer Aufnahmesitzung mit dem neuen Sitzungskonfigurations-API weist das Framework an, bestimmte Ziele mit bestimmte IDs physischer Kameras:

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?, MutableList?>

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 via callback
        it.createCaptureSession(sessionConfiguration)
    }
}

Java

/**
 * 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 logicalCamera; private final List firstPhysicalCamera; private final List secondPhysicalCamera; public DualCameraOutputs(List logicalCamera, List firstPhysicalCamera, List third) { this.logicalCamera = logicalCamera; this.firstPhysicalCamera = firstPhysicalCamera; this.secondPhysicalCamera = third; } public List getLogicalCamera() { return logicalCamera; } public List getFirstPhysicalCamera() { return firstPhysicalCamera; } public List 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 outputConfigsLogical = targets.getLogicalCamera().stream() .map(OutputConfiguration::new) .collect(Collectors.toList()); List outputConfigsPhysical1 = targets.getFirstPhysicalCamera().stream() .map(s -> { OutputConfiguration outputConfiguration = new OutputConfiguration(s); outputConfiguration.setPhysicalCameraId(dualCamera.physicalId1); return outputConfiguration; }) .collect(Collectors.toList()); List 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 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 via callback c.createCaptureSession(sessionConfiguration)); }

Weitere Informationen finden Sie unter createCaptureSession finden Sie Informationen dazu, welche Kombination von Streams unterstützt wird. Streams kombinieren für mehrere Streams über eine einzige logische Kamera. Die Kompatibilität erstreckt sich mit derselben Konfiguration, wobei einer dieser Streams durch zwei Streams ersetzt wird. zwei physische Kameras, die Teil derselben logischen Kamera sind.

Mit der Kamerasitzung fertig sind, senden Sie die gewünschten Erfassungsanfragen. Jedes der Erfassungsanfrage ihre Daten vom zugehörigen physischen Kamera verwenden, falls welche verwendet werden, oder auf die logische Kamera zurückgreifen.

Beispiel für Zoom – Anwendungsfall

Durch die Zusammenführung physischer Kameras zu einem einzigen Stream um zwischen verschiedenen physischen Kameras zu wechseln, Sichtfeld und damit eine andere „Zoomstufe“.

<ph type="x-smartling-placeholder">
</ph>
Abbildung 4: Beispiel für den Austausch der Kameras im Anwendungsfall der Zoomstufe (aus Pixel 3-Anzeige)

Wählen Sie zuerst zwei physische Kameras aus, damit Nutzer die Kamera wechseln können dazwischen liegt. Die besten Ergebnisse erzielen Sie, wenn Sie zwei Kameras auswählen, die der verfügbaren minimalen und maximalen Brennweite.

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: eine pro Stream Diese SurfaceViews werden auf Grundlage der Nutzerinteraktion vertauscht, sodass nur eine davon zu einem bestimmten Zeitpunkt sichtbar sind.

Der folgende Code zeigt, wie die logische Kamera geöffnet und die Kamera konfiguriert wird. Ausgabe, erstellen Sie eine Kamerasitzung und starten Sie zwei Vorschaustreams:

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();
            }
        });

Nun müssen die Nutzenden nur noch eine Benutzeroberfläche bereitstellen, über die sie zwischen den beiden wechseln können. z. B. eine Schaltfläche oder das Doppeltippen auf SurfaceView. Sie könnten sogar Szenenanalysen durchzuführen und zwischen den beiden Streams zu wechseln automatisch.

Objektivverzeichnung

Alle Objektive erzeugen eine gewisse Verzerrung. In Android können Sie die durch Linsen erzeugte Verzerrungen, CameraCharacteristics.LENS_DISTORTION, die das mittlerweile eingestellte CameraCharacteristics.LENS_RADIAL_DISTORTION Bei logischen Kameras ist die Verzerrung minimal und Ihre Anwendung kann je nachdem, wie die Bilder von der Kamera kommen. Bei physischen Kameras: sind möglicherweise sehr unterschiedliche Objektivkonfigurationen, insbesondere das Weitwinkelobjektiv, Objektive.

Einige Geräte implementieren die automatische Verzeichnungskorrektur CaptureRequest.DISTORTION_CORRECTION_MODE Die Verzeichnungskorrektur ist bei 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(), ...);

Das Festlegen einer Aufnahmeanfrage in diesem Modus kann sich auf die Framerate auswirken, der von der Kamera erzeugt wird. Sie können die Verzeichnungskorrektur nur auf Standbilder erfasst.