Nota: Esta página hace referencia al paquete Camera2. A menos que tu app requiera funciones específicas de bajo nivel de Camera2, te recomendamos que uses CameraX. CameraX y Camera2 admiten Android 5.0 (nivel de API 21) y versiones posteriores.
Las cámaras y las vistas previas de la cámara no siempre tienen la misma orientación en los dispositivos Android.
Una cámara se encuentra en una posición fija en un dispositivo, independientemente de si el dispositivo es un teléfono, una tablet o una computadora. Cuando cambia la orientación del dispositivo, cambia la orientación de la cámara.
Como resultado, las apps de cámara suelen suponer una relación fija entre la orientación del dispositivo y la relación de aspecto de la vista previa de la cámara. Cuando un teléfono está en orientación vertical, se supone que la vista previa de la cámara es más alta que ancha. Cuando el teléfono (y la cámara) se rotan a la orientación horizontal, se espera que la vista previa de la cámara sea más ancha que alta.
Sin embargo, estos supuestos se ven desafiados por nuevos factores de forma, como los dispositivos plegables, y modos de visualización, como multiventana y varias pantallas. Los dispositivos plegables cambian el tamaño de la pantalla y la relación de aspecto sin cambiar la orientación. El modo multiventana limita las apps de cámara a una parte de la pantalla, lo que escala la vista previa de la cámara independientemente de la orientación del dispositivo. El modo de varias pantallas permite usar pantallas secundarias que podrían no tener la misma orientación que la pantalla principal.
Orientación de la cámara
El documento de definición de compatibilidad de Android especifica que un sensor de imagen de la cámara "SE DEBE orientar para que la dimensión larga de la cámara se alinee con la dimensión larga de la pantalla". Es decir, cuando el dispositivo se sostiene en orientación horizontal, las cámaras DEBEN capturar imágenes en orientación horizontal. Esto se aplica independientemente de la orientación natural del dispositivo, es decir, se aplica tanto a los dispositivos con orientación horizontal principal como a los dispositivos con orientación vertical principal".
La disposición de la cámara a la pantalla maximiza el área de visualización del visor de la cámara en una app de cámara. Además, los sensores de imagen suelen mostrar sus datos en relaciones de aspecto horizontales, siendo 4:3 la más común.
La orientación natural del sensor de la cámara es horizontal. En la figura 1, el sensor de la cámara frontal (la cámara que apunta en la misma dirección que la pantalla) se rota 270 grados en relación con el teléfono para cumplir con el documento de definición de compatibilidad de Android.
Para exponer la rotación del sensor a las apps, la API de camera2 incluye una constante SENSOR_ORIENTATION. En la mayoría de los teléfonos y las tablets, el dispositivo informa una orientación del sensor de 270 grados para las cámaras frontales y de 90 grados (punto de vista desde la parte posterior del dispositivo) para las cámaras posteriores, lo que alinea el borde largo del sensor con el borde largo del dispositivo. Las cámaras de las laptops suelen informar una orientación del sensor de 0 o 180 grados.
Debido a que los sensores de imagen de la cámara generan sus datos (un búfer de imagen) en la orientación natural del sensor (horizontal), el búfer de imagen se debe rotar la cantidad de grados que especifica SENSOR_ORIENTATION para que la vista previa de la cámara aparezca en posición vertical en la orientación natural del dispositivo. En el caso de las cámaras frontales, la rotación es en el sentido contrario a las manecillas del reloj, mientras que, en el caso de las cámaras posteriores, es en el sentido de las manecillas del reloj.
Por ejemplo, para la cámara frontal de la figura 1, el búfer de imagen que produce el sensor de la cámara se ve de la siguiente manera:
La imagen se debe rotar 270 grados en el sentido contrario a las manecillas del reloj para que la orientación de la vista previa coincida con la del dispositivo:
Una cámara posterior produciría un búfer de imagen con la misma orientación que el búfer anterior, pero SENSOR_ORIENTATION es de 90 grados. Como resultado, el búfer se rota 90 grados en el sentido de las manecillas del reloj.
Rotación del dispositivo
La rotación del dispositivo es la cantidad de grados que se rota un dispositivo desde su orientación natural. Por ejemplo, un teléfono en orientación horizontal tiene una rotación de dispositivo de 90 o 270 grados, según la dirección de rotación.
Un búfer de imagen del sensor de la cámara debe rotarse la misma cantidad de grados que la rotación del dispositivo (además de los grados de orientación del sensor) para que la vista previa de la cámara aparezca en posición vertical.
Cálculo de la orientación
La orientación adecuada de la vista previa de la cámara tiene en cuenta la orientación del sensor y la rotación del dispositivo.
La rotación general del búfer de imágenes del sensor se puede calcular con la siguiente fórmula:
rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360
Donde sign es 1 para las cámaras frontales y -1 para las cámaras posteriores.
En el caso de las cámaras frontales, el búfer de imagen se rota en el sentido contrario a las manecillas del reloj (desde la orientación natural del sensor). En el caso de las cámaras posteriores, el búfer de imagen del sensor se rota en el sentido de las manecillas del reloj.
La expresión deviceOrientationDegrees * sign + 360 convierte la rotación del dispositivo de sentido antihorario a sentido horario para las cámaras posteriores (por ejemplo, convierte 270 grados en sentido antihorario a 90 grados en sentido horario). La operación de módulo
escala el resultado a menos de 360 grados (por ejemplo, escala 540
grados de rotación a 180).
Las diferentes APIs informan la rotación del dispositivo de distintas maneras:
Display#getRotation()proporciona la rotación en el sentido contrario a las manecillas del reloj del dispositivo (desde el punto de vista del usuario). Este valor se conecta a la fórmula anterior tal como está.OrientationEventListener#onOrientationChanged()devuelve la rotación en el sentido de las manecillas del reloj del dispositivo (desde el punto de vista del usuario). Niega el valor para usarlo en la fórmula anterior.
Cámaras frontales
Este es el búfer de imagen que produce el sensor de la cámara en la figura 4:
El búfer se debe rotar 270 grados en el sentido contrario a las manecillas del reloj para ajustarse a la orientación del sensor (consulta Orientación de la cámara más arriba):
Luego, el búfer se rota otros 90 grados en sentido antihorario para tener en cuenta la rotación del dispositivo, lo que genera la orientación correcta de la vista previa de la cámara en la figura 4:
Aquí, la cámara se giró hacia la derecha para cambiar a la orientación horizontal:
Este es el búfer de imagen:
El búfer se debe rotar 270 grados en el sentido contrario a las manecillas del reloj para ajustarse a la orientación del sensor:
Luego, el búfer se rota otros 270 grados en sentido antihorario para tener en cuenta la rotación del dispositivo:
Cámaras posteriores
Las cámaras posteriores suelen tener una orientación del sensor de 90 grados (visto desde la parte posterior del dispositivo). Cuando se orienta la vista previa de la cámara, el búfer de imagen del sensor se rota en el sentido de las manecillas del reloj según la cantidad de rotación del sensor (en lugar de en el sentido contrario a las manecillas del reloj, como las cámaras frontales) y, luego, el búfer de imagen se rota en el sentido contrario a las manecillas del reloj según la cantidad de rotación del dispositivo.
Este es el búfer de imagen del sensor de la cámara de la figura 12:
El búfer se debe rotar 90 grados en el sentido de las manecillas del reloj para ajustarse a la orientación del sensor:
Luego, el búfer se rota 270 grados en el sentido contrario a las manecillas del reloj para tener en cuenta la rotación del dispositivo:
Relación de aspecto
La relación de aspecto de la pantalla cambia cuando se modifica la orientación del dispositivo, pero también cuando se pliegan y despliegan los dispositivos plegables, cuando se cambia el tamaño de las ventanas en entornos multiventana y cuando las apps se abren en pantallas secundarias.
El búfer de imágenes del sensor de la cámara debe estar orientado y ajustado para que coincida con la orientación y la relación de aspecto del elemento de la IU del visor a medida que la IU cambia de orientación de forma dinámica, con o sin el cambio de orientación del dispositivo.
En factores de forma nuevos o en entornos multiventana o de varias pantallas, si tu app supone que la vista previa de la cámara tiene la misma orientación que el dispositivo (vertical u horizontal), es posible que la vista previa se oriente o se ajuste de forma incorrecta, o ambas cosas.
En la figura 16, la aplicación supuso por error que el dispositivo se había rotado 90 grados en sentido antihorario, por lo que la app rotó la vista previa en la misma medida.
En la figura 17, la app no ajustó la relación de aspecto del búfer de imagen para permitir que se ajuste correctamente a las nuevas dimensiones del elemento de la IU de vista previa de la cámara.
Las apps de cámara con orientación fija suelen tener problemas en dispositivos plegables y otros dispositivos con pantalla grande, como las laptops:
En la figura 18, la IU de la app de la cámara está de costado porque la orientación de la app está restringida solo al modo vertical. La imagen del visor se orienta correctamente en relación con el sensor de la cámara.
Modo Retrato insertado
Las apps de cámara que no admiten el modo multiventana (resizeableActivity="false") y restringen su orientación (screenOrientation="portrait" o screenOrientation="landscape") se pueden colocar en modo Retrato insertado en dispositivos de pantalla grande para orientar correctamente la vista previa de la cámara.
El modo Retrato insertado aplica formato letterbox (inserciones) a las apps que solo admiten la orientación vertical, incluso si la relación de aspecto de la pantalla es horizontal. Las apps solo con modo horizontal tienen formato letterbox en orientación horizontal, aunque la relación de aspecto de la pantalla sea vertical. La imagen de la cámara se rota para alinearse con la IU de la app, se recorta para que coincida con la relación de aspecto de la vista previa de la cámara y, luego, se escala para llenar la vista previa.
El modo Retrato insertado se activa cuando la relación de aspecto del sensor de imagen de la cámara y la relación de aspecto de la actividad principal de la aplicación no coinciden.
En la figura 19, se rotó la app de cámara solo para modo vertical para que muestre la IU en posición vertical en la pantalla de la laptop. La app tiene formato letterbox debido a la diferencia en la relación de aspecto entre la app vertical y la pantalla horizontal. La imagen de la vista previa de la cámara se rotó para compensar la rotación de la IU de la app (debido al modo Retrato insertado), y la imagen se recortó y escaló para que se ajuste a la orientación vertical, lo que reduce el campo visual.
Rotar, recortar y ajustar la escala
Se invoca el modo Retrato insertado para una app de cámara solo en modo Retrato en una pantalla que tiene una relación de aspecto horizontal:
La app tiene formato letterbox en orientación vertical:
La imagen de la cámara se rota 90 grados para ajustarse a la reorientación de la app:
La imagen se recorta según la relación de aspecto de la vista previa de la cámara y, luego, se escala para llenar la vista previa (se reduce el campo visual):
En los dispositivos plegables, la orientación del sensor de la cámara puede ser vertical, mientras que la relación de aspecto de la pantalla es horizontal:
Debido a que la vista previa de la cámara se rota para ajustarse a la orientación del sensor, la imagen se orienta correctamente en el visor, pero la app solo vertical se muestra de costado.
El modo de inserción vertical solo necesita agregar formato letterbox a la app en orientación vertical para orientar correctamente la app y la vista previa de la cámara:
API
A partir de Android 12 (nivel de API 31), las apps también pueden controlar de forma explícita el modo Retrato de inserción a través de la propiedad SCALER_ROTATE_AND_CROP de la clase CaptureRequest.
El valor predeterminado es
SCALER_ROTATE_AND_CROP_AUTO, que permite que el
sistema invoque el modo Retrato insertado.
SCALER_ROTATE_AND_CROP_90 es el comportamiento del modo Retrato insertado, como se describió antes.
No todos los dispositivos admiten todos los valores de SCALER_ROTATE_AND_CROP. Para obtener una lista de los valores admitidos, consulta CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES.
CameraX
La biblioteca de Jetpack CameraX facilita la creación de un visor de cámara que se adapte a la orientación del sensor y la rotación del dispositivo.
El elemento de diseño PreviewView crea una vista previa de la cámara y se ajusta automáticamente según la orientación del sensor, la rotación del dispositivo y el escalamiento.
PreviewView mantiene la relación de aspecto de la imagen de la cámara aplicando el tipo de escala FILL_CENTER, que centra la imagen, pero puede recortarla para que coincida con las dimensiones de PreviewView. Para aplicar el formato de pantalla panorámica a la imagen de la cámara, establece el tipo de escala en FIT_CENTER.
Para conocer los conceptos básicos de la creación de una vista previa de la cámara con PreviewView, consulta Cómo implementar una vista previa.
Para ver una implementación de ejemplo completa, consulta el repositorio de CameraXBasic en GitHub.
CameraViewfinder
Al igual que el caso de uso de Preview, la biblioteca de CameraViewfinder proporciona un conjunto de herramientas para simplificar la creación de una vista previa de la cámara. No depende de CameraX Core, por lo que puedes integrarlo sin problemas en tu base de código de Camera2 existente.
En lugar de usar Surface directamente, puedes usar el widget CameraViewfinder para mostrar el feed de la cámara de Camera2.
CameraViewfinder usa de forma interna un TextureView o un SurfaceView para mostrar el feed de la cámara y aplica las transformaciones necesarias para mostrar el visor de forma correcta. Esto implica corregir su relación de aspecto,
escala y rotación.
Para solicitar la superficie del objeto CameraViewfinder, debes crear un ViewfinderSurfaceRequest.
Esta solicitud contiene requisitos para la resolución de la superficie y la información del dispositivo de la cámara de CameraCharacteristics.
Cuando se llama a requestSurfaceAsync(), se envía la solicitud al proveedor de la plataforma, que es un TextureView o un SurfaceView, y se obtiene un ListenableFuture de Surface.
Cuando se llama a markSurfaceSafeToRelease(), se notifica al proveedor de la plataforma que esta no es necesaria y que se pueden liberar los recursos relacionados.
fun startCamera() {
val previewResolution = Size(width, height)
val viewfinderSurfaceRequest =
ViewfinderSurfaceRequest(previewResolution, characteristics)
val surfaceListenableFuture =
cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest)
Futures.addCallback(surfaceListenableFuture, object : FutureCallback<Surface> {
override fun onSuccess(surface: Surface) {
/* create a CaptureSession using this surface as usual */
}
override fun onFailure(t: Throwable) { /* something went wrong */ }
}, ContextCompat.getMainExecutor(context))
}
SurfaceView
SurfaceView es un enfoque directo para crear una vista previa de la cámara si la vista previa no requiere procesamiento y no está animada.
SurfaceView rota automáticamente el búfer de imágenes del sensor de la cámara para que coincida con la orientación de la pantalla, lo que tiene en cuenta tanto la orientación del sensor como la rotación del dispositivo.
Sin embargo, el búfer de imagen se escala para ajustarse a las dimensiones de SurfaceView sin tener en cuenta la relación de aspecto.
Debes asegurarte de que la relación de aspecto del búfer de imagen coincida con la relación de aspecto de SurfaceView, lo que puedes lograr escalando el contenido de SurfaceView en el método onMeasure() del componente:
(El código fuente de computeRelativeRotation() se encuentra en Rotación relativa, más abajo).
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val width = MeasureSpec.getSize(widthMeasureSpec) val height = MeasureSpec.getSize(heightMeasureSpec) val relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees) if (previewWidth > 0f && previewHeight > 0f) { /* Scale factor required to scale the preview to its original size on the x-axis. */ val scaleX = if (relativeRotation % 180 == 0) { width.toFloat() / previewWidth } else { width.toFloat() / previewHeight } /* Scale factor required to scale the preview to its original size on the y-axis. */ val scaleY = if (relativeRotation % 180 == 0) { height.toFloat() / previewHeight } else { height.toFloat() / previewWidth } /* Scale factor required to fit the preview to the SurfaceView size. */ val finalScale = min(scaleX, scaleY) setScaleX(1 / scaleX * finalScale) setScaleY(1 / scaleY * finalScale) } setMeasuredDimension(width, height) }
Para obtener más detalles sobre cómo implementar SurfaceView como una vista previa de la cámara, consulta Orientaciones de la cámara.
TextureView
TextureView tiene un rendimiento inferior al de SurfaceView y requiere más trabajo, pero TextureView te brinda el máximo control de la vista previa de la cámara.
TextureView rota el búfer de imágenes del sensor según la orientación del sensor, pero no controla la rotación del dispositivo ni el escalamiento de la vista previa.
El escalamiento y la rotación se pueden codificar en una transformación de matriz.
Para aprender a escalar y rotar correctamente un TextureView, consulta Cómo brindar compatibilidad con superficies redimensionables en tu app de cámara.
Rotación relativa
La rotación relativa del sensor de la cámara es la cantidad de rotación necesaria para alinear la salida del sensor de la cámara con la orientación del dispositivo.
Los componentes, como SurfaceView y TextureView, usan la rotación relativa para determinar los factores de escala x e y de la imagen de vista previa. También se usa para especificar la rotación del búfer de imágenes del sensor.
Las clases CameraCharacteristics y Surface permiten calcular la rotación relativa del sensor de la cámara:
/** * Computes rotation required to transform the camera sensor output orientation to the * device's current orientation in degrees. * * @param characteristics The CameraCharacteristics to query for the sensor orientation. * @param surfaceRotationDegrees The current device orientation as a Surface constant. * @return Relative rotation of the camera sensor output. */ public fun computeRelativeRotation( characteristics: CameraCharacteristics, surfaceRotationDegrees: Int ): Int { val sensorOrientationDegrees = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0 // Reverse device orientation for back-facing cameras. val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT ) 1 else -1 // Calculate desired orientation relative to camera orientation to make // the image upright relative to the device orientation. return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360 }
Métricas de la ventana
No se debe usar el tamaño de la pantalla para determinar las dimensiones del visor de la cámara. Es posible que la app de la cámara se ejecute en una parte de la pantalla, ya sea en el modo multiventana en dispositivos móviles o en el modo de formato libre en ChromeOS.
WindowManager#getCurrentWindowMetrics()
(agregado en el nivel de API 30) devuelve el tamaño de la ventana de la aplicación, en lugar del
tamaño de la pantalla. Los métodos WindowMetricsCalculator#computeCurrentWindowMetrics() y WindowInfoTracker#currentWindowMetrics() de la biblioteca de WindowManager de Jetpack ofrecen una compatibilidad similar con retrocompatibilidad con el nivel de API 14.
Rotación de 180 grados
Una rotación de 180 grados de un dispositivo (por ejemplo, de orientación natural a orientación natural al revés) no activa la devolución de llamada onConfigurationChanged(). Como resultado, la vista previa de la cámara podría estar al revés.
Para detectar una rotación de 180 grados, implementa un DisplayListener y verifica la rotación del dispositivo con una llamada a Display#getRotation() en la devolución de llamada onDisplayChanged().
Recursos exclusivos
Antes de Android 10, solo la actividad visible más importante en un entorno multiventana estaba en el estado RESUMED. Esto confundía a los usuarios porque el sistema no indicaba qué actividad se había reanudado.
En Android 10 (nivel de API 29), se introdujo la reanudación múltiple, en la que todas las actividades visibles se encuentran en el estado RESUMED. Las actividades visibles aún pueden ingresar al estado PAUSED si, por ejemplo, una actividad transparente se encuentra sobre esta o si la actividad no se puede enfocar, como en el modo de pantalla en pantalla (consulta Compatibilidad con pantalla en pantalla).
Una aplicación que use la cámara, el micrófono o cualquier recurso exclusivo o singleton en el nivel de API 29 o superior debe admitir la reanudación múltiple. Por ejemplo, si tres actividades reanudadas quieren usar la cámara, solo una puede acceder a este recurso exclusivo. Cada actividad debe implementar una devolución de llamada onDisconnected() para mantenerse al tanto del acceso preferente a la cámara por parte de una actividad de mayor prioridad.
Para obtener más información, consulta Reanudación múltiple.
Recursos adicionales
- Para ver un ejemplo de Camera2, consulta la app de Camera2Basic en GitHub.
- Para obtener información sobre el caso de uso de la vista previa de CameraX, consulta Cómo implementar una vista previa de CameraX.
- Para ver un ejemplo de implementación de la vista previa de la cámara de CameraX, consulta el repositorio CameraXBasic en GitHub.
- Para obtener información sobre la vista previa de la cámara en ChromeOS, consulta Orientaciones de la cámara.
- Para obtener información sobre el desarrollo para dispositivos plegables, consulta Más información sobre dispositivos plegables.