Opciones de configuración

Configura cada caso de uso de CameraX para controlar diferentes aspectos de sus operaciones.

Por ejemplo, con el caso de uso de captura de imágenes, puedes establecer una relación de aspecto objetivo y un modo de flash. El siguiente código muestra un ejemplo:

Kotlin

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

Java

ImageCapture imageCapture =
    new ImageCapture.Builder()
        .setFlashMode(...)
        .setTargetAspectRatio(...)
        .build();

Además de las opciones de configuración, algunos casos de uso exponen APIs para alterar, de forma dinámica, la configuración después de que se crean. Para obtener información sobre la configuración específica de cada caso de uso, consulta Cómo implementar una vista previa, Cómo analizar imágenes y Captura de imágenes.

CameraXConfig

Para simplificar, CameraX tiene configuraciones predeterminadas, como ejecutores internos y controladores que son adecuados para la mayoría de los casos de uso. Sin embargo, si tu aplicación tiene requisitos especiales o prefiere personalizar esas configuraciones, CameraXConfig es la interfaz para ese propósito.

Con CameraXConfig, una aplicación puede hacer lo siguiente:

Modelo de uso

En el siguiente procedimiento, se describe la manera de usar CameraXConfig:

  1. Crea un objeto CameraXConfig con tus configuraciones personalizadas.
  2. Implementa la interfaz CameraXConfig.Provider en tu Application y devuelve tu objeto CameraXConfig en getCameraXConfig().
  3. Agrega la clase Application al archivo AndroidManifest.xml, como se describe aquí.

En la siguiente muestra de código, se restringe el registro de CameraX a los mensajes de error únicamente:

Kotlin

class CameraApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
           .setMinimumLoggingLevel(Log.ERROR).build()
   }
}

Mantén una copia local del objeto CameraXConfig si tu aplicación necesita saber la configuración de CameraX después de establecerlo.

Limitador de cámara

Durante la primera invocación de ProcessCameraProvider.getInstance(), CameraX enumera y consulta las características de las cámaras disponibles en el dispositivo. Debido a que CameraX necesita comunicarse con los componentes de hardware, este proceso puede demorar bastante tiempo en cada cámara, especialmente en dispositivos de gama baja. Si tu aplicación solo usa cámaras específicas en el dispositivo, como la cámara frontal predeterminada, puedes configurar CameraX para ignorar otras cámaras, lo que puede reducir la latencia de inicio de las cámaras que usa la aplicación.

Si el CameraSelector que se pasa a CameraXConfig.Builder.setAvailableCamerasLimiter() filtra una cámara, CameraX se comporta como si esa cámara no existiera. Por ejemplo, el siguiente código limita la aplicación para que use solo la cámara posterior predeterminada del dispositivo:

Kotlin

class MainApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
              .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
              .build()
   }
}

Subprocesos

Muchas de las APIs de la plataforma en las que se compiló CameraX requieren el bloqueo de la comunicación entre procesos (IPC) con hardware que a veces puede tardar cientos de milisegundos en responder. Por este motivo, CameraX solo llama a estas APIs desde subprocesos en segundo plano para que el subproceso principal no esté bloqueado y la IU permanezca fluida. CameraX administra internamente estos subprocesos en segundo plano para que este comportamiento se vea transparente. Sin embargo, algunas aplicaciones requieren un control estricto de los subprocesos. CameraXConfig permite que una aplicación establezca los subprocesos en segundo plano que se usan a través de CameraXConfig.Builder.setCameraExecutor() y CameraXConfig.Builder.setSchedulerHandler().

Ejecutor de cámara

El ejecutor de cámara se usa para todas las llamadas internas a la API de la plataforma de cámara y también para las devoluciones de llamada de estas APIs. CameraX asigna y administra un Executor interno para realizar estas tareas. Sin embargo, si tu aplicación requiere un control más estricto de los subprocesos, usa CameraXConfig.Builder.setCameraExecutor().

Controlador del programador

Se usa para programar tareas internas a intervalos fijos, como volver a abrir la cámara cuando no está disponible. Este controlador no ejecuta trabajos y solo los envía al ejecutor de la cámara. En ocasiones, también se usa en las plataformas de APIs heredadas que requieren un Handler para las devoluciones de llamada. En esos casos, las devoluciones de llamada solo se envían directamente al ejecutor de la cámara. CameraX asigna y administra un elemento HandlerThread interno para realizar esas tareas, pero puedes anularlo con CameraXConfig.Builder.setSchedulerHandler().

Registro

El registro de CameraX permite que las aplicaciones filtren mensajes de Logcat, ya que se recomienda evitar mensajes detallados en el código de producción. CameraX admite cuatro niveles de registro, desde el más detallado hasta el más grave:

  • Log.DEBUG (predeterminado)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

Consulta la documentación de registro de Android para obtener descripciones detalladas de estos niveles de registro. Usa CameraXConfig.Builder.setMinimumLoggingLevel(int) para establecer el nivel de registro apropiado para tu aplicación.

Selección automática

Automáticamente, CameraX proporciona funciones específicas para el dispositivo en el que se ejecuta tu app. Por ejemplo, CameraX determinará automáticamente la mejor resolución si no especificas una o si la que especificas no es compatible. Todo esto lo administra la biblioteca, lo que elimina la necesidad de escribir un código específico del dispositivo.

El objetivo de CameraX es inicializar con éxito una sesión de la cámara. Por lo tanto, CameraX ajusta la resolución y las relaciones de aspecto, según la capacidad del dispositivo por los siguientes motivos:

  • El dispositivo no admite el tipo de resolución solicitado.
  • El dispositivo tiene problemas de compatibilidad, como los dispositivos heredados que requieren determinadas resoluciones para funcionar correctamente.
  • En algunos dispositivos, ciertos formatos solo están disponibles en algunas relaciones de aspecto.
  • El dispositivo tiene preferencia por un "mod16 más cercano" para JPEG o codificación de video. Para obtener más información, consulta SCALER_STREAM_CONFIGURATION_MAP.

Aunque CameraX crea y administra la sesión, siempre verifica en el código los tamaños de imagen que se muestran en los resultados del caso de uso y ajustarlos en consecuencia.

Rotación

De forma predeterminada, se configura la rotación de la cámara para que coincida con la rotación de la pantalla durante la creación del caso de uso. En este caso predeterminado, CameraX produce resultados que permiten que la app pueda lograr lo que esperas obtener en la vista previa. Puedes cambiar la rotación a un valor personalizado para admitir dispositivos de una amplia variedad de pantallas. Para ello, pasa la orientación de la pantalla del momento cuando se configura el caso de uso o hazlo de manera dinámica cuando ya se haya creado.

Tu app puede establecer la rotación objetivo con los parámetros de configuración. Luego, puede actualizar la configuración de rotación con los métodos de las APIs de casos de uso (como ImageAnalysis.setTargetRotation()), incluso mientras el ciclo de vida está en estado activo. Esto es posible cuando la app está bloqueada en modo de retrato, por lo que no se produce ninguna reconfiguración de rotación, pero la foto o el caso de uso de análisis requieren que se tenga en cuenta la rotación del dispositivo de ese momento. Por ejemplo, el reconocimiento de rotación puede ser necesario para que los rostros estén orientados correctamente y se puedan detectar, o que las fotos estén configuradas en modo horizontal o vertical.

Los datos de las imágenes capturadas se pueden almacenar sin información de rotación. Los datos Exif contienen información de rotación para que las aplicaciones de galería puedan mostrar la imagen en la orientación correcta después de guardarla.

Para mostrar datos de vista previa con la orientación correcta, puedes usar el resultado de metadatos de Preview.PreviewOutput() para crear transformaciones.

En la siguiente muestra de código, se indica cómo configurar la rotación en un evento de orientación:

Kotlin

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation : Int) {
            // Monitors orientation values to determine the target rotation value
            val rotation : Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

Java

@Override
public void onCreate() {
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) {
       @Override
       public void onOrientationChanged(int orientation) {
           int rotation;

           // Monitors orientation values to determine the target rotation value
           if (orientation >= 45 && orientation < 135) {
               rotation = Surface.ROTATION_270;
           } else if (orientation >= 135 && orientation < 225) {
               rotation = Surface.ROTATION_180;
           } else if (orientation >= 225 && orientation < 315) {
               rotation = Surface.ROTATION_90;
           } else {
               rotation = Surface.ROTATION_0;
           }

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

En función de la rotación, cada caso de uso rotará los datos de la imagen directamente o proporcionará los metadatos de rotación a los consumidores de los datos de la imagen no rotada.

  • Preview: Se proporciona un resultado de metadatos para que se conozca la rotación de la resolución objetivo con el elemento Preview.getTargetRotation().
  • ImageAnalysis: El resultado de los metadatos se proporciona para que se conozcan las coordenadas de búfer de la imagen en relación con las coordenadas de la pantalla.
  • ImageCapture: Se modifican los metadatos de Exif de la imagen, el búfer o ambos para tener en cuenta la configuración de rotación. El valor modificado depende de la implementación de HAL.

Rectángulo de recorte

De forma predeterminada, el rectángulo de recorte es el rectángulo del búfer completo. Puedes personalizarlo con ViewPort y UseCaseGroup. Agrupando casos de uso y configurando el viewport, CameraX garantiza que los rectángulos de recorte de todos los casos de uso del grupo apunten a la misma área del sensor de la cámara.

En el siguiente fragmento de código, se muestra la manera de usar estas dos clases:

Kotlin

val viewPort =  ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

Java

ViewPort viewPort = new ViewPort.Builder(
         new Rational(width, height),
         getDisplay().getRotation()).build();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);

ViewPort define el rectángulo del búfer visible para los usuarios finales. Luego, CameraX calcula el rectángulo de recorte más grande posible, según las propiedades de viewport y los casos de uso adjuntos. Por lo general, para lograr un efecto WYSIWYG, puedes configurar el viewport, según el caso de uso de vista previa. Una forma sencilla de obtener el viewport es usar PreviewView.

En los siguientes fragmentos de código, se muestra la manera de obtener el objeto ViewPort:

Kotlin

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

Java

ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();

En el ejemplo anterior, lo que obtiene la app de ImageAnalysis y ImageCapture coincide con lo que el usuario final ve en PreviewView, si se supone que el tipo de escala de PreviewView se establece en el valor predeterminado, FILL_CENTER. Después de aplicar el rectángulo de recorte y la rotación al búfer de salida, la imagen de todos los casos de uso es la misma, aunque posiblemente con resoluciones diferentes. Si deseas obtener más información para aplicar la información de transformación, consulta Resultado de la transformación.

Selección de cámara

CameraX selecciona automáticamente el mejor dispositivo de cámara, según los requisitos y casos de uso de tu aplicación. Si quieres usar un dispositivo diferente al que seleccionaste para ti, hay algunas opciones:

En la siguiente muestra de código, se ilustra cómo crear un CameraSelector para influenciar en la selección de dispositivos:

Kotlin

fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? {
   val cam2Infos = provider.availableCameraInfos.map {
       Camera2CameraInfo.from(it)
   }.sortedByDescending {
       // HARDWARE_LEVEL is Int type, with the order of:
       // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL
       it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
   }

   return when {
       cam2Infos.isNotEmpty() -> {
           CameraSelector.Builder()
               .addCameraFilter {
                   it.filter { camInfo ->
                       // cam2Infos[0] is either EXTERNAL or best built-in camera
                       val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                       thisCamId == cam2Infos[0].cameraId
                   }
               }.build()
       }
       else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera)
val selector = selectExternalOrBestCamera(processCameraProvider)
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

Cómo seleccionar varias cámaras a la vez

A partir de CameraX 1.3, también puedes seleccionar varias cámaras de forma simultánea. Por ejemplo, puedes utilizar una cámara frontal y una posterior para tomar fotos o grabar videos desde ambas perspectivas al mismo tiempo.

Cuando se usa la función Cámara simultánea, el dispositivo puede operar al mismo tiempo dos cámaras con lentes diferentes, o bien dos cámaras traseras al mismo tiempo. En el siguiente bloque de código, se muestra la manera de configurar dos cámaras cuando se llama a bindToLifecycle y la manera de obtener ambos objetos Camera del elemento ConcurrentCamera que se devuelve.

Kotlin

// Build ConcurrentCameraConfig
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

Java

// Build ConcurrentCameraConfig
SingleCameraConfig primary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

SingleCameraConfig secondary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

ConcurrentCamera concurrentCamera =  
    mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary));

Camera primaryCamera = concurrentCamera.getCameras().get(0);
Camera secondaryCamera = concurrentCamera.getCameras().get(1);

Resolución de la cámara

Puedes permitir que CameraX configure la resolución de imagen, según una combinación de funciones del dispositivo, el nivel de hardware compatible con el dispositivo, el caso de uso y la relación de aspecto proporcionada. Como alternativa, puedes establecer una resolución objetivo específica o una relación de aspecto específica en casos de uso que admitan esa configuración.

Resolución automática

CameraX puede determinar automáticamente la mejor configuración de resolución, según los casos de uso especificados en el objeto cameraProcessProvider.bindToLifecycle(). Siempre que sea posible, especifica todos los casos de uso necesarios para ejecutar simultáneamente en una sola sesión, en una sola llamada al elemento bindToLifecycle(). CameraX determina las resoluciones, según el conjunto de casos de uso vinculados, teniendo cuenta el nivel de hardware compatible con el dispositivo y considerando la variación específica del dispositivo (en la que un dispositivo supera o no alcanza las configuraciones de transmisión disponibles). El intent permite que la aplicación se ejecute en una amplia variedad de dispositivos, al mismo tiempo que se minimizan las instrucciones de código específicas para cada dispositivo.

La relación de aspecto predeterminada para los casos de uso de análisis de imagen y captura de imagen es 4:3.

Los casos de uso tienen una relación de aspecto configurable para permitir que la aplicación especifique la relación de aspecto deseada según el diseño de la IU. El resultado de CameraX se produce para que la relación de aspecto coincida lo más posible con lo solicitado, según lo que permita el dispositivo. Si no hubiera una concordancia exacta en la resolución admitida, se seleccionará la que cumpla con la mayor cantidad de condiciones posible. Entonces, la aplicación indicará cómo aparece la cámara en la app, y CameraX determinará la mejor configuración de resolución de cámara para diferentes dispositivos.

Por ejemplo, una app puede realizar cualquiera de las siguientes acciones:

  • Especificar una resolución objetivo de 4:3 o 16:9 para un caso de uso
  • Especificar una resolución personalizada, en la que CameraX intenta encontrar la coincidencia más cercana
  • Especificar una proporción de aspecto de recorte para ImageCapture

CameraX elige las resoluciones internas de superficie de Camera2 automáticamente. En la siguiente tabla, se muestran las resoluciones:

Caso de uso Resolución de superficie interna Resultado de resolución de datos
Vista previa Relación de aspecto: Es la resolución que mejor se ajusta al objetivo de la configuración. Resolución de superficie interna. Se proporcionan los metadatos para permitir un recorte, una escala y una rotación de la vista para la relación de aspecto objetivo.
Resolución predeterminada: Es la resolución de vista previa más alta o la resolución preferida por el dispositivo más alta que coincide con la relación de aspecto de la vista previa.
Resolución máxima: Es el tamaño de la vista previa, que hace referencia a la mejor coincidencia de tamaño en relación con la resolución de pantalla del dispositivo o a 1080p (1920 x 1080), el que sea menor.
Análisis de imágenes Relación de aspecto: Es la resolución que mejor se ajusta al objetivo de la configuración. Resolución de superficie interna.
Resolución predeterminada: La configuración de resolución objetivo predeterminada es 640 x 480. Ajustar la resolución objetivo y la relación de aspecto correspondiente genera una mejor resolución compatible.
Resolución máxima: Es la resolución máxima de salida del dispositivo de la cámara en formato YUV_420_888 que se recupera de StreamConfigurationMap.getOutputSizes(). La resolución objetivo se establece en 640 x 480 de forma predeterminada, por lo que si deseas una resolución superior a esa, debes usar setTargetResolution() y setTargetAspectRatio() para obtener la resolución más cercana a las que son compatibles.
Captura de imágenes Relación de aspecto: Es la relación de aspecto que mejor se ajusta a la configuración. Resolución de superficie interna.
Resolución predeterminada: Es la resolución más alta disponible o preferida por el dispositivo que coincide con la relación de aspecto de ImageCapture.
Resolución máxima: La resolución máxima de salida del dispositivo de la cámara en formato JPEG. Usa StreamConfigurationMap.getOutputSizes() para recuperarla.

Cómo especificar una resolución

Puedes establecer resoluciones específicas cuando compilas casos de uso con el método setTargetResolution(Size resolution), como se demuestra en la siguiente muestra de código:

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

Java

ImageAnalysis imageAnalysis =
  new ImageAnalysis.Builder()
    .setTargetResolution(new Size(1280, 720))
    .build();

No puedes establecer la relación de aspecto objetivo y la resolución objetivo en el mismo caso de uso, ya que se genera un elemento IllegalArgumentException cuando se compila el objeto de configuración.

Expresa la resolución Size en el fotograma de coordenadas después de rotar los tamaños compatibles, según la rotación objetivo. Por ejemplo, un dispositivo con orientación natural vertical en rotación objetivo natural que solicita una imagen vertical puede especificar 480 x 640 y el mismo dispositivo con rotación a 90 grados y orientación horizontal puede especificar 640 x 480.

La resolución objetivo intenta establecer un límite mínimo para la resolución de la imagen. La resolución de la imagen real es la resolución disponible más próxima al tamaño que no sea menor que la resolución objetivo, según lo determine la implementación de Camera.

Sin embargo, si no existe una resolución que sea igual o mayor que la resolución objetivo, se elige la resolución disponible más próxima a la resolución objetivo. Las resoluciones con la misma relación de aspecto del elemento Size proporcionado tienen una prioridad más alta que las resoluciones de diferentes relaciones de aspecto.

CameraX aplica la mejor resolución que se adecue a las solicitudes. Si la necesidad principal es satisfacer la relación de aspecto, especifica solo el objeto setTargetAspectRatio, y CameraX determinará una resolución específica adecuada, según el dispositivo. Si la necesidad principal de la app es especificar una resolución para que el procesamiento de imágenes sea más eficiente (por ejemplo, una imagen pequeña o mediana, según la capacidad de procesamiento del dispositivo), usa el objeto setTargetResolution(Size resolution).

Si tu app requiere una resolución exacta, consulta la tabla en createCaptureSession() para determinar qué resoluciones máximas admite cada nivel de hardware. Si necesitas verificar las resoluciones específicas compatibles con el dispositivo actual, consulta StreamConfigurationMap.getOutputSizes(int).

Si tu app se ejecuta en Android 10 o versiones posteriores, puedes usar el objeto isSessionConfigurationSupported() para verificar un elemento SessionConfiguration específico.

Cómo controlar la salida de la cámara

Además de permitirte configurar la salida de la cámara, según sea necesario, para cada caso de uso individual, CameraX también implementa las siguientes interfaces para admitir operaciones de cámara comunes a todos los casos de uso vinculados:

  • CameraControl te permite configurar funciones comunes de la cámara.
  • CameraInfo te permite consultar los estados de esas funciones de cámara comunes.

Estas son las funciones de cámara compatibles con CameraControl:

  • Zoom
  • Linterna
  • Enfoque y medición (presionar para enfocar)
  • Compensación de exposición

Cómo obtener instancias de CameraControl y CameraInfo

Recupera instancias de CameraControl y CameraInfo con el objeto Camera que devuelve ProcessCameraProvider.bindToLifecycle(). En el siguiente código, se muestra un ejemplo:

Kotlin

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

Java

Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
CameraControl cameraControl = camera.getCameraControl()
// For querying information and states.
CameraInfo cameraInfo = camera.getCameraInfo()

Por ejemplo, puedes enviar zoom y otras operaciones de CameraControl después de llamar a bindToLifecycle(). Después de detener o destruir la actividad utilizada para vincular la instancia de la cámara, CameraControl ya no puede ejecutar operaciones y muestra un ListenableFuture con errores.

Zoom

CameraControl ofrece dos métodos para cambiar el nivel de zoom:

  • setZoomRatio() establece el zoom según la proporción.

    La proporción debe estar dentro del rango de CameraInfo.getZoomState().getValue().getMinZoomRatio() y CameraInfo.getZoomState().getValue().getMaxZoomRatio(). De lo contrario, la función devuelve un ListenableFuture con errores.

  • setLinearZoom() establece el zoom actual con un valor de zoom lineal de 0 a 1.0.

    La ventaja del zoom lineal es que logra que el campo visual (FOV) se ajuste con los cambios de zoom. Por lo tanto, resulta ideal para usarlo con una vista de Slider.

CameraInfo.getZoomState() muestra un LiveData del estado de zoom actual. El valor cambia cuando se inicializa la cámara o si se establece el nivel de zoom con setZoomRatio() o setLinearZoom(). Cuando se llama a cualquiera de los dos métodos, se establecen los valores que respaldan ZoomState.getZoomRatio() y ZoomState.getLinearZoom(). Esto resulta útil si deseas mostrar el texto de la proporción del zoom junto con un control deslizante. Solo observa el LiveData de ZoomState para actualizar ambos sin necesidad de hacer una conversión.

El ListenableFuture que devuelven ambas APIs ofrece la opción de que las aplicaciones reciban notificaciones cuando se completa una solicitud recurrente con el valor de zoom especificado. Además, si estableces un nuevo valor de zoom mientras la operación anterior aún se está ejecutando, el ListenableFuture de la operación de zoom anterior fallará de inmediato.

Linterna

CameraControl.enableTorch(boolean) habilita o inhabilita la linterna.

CameraInfo.getTorchState() se puede usar para consultar el estado actual de la linterna. Puedes verificar el valor que muestra CameraInfo.hasFlashUnit() para determinar si hay una linterna disponible. Si no lo está, llamar a CameraControl.enableTorch(boolean) hace que el objeto ListenableFuture que se devuelve se complete de inmediato con un resultado con errores y establece el estado de la linterna en TorchState.OFF.

Cuando la linterna está habilitada, permanece encendida durante la captura de fotos y videos, independientemente de la configuración de flashMode. El flashMode en ImageCapture solo funciona cuando la linterna está inhabilitada.

Enfoque y medición

CameraControl.startFocusAndMetering() activa la medición de la exposición y el enfoque automático a través de la configuración de regiones de medición AF/AE/AWB, según la FocusMeteringAction especificada. Esto se suele usar para implementar la función "presionar para enfocar" en muchas aplicaciones de cámara.

MeteringPoint

Para comenzar, crea un MeteringPoint con MeteringPointFactory.createPoint(float x, float y, float size). Un MeteringPoint representa un punto único en la Surface de la cámara. Se almacena en un formato normalizado de modo que se pueda convertir con facilidad en coordenadas de sensores para especificar regiones AF/AE/AWB.

El tamaño de MeteringPoint varía de 0 a 1 y tiene un valor predeterminado de 0.15f. Cuando llamas a MeteringPointFactory.createPoint(float x, float y, float size), CameraX crea una región rectangular centrada en (x, y) para el size proporcionado.

En el siguiente código, se muestra cómo crear un MeteringPoint:

Kotlin

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)
…
}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for
// preview. Please note that if the preview is scaled or cropped in the View,
// it’s the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

startFocusAndMetering y FocusMeteringAction

Para invocar a startFocusAndMetering(), las aplicaciones deben compilar una FocusMeteringAction, que consiste en uno o más MeteringPoints con combinaciones opcionales del modo de medición de FLAG_AF, FLAG_AE, FLAG_AWB. En el siguiente código, se demuestra este uso:

Kotlin

val meteringPoint1 = meteringPointFactory.createPoint(x1, x1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB
      // Optionally add meteringPoint2 for AF/AE.
      .addPoint(meteringPoint2, FLAG_AF | FLAG_AE)
      // The action is canceled in 3 seconds (if not set, default is 5s).
      .setAutoCancelDuration(3, TimeUnit.SECONDS)
      .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering result.
result.addListener({
   // result.get().isFocusSuccessful returns if the auto focus is successful or not.
}, ContextCompat.getMainExecutor(this)

Como se muestra en el código anterior, startFocusAndMetering() toma un objeto FocusMeteringAction que consta de un elemento MeteringPoint para las regiones de medición AF/AE/AWB, y otro MeteringPoint solo está disponible para AF y AE.

Internamente, CameraX lo convierte en MeteringRectangles de Camera2 y establece los parámetros CONTROL_AF_REGIONS/CONTROL_AE_REGIONS/CONTROL_AWB_REGIONS en la solicitud de captura.

Como no todos los dispositivos son compatibles con AF/AE/AWB y varias regiones, CameraX ejecuta FocusMeteringAction de la mejor manera posible. CameraX usa la cantidad máxima compatible de MeteringPoints, en el orden en que se agregaron los puntos. Se ignorarán todos los MeteringPoints agregados después de alcanzada la cantidad máxima. Por ejemplo, si se proporciona un objeto FocusMeteringAction con 3 MeteringPoints en una plataforma que admite solo 2, solo se usarán los primeros 2 MeteringPoints. CameraX ignorará el último MeteringPoint.

Compensación de exposición

La compensación de exposición resulta útil cuando las aplicaciones necesitan ajustar los valores de exposición (VE) más allá del resultado de la exposición automática (EA). Los valores de compensación de exposición se combinan de la siguiente manera para determinar la exposición necesaria en las condiciones actuales de la imagen:

Exposure = ExposureCompensationIndex * ExposureCompensationStep

CameraX proporciona la función Camera.CameraControl.setExposureCompensationIndex() para establecer la compensación de exposición como un valor de índice.

Los valores positivos de índice permiten que la imagen se vea más brillante, mientras que los valores negativos la atenúan. Las aplicaciones pueden consultar el rango admitido por CameraInfo.ExposureState.exposureCompensationRange() descrito en la siguiente sección. Si se admite el valor, el ListenableFuture que se devuelve se completa cuando el valor se habilita correctamente en la solicitud de captura. Si el índice especificado está fuera del rango admitido, setExposureCompensationIndex() hace que el ListenableFuture que se devuelve se complete de inmediato con un resultado con errores.

CameraX solo conserva la solicitud pendiente de setExposureCompensationIndex() más reciente, y llamar a la función varias veces antes de que se ejecute la solicitud anterior provoca su cancelación.

En el siguiente fragmento, se establece un índice de compensación de exposición y se registra una devolución de llamada para cuando se haya ejecutado la solicitud de cambio de exposición:

Kotlin

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
   .addListener({
      // Get the current exposure compensation index, it might be
      // different from the asked value in case this request was
      // canceled by a newer setting request.
      val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
      …
   }, mainExecutor)
  • Camera.CameraInfo.getExposureState() recupera el ExposureState actual, incluida la siguiente información:

    • La compatibilidad con el control de compensación de exposición
    • El índice actual de compensación de exposición
    • El rango del índice de compensación de exposición
    • El paso de compensación de exposición que se usó para calcular el valor de esa compensación

Por ejemplo, el siguiente código inicializa los parámetros de configuración de una SeekBar de exposición con los valores actuales del ExposureState:

Kotlin

val exposureState = camera.cameraInfo.exposureState
binding.seekBar.apply {
   isEnabled = exposureState.isExposureCompensationSupported
   max = exposureState.exposureCompensationRange.upper
   min = exposureState.exposureCompensationRange.lower
   progress = exposureState.exposureCompensationIndex
}

Recursos adicionales

Para obtener más información acerca de CameraX, consulta los siguientes recursos adicionales.

Codelab

  • Cómo comenzar a usar CameraX
  • Muestra de código

  • Apps de ejemplo de CameraX
  • Comunidad de desarrolladores

    Grupo de discusión sobre Android CameraX