API multi-camera

Nota: questa pagina fa riferimento al pacchetto Fotocamera2. A meno che la tua app non richieda funzionalità specifiche di basso livello di Fotocamera2, ti consigliamo di utilizzare FotocameraX. Sia CameraX che Camera2 supportano Android 5.0 (livello API 21) e versioni successive.

La modalità multicamera è stata introdotta con Android 9 (livello API 28). Sin dal rilascio, sono arrivati sul mercato dispositivi che supportano l'API. Molti casi d'uso multicamera sono strettamente collegati a una specifica configurazione hardware. In altre parole, non tutti i casi d'uso sono compatibili con tutti i dispositivi,  il che rende le funzionalità multicamera una buona candidata per la distribuzione delle funzionalità di Google Play.

Ecco alcuni casi d'uso tipici:

  • Zoom: consente di passare da una fotocamera all'altra a seconda dell'area di ritaglio o della lunghezza focale desiderata.
  • Profondità: utilizzo di più fotocamere per creare una mappa di profondità.
  • Bokeh: utilizzo delle informazioni di profondità dedotte per simulare un intervallo di messa a fuoco ristretto, simile a una DSLR.

Differenza tra videocamere logiche e fisiche

Comprendere l'API multicamera richiede la differenza tra videocamere logiche e fisiche. Come riferimento, considera un dispositivo con tre fotocamere posteriori. In questo esempio, ognuna delle tre fotocamere posteriori è considerata una videocamera fisica. Una telecamera logica è un gruppo di due o più telecamere fisiche. L'output di una videocamera logica può essere un flusso che proviene da una delle videocamere fisiche sottostanti o un flusso fuso che proviene da più videocamere fisiche sottostanti contemporaneamente. In ogni caso, il flusso viene gestito dall'hardware Abstraction Layer (HAL) della videocamera.

Molti produttori di telefoni sviluppano applicazioni per fotocamere proprietarie, che di solito sono preinstallate sui loro dispositivi. Per utilizzare tutte le funzionalità dell'hardware, è possibile utilizzare API private o nascoste oppure ricevere un trattamento speciale dall'implementazione del driver a cui altre applicazioni non hanno accesso. Alcuni dispositivi implementano il concetto di telecamere logiche fornendo un flusso misto di fotogrammi provenienti dalle diverse videocamere fisiche, ma solo ad alcune applicazioni con privilegi. Spesso solo una delle fotocamere fisiche è esposta all'inquadratura. La situazione degli sviluppatori di terze parti precedenti ad Android 9 è illustrata nel seguente diagramma:

Figura 1. Le funzionalità della fotocamera in genere sono disponibili solo per le applicazioni con privilegi

A partire da Android 9, le API private non sono più consentite nelle app per Android. Con l'inclusione nel framework del supporto di più videocamere, le best practice di Android consigliano vivamente ai produttori di telefoni di esporre una fotocamera logica per tutte le fotocamere fisiche rivolte nella stessa direzione. Di seguito è riportato ciò che gli sviluppatori di terze parti dovrebbero aspettarsi sui dispositivi con Android 9 e versioni successive:

Figura 2. Accesso completo per sviluppatori a tutti i dispositivi con fotocamera a partire da Android 9

Ciò che la videocamera logica fornisce dipende interamente dall'implementazione dell'OEM della fotocamera HAL. Ad esempio, un dispositivo come Pixel 3 implementa la logica della fotocamera in modo da sceglierne una fisica in base alla lunghezza focale richiesta e all'area di ritaglio.

L'API multi-camera

La nuova API aggiunge le nuove costanti, classi e metodi seguenti:

A causa delle modifiche all'Android Compatibility Definition Document (CDD), l'API multi-camera presenta alcune aspettative da parte degli sviluppatori. I dispositivi con doppia fotocamera esistevano già prima di Android 9, ma l'apertura di più di una fotocamera comportava contemporaneamente tentativi ed errori. Su Android 9 e versioni successive, la modalità multicamera fornisce un insieme di regole per specificare quando è possibile aprire un paio di videocamere fisiche che fanno parte della stessa fotocamera logica.

Nella maggior parte dei casi, i dispositivi con Android 9 e versioni successive espongono tutte le videocamere fisiche (ad eccezione, probabilmente, dei tipi di sensori meno comuni come gli infrarossi) insieme a una videocamera logica più facile da usare. Per ogni combinazione di stream il cui funzionamento è garantito, uno stream appartenente a una videocamera logica può essere sostituito con due stream provenienti dalle videocamere fisiche sottostanti.

Più stream contemporaneamente

L'utilizzo di più stream da videocamere contemporaneamente copre le regole per l'utilizzo di più stream contemporaneamente in una singola videocamera. Con un'aggiunta importante, 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 non elaborato con due flussi fisici. Ciò significa che ogni flusso di tipo YUV o RAW può essere sostituito con due flussi di tipo e dimensione identici. Puoi iniziare con uno stream della videocamera con la seguente configurazione garantita per i dispositivi con videocamera singola:

  • Stream 1: tipo YUV, dimensioni MAXIMUM da videocamera logica id = 0

Quindi, un dispositivo con supporto multicamera consente di creare una sessione che sostituisce lo stream YUV logico con due flussi fisici:

  • Stream 1: tipo YUV, dimensione MAXIMUM dalla videocamera fisica id = 1
  • Stream 2: tipo YUV, dimensione MAXIMUM dalla videocamera fisica id = 2

Puoi sostituire uno stream YUV o RAW con due stream equivalenti solo se queste due videocamere fanno parte di un raggruppamento logico delle videocamere,  elencato nella sezione CameraCharacteristics.getPhysicalCameraIds().

Le garanzie offerte dal framework sono solo il minimo indispensabile per ottenere frame da più videocamere fisiche contemporaneamente. Gli stream aggiuntivi sono supportati nella maggior parte dei dispositivi e a volte consentono anche l'apertura di più videocamere fisiche in modo indipendente. Dal momento che non è una garanzia definitiva dal framework, farlo richiede l'esecuzione di test e ottimizzazione per dispositivo utilizzando prove ed errori.

Creazione di una sessione con più videocamere fisiche

Quando utilizzi fotocamere fisiche su un dispositivo abilitato per più videocamere, apri una singola CameraDevice (la videocamera logica) e interagisci con essa durante una singola sessione. Crea la singola sessione utilizzando l'API CameraDevice.createCaptureSession(SessionConfiguration config), che è stata aggiunta nel livello API 28. La configurazione della sessione prevede una serie di configurazioni di output, ognuna delle quali ha un insieme di destinazioni di output e, facoltativamente, un ID videocamera fisica desiderato.

Figura 3. Modello SessionConfiguration e OutputConfiguration

Alle richieste di acquisizione è associato un target di output. Il framework determina a quale videocamera fisica (o logica) vengono inviate le richieste in base alla destinazione di output collegata. Se il target di output corrisponde a uno dei target di output inviati come configurazione di output insieme a un ID videocamera fisica, la videocamera fisica riceve ed elabora la richiesta.

Utilizzo di un paio di videocamere fisiche

Un'altra aggiunta alle API fotocamera per multicamera è la possibilità di identificare le videocamere logiche e trovare le videocamere fisiche dietro di loro. Puoi definire una funzione per identificare meglio le potenziali coppie di videocamere fisiche che puoi utilizzare per sostituire uno degli stream logici delle videocamere:

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

La gestione dello stato delle videocamere fisiche è controllata dalla videocamera logica. Per aprire una "doppia fotocamera", apri la videocamera logica corrispondente alle videocamere fisiche:

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

Oltre a selezionare la fotocamera da aprire, la procedura equivale ad aprire una fotocamera nelle versioni precedenti di Android. La creazione di una sessione di acquisizione con la nuova API di configurazione della sessione indica al framework di associare determinati target a specifici ID videocamera fisica:

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

Consulta createCaptureSession per informazioni su quale combinazione di stream è supportata. La combinazione di flussi è per più stream su una singola videocamera logica. La compatibilità si estende all'utilizzo della stessa configurazione e alla sostituzione di uno di questi flussi con due stream provenienti da due videocamere fisiche che fanno parte della stessa videocamera logica.

Con la sessione della videocamera pronta, invia le richieste di acquisizione desiderate. Ogni target della richiesta di acquisizione riceve i dati dalla videocamera fisica associata, se in uso, oppure utilizza la videocamera logica.

Esempio di caso d'uso di Zoom

È possibile utilizzare l'unione di videocamere fisiche in un unico stream in modo che gli utenti possano passare da una videocamera fisica all'altra per sperimentare un campo visivo diverso, acquisendo in modo efficace un "livello di zoom" diverso.

Figura 4. Esempio di sostituzione della fotocamera per un caso d'uso del livello di zoom (dall'annuncio per Pixel 3)

Inizia selezionando la coppia di videocamere fisiche per consentire agli utenti di passare da una videocamera all'altra. Per ottenere il massimo effetto, puoi scegliere la coppia di fotocamere che forniscono la lunghezza focale minima e massima disponibili.

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

Un'architettura sensata sarebbe avere due SurfaceViews, uno per ogni stream. Questi SurfaceViews vengono scambiati in base all'interazione dell'utente, in modo che solo uno sia visibile in un determinato momento.

Il seguente codice mostra come aprire la videocamera logica, configurare le uscite della videocamera, creare una sessione della videocamera e avviare due stream di anteprima:

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

Non ti resta che fornire all'utente un'interfaccia utente per passare da una piattaforma all'altra, ad esempio un pulsante o toccando due volte SurfaceView. Puoi anche eseguire qualche forma di analisi delle scene e passare automaticamente da uno stream all'altro.

Distorsione obiettivo

Tutti gli obiettivi producono una certa distorsione. In Android, puoi eseguire query sulla distorsione creata dagli obiettivi utilizzando CameraCharacteristics.LENS_DISTORTION, che sostituisce CameraCharacteristics.LENS_RADIAL_DISTORTION. Per le videocamere logiche, la distorsione è minima e l'applicazione può utilizzare i fotogrammi più o meno poiché provengono dalla videocamera. Per le fotocamere fisiche, esistono configurazioni dell'obiettivo potenzialmente molto diverse, in particolare sugli obiettivi grandangolari.

Alcuni dispositivi possono implementare la correzione automatica della distorsione tramite CaptureRequest.DISTORTION_CORRECTION_MODE. Per impostazione predefinita, la correzione della distorsione è attiva per la maggior parte dei dispositivi.

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(), ...);

L'impostazione di una richiesta di acquisizione in questa modalità può influire sulla frequenza fotogrammi prodotta dalla fotocamera. Puoi scegliere di impostare la correzione della distorsione solo sulle acquisizioni di immagini fisse.