En esta página, se describe la arquitectura de CameraX, que incluye la estructura, cómo trabajar con la API y con ciclos de vida, y cómo combinar los casos de uso.
Estructura de CameraX
Puedes usar CameraX para interactuar con la cámara del dispositivo mediante una abstracción llamada caso de uso. Están disponibles los siguientes casos de uso:
- Vista previa: Acepta una plataforma para mostrar una vista previa, como un elemento
PreviewView
. - Análisis de imágenes: Proporciona búferes a los que se puede acceder desde la CPU para análisis, que sirven, por ejemplo, para aprendizaje automático.
- Captura de imágenes: Captura y guarda una foto.
- Captura de video: Captura video y audio con
VideoCapture
Los casos de uso pueden combinarse y estar activos en simultáneo. Por ejemplo, una app puede permitir que el usuario vea lo mismo que la cámara con un caso de uso de vista previa, tener otro caso de análisis de imágenes para determinar si las personas que aparecen en la foto están sonriendo e incluir otro de captura de imágenes que tome una foto cuando efectivamente estén sonriendo.
Modelo de API
Para trabajar con la biblioteca, tienes que especificar lo siguiente:
- El caso de uso deseado con sus opciones de configuración
- Qué hacer con los datos resultantes al juntar objetos de escucha
- Cuál es el flujo pretendido, como los momentos para habilitar las cámaras y producir datos, mediante la vinculación del caso de uso a los ciclos de vida de la arquitectura de Android
Hay 2 maneras de escribir una app de CameraX: un CameraController
(ideal si quieres la manera más sencilla de usar CameraX) o un CameraProvider
(ideal si necesitas más flexibilidad).
CameraController
Un CameraController
proporciona la mayor parte de la funcionalidad principal de CameraX en una sola clase. Requiere poco código de configuración y controla automáticamente la inicialización de la cámara, la administración de casos de uso, la rotación objetivo, la función de presionar para enfocar, pellizcar para acercar y mucho más. La clase concreta que extiende CameraController
es LifecycleCameraController
.
Kotlin
val previewView: PreviewView = viewBinding.previewView var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA previewView.controller = cameraController
Java
PreviewView previewView = viewBinding.previewView; LifecycleCameraController cameraController = new LifecycleCameraController(baseContext); cameraController.bindToLifecycle(this); cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA); previewView.setController(cameraController);
Los UseCase
predeterminados para CameraController
son Preview
, ImageCapture
y ImageAnalysis
. Para desactivar ImageCapture
o ImageAnalysis
, o activar VideoCapture
, usa el método setEnabledUseCases()
.
Para ver más usos de CameraController
, consulta la muestra del escáner de código QR o el video introductorio de CameraController
.
CameraProvider
Un CameraProvider
sigue siendo fácil de usar, pero como el desarrollador de apps controla más aspectos de la configuración, existen más oportunidades para personalizar la configuración, como habilitar la rotación de la imagen de salida o establecer el formato en ImageAnalysis
. También puedes usar una Surface
personalizada para obtener una vista previa de la cámara que brinda mayor flexibilidad, mientras que, con CameraController, debes usar una PreviewView
. Puede ser útil usar el código Surface
existente si ya es una entrada para otras partes de la app.
Puedes configurar casos de uso con los métodos set()
y finalizarlos con el método build()
. Cada objeto de caso de uso proporciona un conjunto de APIs específicas de casos de uso. Por ejemplo, el caso de uso de la captura de imagen proporciona una llamada al método takePicture()
.
Una aplicación especificará un ciclo de vida con el que asociar la cámara mediante cameraProvider.bindToLifecycle()
, en lugar de llamar métodos de inicio y finalización en onResume()
y onPause()
.
El ciclo de vida informa a CameraX cuándo se debe configurar la sesión de captura de la cámara y se asegura de que el estado de la cámara cambie de manera adecuada para que coincida con sus transiciones.
Si quieres conocer los pasos de implementación para cada caso de uso, consulta Cómo implementar una vista previa, Análisis de imágenes, Cómo capturar imágenes y Captura de video.
El caso de uso de la vista previa interactúa con un elemento Surface
para mostrarla. Las aplicaciones crean el caso de uso con opciones de configuración que utilizan el siguiente código:
Kotlin
val preview = Preview.Builder().build() val viewFinder: PreviewView = findViewById(R.id.previewView) // The use case is bound to an Android Lifecycle with the following code val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) // PreviewView creates a surface provider and is the recommended provider preview.setSurfaceProvider(viewFinder.getSurfaceProvider())
Java
Preview preview = new Preview.Builder().build(); PreviewView viewFinder = findViewById(R.id.view_finder); // The use case is bound to an Android Lifecycle with the following code Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview); // PreviewView creates a surface provider, using a Surface from a different // kind of view will require you to implement your own surface provider. preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();
Para ver más ejemplos de código, consulta la app de muestra de CameraX oficial.
Ciclos de vida de CameraX
CameraX obedece un ciclo de vida para determinar cuándo abrir la cámara, crear una sesión de captura, detenerse y apagarse. Las APIs de casos de uso proporcionan llamadas y devoluciones de llamada de métodos para supervisar el progreso.
Según se explica en el artículo para combinar casos de uso, puedes vincular combinaciones de casos de uso a un solo ciclo de vida. Cuando tu app necesite admitir casos de uso que no se puedan combinar, podrás realizar una de las siguientes acciones:
- Agrupar casos de uso compatibles en más de un fragmento y, luego, intercambiar fragmentos
- Crear un componente del ciclo de vida personalizado y usarlo para controlar el ciclo de vida de la cámara de forma manual
En caso de desacoplar la vista y los LifecycleOwner de los casos de uso de la cámara (por ejemplo, si usas un ciclo de vida personalizado o un fragmento de retención), debes asegurarte de que todos los casos de uso estén desvinculados de CameraX. Para ello, utiliza ProcessCameraProvider.unbindAll()
o desvincula cada caso de uso de forma individual. De manera alternativa, cuando vinculas casos de uso a un ciclo de vida, puedes permitir que CameraX administre la apertura y el cierre de la sesión de captura, y la desvinculación de los casos de uso.
Si todas las funciones de tu cámara corresponden al ciclo de vida de un solo componente relacionado con los ciclos de vida, como un fragmento AppCompatActivity
o AppCompat
, el uso del ciclo de vida de ese componente cuando vincules todos los casos de uso deseados garantizará que las funciones de la cámara estén listas cuando el componente esté activo o que se descarten de manera segura y sin consumir recursos.
LifecycleOwner personalizado
Para casos avanzados, puedes crear un LifecycleOwner
personalizado para permitir que tu app controle explícitamente el ciclo de vida de la sesión de CameraX en lugar de vincularlo a un LifecycleOwner
de Android estándar.
En la siguiente muestra de código, se indica cómo crear un LifecycleOwner simple y personalizado:
Kotlin
class CustomLifecycle : LifecycleOwner { private val lifecycleRegistry: LifecycleRegistry init { lifecycleRegistry = LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED) } ... fun doOnResume() { lifecycleRegistry.markState(State.RESUMED) } ... override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Java
public class CustomLifecycle implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; public CustomLifecycle() { lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } ... public void doOnResume() { lifecycleRegistry.markState(State.RESUMED); } ... public Lifecycle getLifecycle() { return lifecycleRegistry; } }
Con este LifecycleOwner
, tu app puede colocar transiciones de estado en los puntos deseados de su código. Si quieres obtener más información para implementar esta función en tu app, consulta Cómo implementar un LifecycleOwner personalizado.
Casos de uso simultáneos
Los casos de uso pueden ejecutarse en simultáneo. Si bien pueden estar vinculados de manera secuencial a un ciclo de vida, te recomendamos que vincules todos los casos de uso con una sola llamada a CameraProcessProvider.bindToLifecycle()
. Obtén más información sobre las prácticas recomendadas para los cambios de configuración en Administra los cambios en la configuración.
En la siguiente muestra de código, la app especifica los dos casos de uso que se crearán y ejecutarán de manera simultánea. También especifica el ciclo de vida que se usará para ambos casos de uso, para que los dos se inicien y se detengan según el ciclo de vida.
Kotlin
private lateinit var imageCapture: ImageCapture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { // Camera provider is now guaranteed to be available val cameraProvider = cameraProviderFuture.get() // Set up the preview use case to display camera preview. val preview = Preview.Builder().build() // Set up the capture use case to allow users to take photos. imageCapture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() // Choose the camera by requiring a lens facing val cameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_FRONT) .build() // Attach use cases to the camera with the same lifecycle owner val camera = cameraProvider.bindToLifecycle( this as LifecycleOwner, cameraSelector, preview, imageCapture) // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()) }, ContextCompat.getMainExecutor(this)) }
Java
private ImageCapture imageCapture; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); PreviewView previewView = findViewById(R.id.previewView); ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { // Camera provider is now guaranteed to be available ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); // Set up the view finder use case to display camera preview Preview preview = new Preview.Builder().build(); // Set up the capture use case to allow users to take photos imageCapture = new ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build(); // Choose the camera by requiring a lens facing CameraSelector cameraSelector = new CameraSelector.Builder() .requireLensFacing(lensFacing) .build(); // Attach use cases to the camera with the same lifecycle owner Camera camera = cameraProvider.bindToLifecycle( ((LifecycleOwner) this), cameraSelector, preview, imageCapture); // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()); } catch (InterruptedException | ExecutionException e) { // Currently no exceptions thrown. cameraProviderFuture.get() // shouldn't block since the listener is being called, so no need to // handle InterruptedException. } }, ContextCompat.getMainExecutor(this)); }
CameraX permite el uso simultáneo de una instancia de Preview
, VideoCapture
, ImageAnalysis
y ImageCapture
. Además:
- Todos los casos de uso pueden funcionar por su cuenta. Por ejemplo, una app puede grabar video sin usar la vista previa.
- Cuando se habilitan las extensiones, solo se garantiza que funcione la combinación
ImageCapture
yPreview
. En función de la implementación del OEM, es posible que tampoco se pueda agregarImageAnalysis
. No se pueden habilitar las extensiones para el caso de uso deVideoCapture
. Para obtener más información, consulta el documento de referencia de extensiones. - En función de sus capacidades, algunas cámaras pueden admitir la combinación en modos de menor resolución, pero no pueden admitir la misma combinación en resoluciones más altas.
- En dispositivos con un nivel de hardware de la cámara
FULL
o inferior, combinarPreview
,VideoCapture
yImageCapture
oImageAnalysis
puede forzar a CameraX a duplicar el flujoPRIV
de la cámara paraPreview
yVideoCapture
. Esta duplicación, llamada uso compartido de transmisiones, permite el uso simultáneo de estas funciones, pero a costa de aumentar las demandas de procesamiento. Como resultado, es posible que experimentes una latencia un poco más alta y una duración de batería reducida.
El nivel de hardware compatible se puede recuperar desde Camera2CameraInfo
. Por ejemplo, el siguiente código verifica si la cámara posterior predeterminada es un dispositivo LEVEL_3
:
Kotlin
@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.availableCameraInfos) .firstOrNull() ?.let { Camera2CameraInfo.from(it) } ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 } return false }
Java
@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class) Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { List\<CameraInfo\> filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.getAvailableCameraInfos()); if (!filteredCameraInfos.isEmpty()) { return Objects.equals( Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL), CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3); } } return false; }
Permisos
La app necesita el permiso CAMERA
. Para guardar imágenes en archivos, también necesitarás el permiso WRITE_EXTERNAL_STORAGE
, excepto en dispositivos que ejecuten Android 10 o versiones posteriores.
Si quieres obtener más información para configurar permisos en tu app, consulta este artículo.
Requisitos
CameraX tiene los siguientes requisitos mínimos de versión:
- Nivel 21 de la API de Android
- Componentes de la arquitectura de Android 1.1.1
Para actividades relacionadas con el ciclo de vida, usa FragmentActivity
o AppCompatActivity
.
Cómo declarar dependencias
Para agregar una dependencia en CameraX, debes agregar el repositorio de Maven de Google a tu proyecto.
Abre el archivo settings.gradle
de tu proyecto y agrega el repositorio google()
como se muestra a continuación:
Groovy
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Kotlin
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Agrega lo siguiente al final del bloque de Android:
Groovy
android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Kotlin
android { compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Agrega lo siguiente al archivo build.gradle
de cada módulo para una app:
Groovy
dependencies { // CameraX core library using the camera2 implementation def camerax_version = "1.5.0-alpha03" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" // If you want to additionally use the CameraX Lifecycle library implementation "androidx.camera:camera-lifecycle:${camerax_version}" // If you want to additionally use the CameraX VideoCapture library implementation "androidx.camera:camera-video:${camerax_version}" // If you want to additionally use the CameraX View class implementation "androidx.camera:camera-view:${camerax_version}" // If you want to additionally add CameraX ML Kit Vision Integration implementation "androidx.camera:camera-mlkit-vision:${camerax_version}" // If you want to additionally use the CameraX Extensions library implementation "androidx.camera:camera-extensions:${camerax_version}" }
Kotlin
dependencies { // CameraX core library using the camera2 implementation val camerax_version = "1.5.0-alpha03" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation("androidx.camera:camera-core:${camerax_version}") implementation("androidx.camera:camera-camera2:${camerax_version}") // If you want to additionally use the CameraX Lifecycle library implementation("androidx.camera:camera-lifecycle:${camerax_version}") // If you want to additionally use the CameraX VideoCapture library implementation("androidx.camera:camera-video:${camerax_version}") // If you want to additionally use the CameraX View class implementation("androidx.camera:camera-view:${camerax_version}") // If you want to additionally add CameraX ML Kit Vision Integration implementation("androidx.camera:camera-mlkit-vision:${camerax_version}") // If you want to additionally use the CameraX Extensions library implementation("androidx.camera:camera-extensions:${camerax_version}") }
Si quieres obtener más información para configurar tu app y que cumpla con estos requisitos, consulta Cómo declarar dependencias.
Interoperabilidad de CameraX con Camera2
CameraX se compiló en Camera2 y expone formas de leer, así como también de escribir, propiedades en la implementación de Camera2. Para obtener más información, consulta el paquete de interoperabilidad.
Si deseas obtener más información sobre la forma en la que CameraX configuró las propiedades de Camera2, usa Camera2CameraInfo
para leer las CameraCharacteristics
subyacentes. También puedes optar por escribir las propiedades subyacentes de Camera2 en una de las dos rutas siguientes:
Usa
Camera2CameraControl
, que te permite establecer propiedades en laCaptureRequest
subyacente, como el modo de enfoque automático.Extiende un
UseCase
de CameraX con unCamera2Interop.Extender
. Esto te permite establecer propiedades en CaptureRequest, al igual queCamera2CameraControl
. También te proporciona algunos controles adicionales, como configurar el caso de uso de transmisión para optimizar la cámara para tu caso de uso. Si quieres más información, consulta Usa casos de uso de transmisión para obtener un mejor rendimiento.
En la siguiente muestra de código, se usan casos de uso de transmisión para realizar optimizaciones con el objetivo de realizar una videollamada.
Usa Camera2CameraInfo
para recuperar si está disponible el caso de uso de transmisión de videollamadas. Luego, usa un Camera2Interop.Extender
para establecer el caso de uso de transmisión subyacente.
Kotlin
// Set underlying Camera2 stream use case to optimize for video calls. val videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong() // Check available CameraInfos to find the first one that supports // the video call stream use case. val frontCameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES )?.contains(videoCallStreamId) val isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT) (isVideoCallStreamingSupported == true) && isFrontFacing } val cameraSelector = frontCameraInfo.cameraSelector // Start with a Preview Builder. val previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation) // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId) // Bind the Preview UseCase and the corresponding CameraSelector. val preview = previewBuilder.build() camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Java
// Set underlying Camera2 stream use case to optimize for video calls. Long videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong(); // Check available CameraInfos to find the first one that supports // the video call stream use case. List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos(); CameraInfo frontCameraInfo = null; for (cameraInfo in cameraInfos) { Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES ); boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases) .contains(videoCallStreamId); boolean isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT); if (isVideoCallStreamingSupported && isFrontFacing) { frontCameraInfo = cameraInfo; } } if (frontCameraInfo == null) { // Handle case where video call streaming is not supported. } CameraSelector cameraSelector = frontCameraInfo.getCameraSelector(); // Start with a Preview Builder. Preview.Builder previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation); // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId); // Bind the Preview UseCase and the corresponding CameraSelector. Preview preview = previewBuilder.build() Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Recursos adicionales
Para obtener más información acerca de CameraX, consulta los siguientes recursos adicionales.
Codelab
Muestra de código