注意:本页介绍的是 Camera2 软件包。除非您的应用需要 Camera2 中的特定低级功能,否则我们建议您使用 CameraX。CameraX 和 Camera2 都支持 Android 5.0(API 级别 21)及更高版本。
Android 上的相机和相机预览的方向不一定始终相同 设备。
相机位于设备上的固定位置,而无论设备是否 是手机、平板电脑或计算机。当设备屏幕方向改变时, 改变摄像头方向。
因此,相机应用通常会假定 设备屏幕方向和相机预览的宽高比。当 手机纵向显示,则假定相机预览画面较高 大于宽度。当手机(和相机)旋转为横向时, 相机预览画面的宽度预计会大于高度。
但是,这些假设受到了可折叠设备等新外形规格设备的挑战。 设备和显示模式 例如 多窗口模式 和 多显示屏。 可折叠设备会不改变显示大小和宽高比 屏幕方向。多窗口模式会将相机应用限制在 调整相机预览,而不考虑设备的屏幕方向。 多显示屏模式支持使用辅助显示屏,但辅助显示屏可能 与主显示屏的方向相同。
摄像头方向
通过 Android 兼容性定义 规定摄像头图像传感器“必须朝向正确方向,以便长 摄像头的尺寸与屏幕的长度方向一致也就是说, 设备处于横屏模式时,摄像头必须以 横向模式。无论设备的自然 屏幕方向;也就是说,它适用于以横屏为主的设备 纵向主要设备。”
摄像头到屏幕的排列可将摄像头的显示区域最大化 取景器。此外,图像传感器通常会以 横向宽高比,4:3 最常见。
![手机和相机传感器均处于竖屏模式。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_preview_portrait_front_facing.png?authuser=0&hl=sl)
相机传感器的自然屏幕方向为横向。在图 1 中,传感器 前置摄像头的位置(摄像头指向的方向与 会相对于手机旋转 270 度,以便符合 Android 兼容性定义。
为了向应用提供传感器旋转信息,
camera2 API 包含一个
SENSOR_ORIENTATION
常量。对于大多数手机和平板电脑,设备会报告传感器方向
前置摄像头的视角为 270 度,
使用设备背面)
传感器与设备的长边对齐。笔记本电脑摄像头通常会报告
传感器方向为 0 度或 180 度。
由于相机图像传感器会在
传感器的自然方向(横向),图片缓冲区必须旋转
SENSOR_ORIENTATION
指定的相机预览角度的度数
以设备的自然屏幕方向直立显示。对于前置摄像头,
旋转是逆时针方向的表示顺时针后置摄像头
例如,对于图 1 中的前置摄像头,图像缓冲区 相机传感器生成的图片如下所示:
![摄像头传感器已旋转至横向且包含图片
位于左上方。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_portrait_rotated_90.png?authuser=0&hl=sl)
图片必须逆时针旋转 270 度 与设备屏幕方向一致:
![摄像头传感器处于纵向模式,图片保持竖直。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_portrait_without_coordinates.png?authuser=0&hl=sl)
后置摄像头会生成一个方向相同的图像缓冲区
与上面的缓冲区相同,但 SENSOR_ORIENTATION
为 90 度。因此,
缓冲区顺时针旋转 90 度。
设备旋转
设备旋转角度是指设备从自然旋转角度 屏幕方向。例如,处于横屏模式的手机有一个设备 旋转 90 度或 270 度,具体取决于旋转方向。
摄像头传感器图像缓冲区的旋转角度必须与 设备旋转角度(以及传感器朝向角度) 相机预览会保持竖直
方向计算
相机预览的正确屏幕方向将考虑传感器 屏幕方向和设备旋转角度。
可以使用 以下公式:
rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360
其中,sign
表示前置摄像头的 1
,-1
表示后置摄像头。
对于前置摄像头,图像缓冲区逆时针旋转(从 传感器的自然方向)。对于后置摄像头,传感器 图像缓冲区将顺时针旋转。
表达式 deviceOrientationDegrees * sign + 360
会转换设备旋转角度
(例如,
将逆时针方向的 270 度转换为顺时针方向的 90 度)。模数
运算将结果缩放到小于 360 度(例如,缩放到 540)
旋转角度为 180)。
不同的 API 以不同的方式报告设备旋转角度:
Display#getRotation()
提供设备的逆时针旋转角度(从用户所在位置开始) 视图)。此值会按原样插入上述公式。OrientationEventListener#onOrientationChanged()
返回设备顺时针旋转的角度(从用户的角度)。 用于求反,以便在上述公式中使用。
前置摄像头
![横向模式下的相机预览和传感器,传感器
正上方。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_preview_landscape_counterclockwise_front_facing.png?authuser=0&hl=sl)
下面是图 2 中相机传感器生成的图像缓冲区:
![横屏模式的摄像头传感器,图片保持竖直。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_landscape.png?authuser=0&hl=sl)
缓冲区必须逆时针旋转 270 度才能针对传感器进行调整 方向(请参阅上文的镜头方向):
![摄像头传感器已旋转为纵向,图片为侧面,
右上角。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_landscape_rotated_270.png?authuser=0&hl=sl)
然后将缓冲区逆时针再旋转 90 度, 以确保设备旋转的角度正确 图 2 中的相机预览:
![摄像头传感器已旋转至横向且包含图片](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_landscape_without_coordinates.png?authuser=0&hl=sl)
以下是镜头向右变为横向的样子:
![横向模式下的相机预览和传感器,但
传感器上下颠倒。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_preview_landscape_clockwise_front_facing.png?authuser=0&hl=sl)
以下是图像缓冲区:
![摄像头传感器已旋转至横向,图片上下颠倒
。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_landscape_clockwise.png?authuser=0&hl=sl)
缓冲区必须逆时针旋转 270 度才能针对传感器进行调整 屏幕方向:
![摄像头传感器等级为纵向,图片为侧面,
。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_landscape_clockwise_rotated_270.png?authuser=0&hl=sl)
然后将缓冲区再逆时针旋转 270 度 设备旋转:
![摄像头传感器已旋转至横向且包含图片](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_landscape_without_coordinates.png?authuser=0&hl=sl)
后置摄像头
后置摄像头的传感器方向通常为 90 度(例如 从设备背面查看)。在调整相机预览方向时, 传感器图像缓冲区按照传感器的旋转量顺时针旋转 (而不是像前置摄像头那样按逆时针方向),然后将图片 缓冲区会随着设备旋转量逆时针旋转。
![横向模式下的相机预览和传感器,但
传感器上下颠倒。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_preview_landscape_clockwise_back_facing.png?authuser=0&hl=sl)
以下是图 4 中相机传感器的图像缓冲区:
![摄像头传感器已旋转至横向,图片上下颠倒
。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_landscape_clockwise.png?authuser=0&hl=sl)
缓冲区必须顺时针旋转 90 度才能针对传感器进行调整 屏幕方向:
![摄像头传感器等级为纵向,图片为侧面,
。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_landscape_clockwise_rotated_270.png?authuser=0&hl=sl)
然后,考虑到设备因素,缓冲区会逆时针旋转 270 度。 轮播:
![摄像头传感器已旋转至横向且包含图片](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/camera_sensor_landscape_without_coordinates.png?authuser=0&hl=sl)
宽高比
在设备屏幕方向发生变化时,以及当屏幕宽高比发生变化时, 在多窗口模式下调整窗口大小时,可折叠设备可折叠和展开 以及应用何时在辅助显示屏上打开。
相机传感器图像缓冲区的朝向和缩放比例必须 取景器界面元素作为界面的方向和宽高比 动态改变屏幕方向 - 无论是否改变设备 屏幕方向。
在新型设备、多窗口或多显示屏环境中,如果您的设备是 应用假定相机预览的方向与设备相同 (纵向或横向)您的预览方向可能不正确,或为横向 不正确,或两者兼有。
![展开的可折叠设备,开启人像相机预览
。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/foldable_with_rotation_problem.png?authuser=0&hl=sl)
在图 5 中,应用错误地假定设备旋转了 90 逆时针角度;因此,应用将预览画面旋转相同的幅度。
![展开的可折叠设备,相机预览画面为竖直,但出现挤压
导致出现这种问题](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/foldable_with_scaling_problem.png?authuser=0&hl=sl)
在图 6 中,应用未将图像缓冲区的宽高比调整为 使其能够适当缩放以适应相机预览界面的新尺寸 元素。
屏幕方向固定的相机应用在可折叠设备上通常会出现问题, 笔记本电脑等其他大屏设备:
![笔记本电脑上的相机预览是竖直的,但应用界面却在侧面。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/laptop_camera_preview_fixed-orientation_app.png?authuser=0&hl=sl)
在图 7 中,由于相机应用的屏幕方向为横向,因此其界面为横向 仅限纵向。取景器图像已正确朝向 相对于相机传感器的位置。
边衬区人像模式
不支持多窗口模式的相机应用
(resizeableActivity="false"
)
并限制它们的屏幕方向
(screenOrientation="portrait"
或 screenOrientation="landscape"
)
在大屏设备上可以置于边衬区纵向模式,以便正确定位
相机预览
纵向插入(边衬区)信箱模式(边衬区)且仅限纵向的应用 即使显示屏宽高比为横向也是如此。 即使应用仅在横屏模式下显示,在横屏模式下仍会显示信箱模式 显示宽高比为纵向。旋转相机图片以对齐 与应用界面、剪裁以匹配相机预览的宽高比,以及 然后进行缩放以填充整个预览区域。
当相机图片的宽高比时触发内嵌人像模式 传感器与应用主要 activity 的宽高比不匹配。
![在笔记本电脑上以正确纵向模式显示的相机预览和应用界面。
宽幅预览图片会缩放和剪裁以适合纵向尺寸
屏幕方向。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/laptop_inset_portrait_mode.png?authuser=0&hl=sl)
在图 8 中,旋转了仅限纵向的相机应用以显示界面 在笔记本电脑显示屏上保持竖直由于存在差异,应用进入信箱模式 纵向应用和横向显示之间的宽高比。相机 预览图像已旋转,以补偿应用的界面旋转(由于 图片插入,且图片已剪裁并缩放以适应 纵向显示,从而缩小视野范围。
旋转、剪裁、缩放
为显示屏上的仅支持人像的相机应用调用边衬区人像模式 横向宽高比的图片:
![笔记本电脑上的相机预览是竖直的,但应用界面却在侧面。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/laptop_inset_portrait_mode_fixed-orientation_app.png?authuser=0&hl=sl)
应用在纵向模式下会进入信箱模式:
![应用已旋转为纵向,并进入信箱模式。图片为
从左到右,从上到右](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/laptop_inset_portrait_mode_rotate_ui.png?authuser=0&hl=sl)
相机图片会旋转 90 度,以适应 app:
![传感器图片已旋转 90 度,使其保持竖直。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/laptop_inset_portrait_mode_rotate_sensor_image.png?authuser=0&hl=sl)
图片已剪裁为相机预览的宽高比,然后调整为 填满预览区域(缩小视野):
![为填充相机预览画面而剪裁过的相机图片已缩放。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/laptop_inset_portrait_mode_crop_and_scale.png?authuser=0&hl=sl)
在可折叠设备上,摄像头传感器的方向可以是纵向 而显示屏的宽高比为横向:
![在展开的宽屏显示屏上,相机预览和应用界面侧向显示。](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/unfolded_phone_camera_preview_fixed-orientation_app.png?authuser=0&hl=sl)
由于相机预览会旋转以针对传感器方向进行调整, 图片在取景器中方向正确,但仅限纵向的应用 是横向的。
插入竖屏模式只需要在纵向模式下让应用进入信箱模式 正确定位应用和相机预览:
![处于纵向模式且进入信箱模式的应用,具有相机预览功能
在可折叠设备上保持竖直](https://developer.android.google.cn/static/images/training/camera/camera2/camera-preview/unfolded_phone_inset_portrait_mode.png?authuser=0&hl=sl)
API
从 Android 12(API 级别 31)开始,应用还可以明确控制边衬区纵向
我们将使用
SCALER_ROTATE_AND_CROP
CaptureRequest
的属性
类。
默认值为
SCALER_ROTATE_AND_CROP_AUTO
,
使系统能够调用边衬区人像模式
SCALER_ROTATE_AND_CROP_90
是上述插入人像模式的行为。
并非所有设备都支持所有 SCALER_ROTATE_AND_CROP
值。获取列表
支持的值、引用
CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES
。
CameraX
Jetpack CameraX 库 创建可适应传感器方向和 完成一项简单的任务。
PreviewView
布局元素
创建相机预览,自动调整传感器方向,
设备旋转和缩放。PreviewView
会保持
将
FILL_CENTER
缩放类型,将图片居中,但可能会根据尺寸对其进行剪裁
(位于 PreviewView
中)。要对相机图片添加信箱模式,请将缩放类型设置为
FIT_CENTER
。
如需了解使用 PreviewView
创建相机预览的基础知识,请参阅
实现预览。
有关完整的示例实现,请参阅
CameraXBasic
代码库。
相机取景器
与预览用例类似, CameraViewfinder 库提供了一套工具来简化相机预览的创建过程。 它不依赖于 CameraX Core,因此您可以将其无缝集成到 现有的 Camera2 代码库。
您不必使用
Surface
您可以直接使用
CameraViewfinder
微件,用于显示 Camera2 的相机画面。
CameraViewfinder
在内部使用 TextureView
或 SurfaceView
显示相机画面,并对其应用所需的转换
正确显示取景器。
这涉及修正其宽高比、缩放和旋转。
如需从 CameraViewfinder
对象请求 Surface,您需要:
创建 ViewfinderSurfaceRequest
。
此请求包含对界面分辨率和摄像头设备的要求
信息来自 CameraCharacteristics
。
正在呼叫requestSurfaceAsync()
将请求发送到 Surface 提供程序,该提供程序可以是 TextureView
或
SurfaceView
并获取 ListenableFuture
为 Surface
。
正在呼叫markSurfaceSafeToRelease()
通知 Surface 提供程序无需 Surface,并且
可以释放资源
Kotlin
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)) }
Java
void startCamera(){ Size previewResolution = new Size(width, height); ViewfinderSurfaceRequest viewfinderSurfaceRequest = new ViewfinderSurfaceRequest(previewResolution, characteristics); ListenableFuture<Surface> surfaceListenableFuture = cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest); Futures.addCallback(surfaceListenableFuture, new FutureCallback<Surface>() { @Override public void onSuccess(Surface result) { /* create a CaptureSession using this surface as usual */ } @Override public void onFailure(Throwable t) { /* something went wrong */} }, ContextCompat.getMainExecutor(context)); }
SurfaceView
SurfaceView
是
在相机预览无法获得相机预览时,
需要处理,没有动画效果。
SurfaceView
会自动旋转摄像头传感器图像缓冲区以进行匹配
显示屏方向,同时考虑传感器方向和设备
轮替。不过,系统会缩放图像缓冲区以适应 SurfaceView
尺寸而不考虑宽高比
您必须确保图像缓冲区的纵横比与纵横比匹配
SurfaceView
的宽高比,这可以通过缩放内容
位于组件的 SurfaceView
中,
onMeasure()
方法:
(computeRelativeRotation()
源代码位于
详见下文的相对旋转)。
Kotlin
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) }
Java
@Override void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees); if (previewWidth > 0f && previewHeight > 0f) { /* Scale factor required to scale the preview to its original size on the x-axis. */ float scaleX = (relativeRotation % 180 == 0) ? (float) width / previewWidth : (float) width / previewHeight; /* Scale factor required to scale the preview to its original size on the y-axis. */ float scaleY = (relativeRotation % 180 == 0) ? (float) height / previewHeight : (float) height / previewWidth; /* Scale factor required to fit the preview to the SurfaceView size. */ float finalScale = Math.min(scaleX, scaleY); setScaleX(1 / scaleX * finalScale); setScaleY(1 / scaleY * finalScale); } setMeasuredDimension(width, height); }
如需详细了解如何以相机预览的形式实现 SurfaceView
,请参阅
相机方向。
TextureView
TextureView
性能不如
SurfaceView
- 工作量更大,但 TextureView
可以帮您最大限度地
控制相机预览。
TextureView
会根据传感器方向旋转传感器图像缓冲区,但
不处理设备旋转或预览缩放。
缩放和旋转可采用
矩阵转换。要了解如何
正确缩放和旋转 TextureView
,请参阅
在相机应用中支持可调整大小的 Surface
相对旋转
相机传感器的相对旋转角度是 使摄像头传感器输出与设备方向对齐。
SurfaceView
和 TextureView
等组件使用相对旋转
确定预览图片的 x 和 y 缩放比例。它还可用于
指定传感器图像缓冲区的旋转。
通过
CameraCharacteristics
和
Surface
类可用于计算
摄像头传感器的相对旋转:
Kotlin
/** * 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)!! // 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 }
Java
/** * 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 int computeRelativeRotation( CameraCharacteristics characteristics, int surfaceRotationDegrees ){ Integer sensorOrientationDegrees = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); // Reverse device orientation for back-facing cameras. int sign = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT ? 1 : -1; // Calculate desired orientation relative to camera orientation to make // the image upright relative to the device orientation. return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360; }
窗口指标
不应使用屏幕尺寸来确定摄像头的尺寸 取景器;相机应用可能会在屏幕的某一部分运行, 在移动设备上处于多窗口模式,在 ChromeOS 中则设为空闲模式。
WindowManager#getCurrentWindowMetrics()
(在 API 级别 30 中新增)会返回应用窗口的大小,而不是
屏幕尺寸Jetpack WindowManager 库方法
WindowMetricsCalculator#computeCurrentWindowMetrics()
和
WindowInfoTracker#currentWindowMetrics()
提供类似的支持,向后兼容 API 级别 14。
旋转 180 度
设备旋转 180 度(例如,从自然屏幕方向旋转到
自然屏幕方向倒置)不会触发
onConfigurationChanged()
回调。因此,相机预览画面可能会上下颠倒。
要检测 180 度旋转,请实现
DisplayListener
并调用以下代码来检查设备旋转角度:
Display#getRotation()
在
onDisplayChanged()
回调。
专属资源
在 Android 10 之前,只有多窗口模式下最顶层的可见 activity
处于 RESUMED
状态。这会让用户感到困惑
系统不会提供关于哪个 activity 已恢复的指示。
Android 10(API 级别 29)引入了多项恢复功能,其中所有可见 activity
处于 RESUMED
状态。可见 activity 仍可进入 PAUSED
状态,例如,透明 activity 位于 activity 之上
该 activity 不可聚焦,例如在画中画模式下(请参阅
画中画支持)。
使用摄像头、麦克风或任何专有或专有技术的应用
API 级别 29 或更高级别上的单例资源必须支持多项恢复。对于
例如,如果三个已恢复的 Activity 想要使用相机,则只有一个 Activity 能够
访问此专属资源。每个 activity 都必须实现
onDisconnected()
回调,以知晓更高的优先级对相机的抢先访问
活动。
如需了解详情,请参阅 多项恢复。
其他资源
- 如需查看 Camera2 示例,请参阅 Camera2Basic 应用 。
- 如需了解 CameraX 预览用例,请参阅 CameraX 实现预览。
- 有关 CameraX 相机预览示例实现,请参阅 CameraXBasic 代码库。
- 如需了解 ChromeOS 上的相机预览,请参阅 镜头方向。
- 如需了解如何针对可折叠设备进行开发,请参阅 了解可折叠设备。