API de varias cámaras

Nota: Esta página hace referencia al paquete Camera2. A menos que la app requiera funciones específicas y de bajo nivel de Camera2, te recomendamos que uses CameraX. CameraX y Camera2 admiten Android 5.0 (nivel de API 21) y versiones posteriores.

El uso de varias cámaras se introdujo en Android 9 (nivel de API 28). Desde su lanzamiento, llegaron al mercado dispositivos compatibles con la API. Muchos casos de uso de varias cámaras tienen acoplamiento alto con una configuración de hardware específica. En otras palabras, no Todos los casos de uso son compatibles con todos los dispositivos,  por lo que el uso de varias cámaras una buena candidata para las funciones de Play Publicación.

Estos son algunos casos prácticos típicos:

  • Zoom: Cambiar de cámara según la región de recorte o el enfoque deseado del conjunto de datos.
  • Profundidad: Cómo usar varias cámaras para crear un mapa de profundidad
  • Bokeh: Uso de información de profundidad inferida para simular una cámara angosta similar a una DSLR. de enfoque.

Diferencias entre una cámara lógica y una física

Para entender la API de varias cámaras, es necesario entender la diferencia entre cámaras físicas y lógicas. A modo de referencia, considera un dispositivo con tres y las cámaras posteriores. En este ejemplo, cada una de las tres cámaras traseras está una cámara física. Entonces, una cámara lógica es un grupo de dos o más de esas cámaras físicas. El resultado de la lógica cámara puede ser una transmisión que proviene de una de las cámaras físicas subyacentes, o una transmisión fusionada que proviene de más de una cámara física subyacente al mismo tiempo. De cualquier manera, el hardware de la cámara controla la transmisión. Capa de abstracción (HAL).

Muchos fabricantes de teléfonos desarrollan aplicaciones de cámara propias, que suelen vienen preinstaladas en sus dispositivos. Para usar todas las capacidades del hardware, pueden usar APIs ocultas o privadas, o recibir un tratamiento especial de la implementación del controlador a la que otras aplicaciones no tienen acceso. Algunos para implementar el concepto de cámaras lógicas, ya que proporcionan una transmisión fusionada de fotogramas de las distintas cámaras físicas, pero solo a ciertos aplicaciones. A menudo, solo una de las cámaras físicas se expone al en un framework de aplicaciones. La situación de los desarrolladores externos antes de Android 9 es ilustradas en el siguiente diagrama:

Figura 1: Por lo general, las funciones de la cámara solo están disponibles para apps con privilegios

A partir de Android 9, ya no se permiten las APIs privadas en las apps para Android. Con la compatibilidad con varias cámaras en el framework, Android recomienda que los fabricantes de teléfonos expongan una cámara lógica para todas las cámaras físicas orientadas en la misma dirección. Esto es lo que desarrolladores externos deberían esperar ver en los dispositivos con Android 9 y mayor:

Figura 2: Acceso completo de desarrollador a todos los dispositivos de cámara a partir de Android 9

Lo que proporcione la cámara lógica depende por completo de la implementación del OEM de la HAL de la cámara. Por ejemplo, un dispositivo como el Pixel 3 implementa su lógica cámara de tal manera que elige una de sus cámaras físicas en función del la longitud focal y la región de recorte solicitadas.

La API de varias cámaras

La nueva API agrega las siguientes constantes, clases y métodos nuevos:

Debido a cambios en el Documento de definición de compatibilidad de Android (CDD), el de varias cámaras también conlleva ciertas expectativas de los desarrolladores. Dispositivos con cámaras dobles existían antes de Android 9, pero la apertura de más de una cámara al mismo tiempo que implican prueba y error. En Android 9 y versiones posteriores, utiliza varias cámaras ofrece un conjunto de reglas para especificar cuándo es posible abrir un par de cámaras que son parte de la misma cámara lógica.

En la mayoría de los casos, los dispositivos con Android 9 y versiones posteriores exponen todas las (excepto en el caso de los tipos de sensores menos comunes, como los infrarrojos), junto con una cámara lógica fácil de usar. Para cada combinación de transmisiones que sean que funcione, se puede reemplazar una transmisión que pertenezca a una cámara lógica dos transmisiones desde las cámaras físicas subyacentes.

Varias transmisiones a la vez

Cómo usar varias transmisiones de cámara a la vez abarca las reglas para usar varias transmisiones simultáneas en una sola cámara. Con una adición importante, se aplican las mismas reglas para varias cámaras. CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA explica cómo reemplazar un flujo lógico YUV_420_888 o sin procesar por dos transmisiones físicas. Es decir, cada transmisión de tipo YUV o RAW se puede reemplazar por dos flujos del mismo tipo y tamaño. Puedes comenzar con una transmisión de la cámara de la siguiente configuración garantizada para dispositivos con una sola cámara:

  • Transmisión 1: Tipo YUV, tamaño MAXIMUM de la cámara lógica id = 0

Un dispositivo compatible con varias cámaras te permite crear una sesión. y reemplaza esa transmisión lógica YUV con dos transmisiones físicas:

  • Transmisión 1: Tipo YUV, tamaño MAXIMUM de la cámara física id = 1
  • Transmisión 2: Tipo YUV, tamaño MAXIMUM de la cámara física id = 2

Puedes reemplazar una transmisión YUV o RAW con dos transmisiones equivalentes solo si esas dos cámaras son parte de una agrupación de cámaras lógica,  que aparece en CameraCharacteristics.getPhysicalCameraIds()

Las garantías que proporciona el framework son solo las mínimas requeridas para obtener fotogramas de más de una cámara física a la vez. Transmisiones adicionales son compatibles con la mayoría de los dispositivos y, a veces, incluso se permiten abrir varias dispositivos de cámara de forma independiente. Ya que no es una garantía sólida de la Para ello, se deben realizar pruebas y ajustes por dispositivo mediante ensayo y error.

Crea una sesión con varias cámaras físicas

Cuando uses cámaras físicas en un dispositivo compatible con varias cámaras, abre una sola CameraDevice (la cámara lógica) e interactuar con ella en una sola sesión. Crea la sesión única con la API CameraDevice.createCaptureSession(SessionConfiguration config), que era agregado en el nivel de API 28. La configuración de la sesión tiene varias salidas de cada configuración, cada una de las cuales tiene un conjunto de objetivos de salida y, opcionalmente, un el ID de la cámara física deseada.

Figura 3: Modelos de SessionConfiguration y OutputConfiguration

Las solicitudes de captura tienen un objetivo de salida asociado. El marco de trabajo determina a qué cámara física (o lógica) se envían las solicitudes según y qué destino de salida se adjunta. Si el destino de salida corresponde a uno de objetivos de salida que se envió como una configuración de salida junto con una el ID de la cámara y, luego, esa cámara física recibe y procesa la solicitud.

Usar un par de cámaras físicas

Otra adición a las API de cámara para varias cámaras es la capacidad de identificar cámaras lógicas y encuentra las cámaras físicas detrás de ellas. Puedes definir para identificar posibles pares de cámaras físicas que puedes usar para reemplazar una de las transmisiones de cámara lógicas:

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 cámara lógica controla el manejo del estado de las cámaras físicas. Para abrir una "cámara doble" abre la cámara lógica correspondiente a la cámaras:

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

Además de seleccionar qué cámara abrir, el proceso es el mismo que abrir. una cámara en versiones anteriores de Android. Crear una sesión de captura con el nuevo La API de configuración de sesiones le indica al framework que asocie ciertos destinos con IDs específicos de la cámara física:

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?, 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 para obtener información sobre qué combinación de transmisiones es compatible. Combinar transmisiones es para varias transmisiones en una sola cámara lógica. La compatibilidad se extiende a usando la misma configuración y reemplazando una de esas transmisiones por dos de dos cámaras físicas que forman parte de la misma cámara lógica.

Con la sesión de cámara listo, envía el estado deseado solicitudes de captura. Cada objetivo de la solicitud de captura recibe sus datos de su ubicación física cámara, si alguna está en uso, o recurrir a la cámara lógica.

Caso de uso de ejemplo de zoom

Es posible combinar cámaras físicas en una sola transmisión para que los usuarios puedan alternar entre las diferentes cámaras físicas para experimentar un un campo visual diferente, lo que captura eficazmente un “nivel de zoom” diferente.

Figura 4: Ejemplo de cambio de cámaras por un caso de uso de nivel de zoom (del anuncio de Pixel 3)

Primero, selecciona el par de cámaras físicas para permitir que los usuarios cambien en el medio. Para lograr el máximo efecto, puedes elegir el par de cámaras que proporcionan la longitud focal mínima y máxima disponible.

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

Una arquitectura razonable para esto sería tener dos SurfaceViews: uno para cada transmisión. Estos SurfaceViews se intercambian según la interacción del usuario, de manera que solo uno visibles en cualquier momento.

En el siguiente código, se muestra cómo abrir la cámara lógica y configurar la cámara resultados, crea una sesión de cámara y, luego, inicia dos transmisiones de vista previa:

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

Lo único que queda por hacer es proporcionar una IU para que el usuario cambie entre los dos. plataformas, como un botón o presionar dos veces el SurfaceView. Incluso podrías realizar algún tipo de análisis de escena y alternar entre las dos transmisiones automáticamente.

Distorsión del lente

Todos los lentes producen cierta distorsión. En Android, puedes consultar distorsión creada por las lentes con CameraCharacteristics.LENS_DISTORTION: que reemplaza la versión obsoleta CameraCharacteristics.LENS_RADIAL_DISTORTION Para cámaras lógicas, la distorsión es mínima y tu aplicación puede usar los fotogramas en mayor o menor medida a medida que salen de la cámara. Para las cámaras físicas, podrías tener configuraciones de lentes muy diferentes, con lentes inteligentes.

Algunos dispositivos pueden implementar la corrección automática de distorsión mediante CaptureRequest.DISTORTION_CORRECTION_MODE La corrección de distorsión está activada de forma predeterminada en la mayoría de los dispositivos.

Kotlin

val cameraSession: CameraCaptureSession = ...

        // Use still capture template to build the capture request
        val captureRequest = cameraSession.device.createCaptureRequest(
            CameraDevice.TEMPLATE_STILL_CAPTURE
        )

        // Determine if this device supports distortion correction
        val characteristics: CameraCharacteristics = ...
        val supportsDistortionCorrection = characteristics.get(
            CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
        )?.contains(
            CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
        ) ?: false

        if (supportsDistortionCorrection) {
            captureRequest.set(
                CaptureRequest.DISTORTION_CORRECTION_MODE,
                CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
            )
        }

        // Add output target, set other capture request parameters...

        // Dispatch the capture request
        cameraSession.capture(captureRequest.build(), ...)

Java

CameraCaptureSession cameraSession = ...;

        // Use still capture template to build the capture request
        CaptureRequest.Builder captureRequestBuilder = null;
        try {
            captureRequestBuilder = cameraSession.getDevice().createCaptureRequest(
                    CameraDevice.TEMPLATE_STILL_CAPTURE
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        // Determine if this device supports distortion correction
        CameraCharacteristics characteristics = ...;
        boolean supportsDistortionCorrection = Arrays.stream(
                        characteristics.get(
                                CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
                        ))
                .anyMatch(i -> i == CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY);
        if (supportsDistortionCorrection) {
            captureRequestBuilder.set(
                    CaptureRequest.DISTORTION_CORRECTION_MODE,
                    CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
            );
        }

        // Add output target, set other capture request parameters...

        // Dispatch the capture request
        cameraSession.capture(captureRequestBuilder.build(), ...);

Configurar una solicitud de captura en este modo puede afectar la velocidad de fotogramas que se puede producidos por la cámara. Puedes elegir establecer la corrección de distorsión únicamente en capturas de imágenes fijas.