Android Camera旋转角度
时间:2023-05-28 15:37:00
首先要理解 info.orientation 官方解释
官方定义:orientation 表示相机图像的方向。当相机图像顺时针旋转到设备自然方向一致时,其值为角度。例如,假设设备是垂直屏幕。横屏安装后置相机传感器。当你面向屏幕时,如果后置相机传感器顶边的和设备自然方向的右边是平行的,则后置相机的 orientation 是 90。如果前置相机传感器的顶部与设备自然方向的右侧平行,则前置相机 orientation 是 270。
我画了一简单的草图,我用两部手机测试,一部是华为荣耀 6plus 、另一个是定制的 T6A,测试时,将手机固定在垂直屏幕应用程序上
另一款 Android 机 T6A ,相机的位置很特别,我得到的 info.orientation = 0; 这意味着我不需要旋转。捕获的数据与屏幕方向一致
特别说明:
对于后置相机,只需旋转后置相机 orientation 即 可与屏幕方向保持一致;
对于前置相机的预览方向,相机预览的图像是相机收集的图像的镜像。由于系统对前置相机收集的图像进行了镜像,因此需要旋转 270-180,也是 90 与屏幕方向保持一致。
一、适应目标
根据相机旋转角度以及屏幕显示旋转角度选择相机预览数据显示View上的预览数据显示旋转角度,眼睛直接看到的真实画面与手机屏幕上显示的画面效果相同。
-
相机旋转角:相机成像相对于手机的旋转角度,如果设备已经安装在相机上,则相机相对于设备的旋转角度是固定的。
- Camera API获取方式
Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(cameraId, info); Log.i(TAG, "orientation: " info.orientation);
- Camera2 API获取方式
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); Log.i(TAG, "orientation: " sensorOrientation);
-
屏幕显示旋转角度: Activity#getWindowManager().getDefaultDisplay().getRotation()的值,可以是ROTATION_0、ROTATION_90、ROTATION_180、ROTATION_270。
-
旋转角度显示预览数据:根据相机旋转角度和屏幕显示旋转角度,我们可以计算预览数据显示旋转角度。顺时针旋转方向。
二、发现规律
选择后置摄像头和前置摄像头,从各个角度握住手机相机旋转角度、屏幕显示旋转角度、预览数据如下,我们可以总结预览数据后前后摄像头的镜像差异预览数据显示旋转角度:
相机 | 相机旋转角度 | 屏幕显示旋转角度 | 预览帧数据 | 预览数据显示旋转角度 |
---|---|---|---|---|
后置 | 90 | Surface.ROTATION_0 (portrait) |
后置_portrait |
90 |
后置 | 90 | Surface.ROTATION_90 (landscape) |
后置_landscape |
0 |
后置 | 90 | Surface.ROTATION_180 (reverse-portrait) |
后置_reverse-portrait |
270 |
后置 | 90 | Surface.ROTATION_270 (reverse-landscape) |
后置_reverse-landscape |
180 |
前置 | 270 | Surface.ROTATION_0 (portrait) |
(收集后的未镜像图像)前置_portrait |
(先内部镜像)90 |
前置 | 270 | Surface.ROTATION_90 (landscape) |
(收集后的未镜像图像)前置_landscape |
(内部镜像)0 |
前置 | 270 | Surface.ROTATION_180 (reverse-portrait) |
(收集后的未镜像图像)前置_reverse-portrait |
(内部镜像)270 |
前置 | 270 | Surface.ROTATION_270 (reverse-landscape) |
(收集后的未镜像图像)前置_reverse-landscape |
(内部镜像)180 |
-
内部镜像:
对于后置摄像头,预览数据需要旋转以显示正常效果:原始图 预览数据显示旋转角度 效果图 后置_portrait
90 正常预览
对于前置摄像头,在旋转之前,我们必须旋转左右镜像才能得到预期的结果。以垂直屏幕为例:
原始图 镜像图 预览数据显示旋转角度 效果图 前置_portrait
镜像后数据
90 正常
三、总结
遍历分析了以上的所有情况后,我们也得出了以下结果:
-
映射关系:
相机 相机旋转角度 屏幕显示旋转角度 预览数据显示旋转角度 后置 90 Surface.ROTATION_0 (portrait)
90 后置 90 Surface.ROTATION_90 (landscape)
0 后置 90 Surface.ROTATION_180 (reverse-portrait)
270 后置 90 Surface.ROTATION_270 (reverse-landscape)
180 前置 270 Surface.ROTATION_0 (portrait)
90 前置 270 Surface.ROTATION_90 (landscape)
0 前置 270 Surface.ROTATION_180 (reverse-portrait)
270 前置 270 Surface.ROTATION_270 (reverse-landscape)
180 -
总结函数:
如果只是简单总结下上面的数值映射关系,我们可以发现预览数据显示旋转角度似乎只和屏幕显示旋转角度有关,于是可以得出以下函数:private int getCameraOri(int rotation) { switch (rotation) { case Surface.ROTATION_0: return 90; case Surface.ROTATION_90: return 0; case Surface.ROTATION_180: return 270; case Surface.ROTATION_270: return 180; default: return 0; } }
但是真的足够了吗?可以看到,这个函数的入参仅仅只有
rotation
,并未考虑到cameraId
和cameraOrientation
,我们重新思考下:-
后置摄像头
对于后置摄像头,旋转角度是90度,且不考虑镜像关系,也就是说:- 在
rotation
是Surface.ROTATION_0
时,预览数据需顺时针旋转90度(cameraOrientation)
; - 在
rotation
是Surface.ROTATION_90
时,预览数据不需要旋转,即旋转0度(cameraOrientation - 90)
; - 在
rotation
是Surface.ROTATION_180
时,预览数据需顺时针旋转270度(cameraOrientation - 180) + 360
; - 在
rotation
是Surface.ROTATION_270
时,预览数据需顺时针旋转180度(cameraOrientation - 270) + 360
;
屏幕显示旋转角度每增加90度,预览数据显示旋转角度减少90度。
因此后置摄像头的适配代码如下:
int degrees = rotation * 90; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; default: break; } // result 即为在camera.setDisplayOrientation(int)的参数 result = (info.orientation - degrees + 360) % 360;
- 在
-
前置摄像头
对于前置摄像头,旋转角度是270度,也就是说:- 在
rotation
是Surface.ROTATION_0
时,预览数据需在左右镜像后顺时针旋转270度(cameraOrientation)
; - 在
rotation
是Surface.ROTATION_90
时,预览数据需在左右镜像后不需要旋转,即旋转0度(cameraOrientation + 90) - 360
; - 在
rotation
是Surface.ROTATION_180
时,预览数据需在左右镜像后顺时针旋转90度(cameraOrientation + 180) - 360
; - 在
rotation
是Surface.ROTATION_270
时,预览数据需在左右镜像后顺时针旋转180度(cameraOrientation + 270) - 360
;
其中系统内部已经帮我们处理了镜像操作(可见下面第二段代码的注释),我们只需要传入旋转角度即可。
- 在
-
综上,可得出Camera旋转角度适配代码如下:
-
displayOrientation = getCameraOri(rotation,mCameraId);
camera.setDisplayOrientation(displayOrientation);
private int getCameraOri(int rotation, int cameraId) {
int degrees = rotation * 90;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
default:
break;
}
// result 即为在camera.setDisplayOrientation(int)的参数
int result;
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360;
} else {
result = (info.orientation - degrees + 360) % 360;
}
return result;
}
事实上,android.hardware.Camera
类中早已帮我们实现了适配方案,且在public native final void setDisplayOrientation(int degrees)
的注释中说明了在旋转前会做一次镜像操作:
Set the clockwise rotation of preview display in degrees. This affects
the preview frames and the picture displayed after snapshot. This method
is useful for portrait mode applications. Note that preview display of
front-facing cameras is flipped horizontally before the rotation, that
is, the image is reflected along the central vertical axis of the camera
sensor. So the users can see themselves as looking into a mirror.
/**
* Set the clockwise rotation of preview display in degrees. This affects
* the preview frames and the picture displayed after snapshot. This method
* is useful for portrait mode applications. Note that preview display of
* front-facing cameras is flipped horizontally before the rotation, that
* is, the image is reflected along the central vertical axis of the camera
* sensor. So the users can see themselves as looking into a mirror.
*
* This does not affect the order of byte array passed in {@link
* PreviewCallback#onPreviewFrame}, JPEG pictures, or recorded videos. This
* method is not allowed to be called during preview.
*
*
If you want to make the camera image show in the same orientation as
* the display, you can use the following code.
*
* public static void setCameraDisplayOrientation(Activity activity,
* int cameraId, android.hardware.Camera camera) {
* android.hardware.Camera.CameraInfo info =
* new android.hardware.Camera.CameraInfo();
* android.hardware.Camera.getCameraInfo(cameraId, info);
* int rotation = activity.getWindowManager().getDefaultDisplay()
* .getRotation();
* int degrees = 0;
* switch (rotation) {
* case Surface.ROTATION_0: degrees = 0; break;
* case Surface.ROTATION_90: degrees = 90; break;
* case Surface.ROTATION_180: degrees = 180; break;
* case Surface.ROTATION_270: degrees = 270; break;
* }
*
* int result;
* if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
* result = (info.orientation + degrees) % 360;
* result = (360 - result) % 360; // compensate the mirror
* } else { // back-facing
* result = (info.orientation - degrees + 360) % 360;
* }
* camera.setDisplayOrientation(result);
* }
*
*
* Starting from API level 14, this method can be called when preview is
* active.
*
*
Note: Before API level 24, the default value for orientation is 0. Starting in
* API level 24, the default orientation will be such that applications in forced-landscape mode
* will have correct preview orientation, which may be either a default of 0 or
* 180. Applications that operate in portrait mode or allow for changing orientation must still
* call this method after each orientation change to ensure correct preview display in all
* cases.
*
* @param degrees the angle that the picture will be rotated clockwise.
* Valid values are 0, 90, 180, and 270.
* @throws RuntimeException if setting orientation fails; usually this would
* be because of a hardware or other low-level error, or because
* release() has been called on this Camera instance.
* @see #setPreviewDisplay(SurfaceHolder)
*/
public native final void setDisplayOrientation(int degrees);
运行效果也确实如此,我们也可以翻下android源码看看内部的实现:
- android.hardware.Camera.java
public native final void setDisplayOrientation(int degrees);
- frameworks\base\core\jni\android_hardware_Camera.cpp
static void android_hardware_Camera_setDisplayOrientation(JNIEnv *env, jobject thiz,
jint value)
{
ALOGV("setDisplayOrientation");
sp camera = get_native_camera(env, thiz, NULL);
if (camera == 0) return;
if (camera->sendCommand(CAMERA_CMD_SET_DISPLAY_ORIENTATION, value, 0) != NO_ERROR) {
jniThrowRuntimeException(env, "set display orientation failed");
}
}
- frameworks\av\camera\CameraClient.cpp
status_t CameraClient::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) {
...
if (cmd == CAMERA_CMD_SET_DISPLAY_ORIENTATION) {
// Mirror the preview if the camera is front-facing.
orientation = getOrientation(arg1, mCameraFacing == CAMERA_FACING_FRONT);
if (orientation == -1) return BAD_VALUE;
if (mOrientation != orientation) {
mOrientation = orientation;
if (mPreviewWindow != 0) {
mHardware->setPreviewTransform(mOrientation);
}
}
return OK;
}
...
return mHardware->sendCommand(cmd, arg1, arg2);
}
int CameraClient::getOrientation(int degrees, bool mirror) {
if (!mirror) {
if (degrees == 0) return 0;
else if (degrees == 90) return HAL_TRANSFORM_ROT_90;
else if (degrees == 180) return HAL_TRANSFORM_ROT_180;
else if (degrees == 270) return HAL_TRANSFORM_ROT_270;
} else { // Do mirror (horizontal flip)
if (degrees == 0) { // FLIP_H and ROT_0
return HAL_TRANSFORM_FLIP_H;
} else if (degrees == 90) { // FLIP_H and ROT_90
return HAL_TRANSFORM_FLIP_H | HAL_TRANSFORM_ROT_90;
} else if (degrees == 180) { // FLIP_H and ROT_180
return HAL_TRANSFORM_FLIP_V;
} else if (degrees == 270) { // FLIP_H and ROT_270
return HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_ROT_90;
}
}
ALOGE("Invalid setDisplayOrientation degrees=%d", degrees);
return -1;
}
这里有一段关键代码,对于前置摄像头的不同旋转角度,设置水平镜像、垂直镜像、旋转角度。
if (degrees == 0) { // FLIP_H and ROT_0
return HAL_TRANSFORM_FLIP_H;
} else if (degrees == 90) { // FLIP_H and ROT_90
return HAL_TRANSFORM_FLIP_H | HAL_TRANSFORM_ROT_90;
} else if (degrees == 180) { // FLIP_H and ROT_180
return HAL_TRANSFORM_FLIP_V;
} else if (degrees == 270) { // FLIP_H and ROT_270
return HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_ROT_90;
}
其中HAL_TRANSFORM_XXX
的定义如下:
/**
* Transformation definitions
*
* IMPORTANT NOTE:
* HAL_TRANSFORM_ROT_90 is applied CLOCKWISE and AFTER HAL_TRANSFORM_FLIP_{H|V}.
*
*/
typedef enum android_transform {
/* flip source image horizontally (around the vertical axis) */
HAL_TRANSFORM_FLIP_H = 0x01,
/* flip source image vertically (around the horizontal axis)*/
HAL_TRANSFORM_FLIP_V = 0x02,
/* rotate source image 90 degrees clockwise */
HAL_TRANSFORM_ROT_90 = 0x04,
/* rotate source image 180 degrees */
HAL_TRANSFORM_ROT_180 = 0x03,
/* rotate source image 270 degrees clockwise */
HAL_TRANSFORM_ROT_270 = 0x07,
/* don't use. see system/window.h */
HAL_TRANSFORM_RESERVED = 0x08,
} android_transform_t;
- frameworks\av\services\camera\libcameraservice\device1\CameraHardwareInterface.cpp
status_t CameraHardwareInterface::setPreviewTransform(int transform) {
int rc = OK;
mPreviewTransform = transform;
if (mPreviewWindow != nullptr) {
rc = native_window_set_buffers_transform(mPreviewWindow.get(),
mPreviewTransform);
}
return rc;
}
- frameworks\native\libs\nativewindow\include\system\window.h
/*
* native_window_set_buffers_transform(..., int transform)
* All buffers queued after this call will be displayed transformed according
* to the transform parameter specified.
*/
static inline int native_window_set_buffers_transform(
struct ANativeWindow* window,
int transform)
{
return window->perform(window, NATIVE_WINDOW_SET_BUFFERS_TRANSFORM,
transform);
}
- frameworks/native/libs/gui/Surface.cpp
// ANativeWindow::perform 函数指向本地的 hook_perform 函数
Surface::Surface(
const sp& bufferProducer,
bool controlledByApp)
: mGraphicBufferProducer(bufferProducer),
mCrop(Rect::EMPTY_RECT),
mGenerationNumber(0),
mSharedBufferMode(false),
mAutoRefresh(false),
mSharedBufferSlot(BufferItem::INVALID_BUFFER_SLOT),
mSharedBufferHasBeenQueued(false)
{
...
ANativeWindow::perform = hook_perform;
...
}
int Surface::hook_perform(ANativeWindow* window, int operation, ...) {
va_list args;
va_start(args, operation);
Surface* c = getSelf(window);
int result = c->perform(operation, args);
va_end(args);
return result;
}
int Surface::perform(int operation, va_list args)
{
int res = NO_ERROR;
switch (operation) {
...
case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM:
res = dispatchSetBuffersTransform(args);
break;
...
}
}
int Surface::dispatchSetBuffersTransform(va_list args) {
uint32_t transform = va_arg(args, uint32_t);
return setBuffersTransform(transform);
}
// 最终是设置了mTransform的值
int Surface::setBuffersTransform(uint32_t transform)
{
ATRACE_CALL();
ALOGV("Surface::setBuffersTransform");
Mutex::Autolock lock(mMutex);
mTransform = transform;
return NO_ERROR;
}
// 再看下mTransform在哪里被使用
int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
...
IGraphicBufferProducer::QueueBufferInput input(timestamp, isAutoTimestamp,
mDataSpace, crop, mScalingMode, mTransform ^ mStickyTransform,
fence, mStickyTransform);
if (mConnectedToCpu || mDirtyRegion.bounds() == Rect::INVALID_RECT) {
input.setSurfaceDamage(Region::INVALID_REGION);
} else {
// Here we do two things:
// 1) The surface damage was specified using the OpenGL ES convention of
// the origin being in the bottom-left corner. Here we flip to the
// convention that the rest of the system uses (top-left corner) by
// subtracting all top/bottom coordinates from the buffer height.
// 2) If the buffer is coming in rotated (for example, because the EGL
// implementation is reacting to the transform hint coming back from
// SurfaceFlinger), the surface damage needs to be rotated the
// opposite direction, since it was generated assuming an unrotated
// buffer (the app doesn't know that the EGL implementation is
// reacting to the transform hint behind its back). The
// transformations in the switch statement below apply those
// complementary rotations (e.g., if 90 degrees, rotate 270 degrees).
int width = buffer->width;
int height = buffer->height;
bool rotated90 = (mTransform ^ mStickyTransform) &
NATIVE_WINDOW_TRANSFORM_ROT_90;
if (rotated90) {
std::swap(width, height);
}
Region flippedRegion;
for (auto rect : mDirtyRegion) {
int left = rect.left;
int right = rect.right;
int top = height - rect.bottom; // Flip from OpenGL convention
int bottom = height - rect.top; // Flip from OpenGL convention
switch (mTransform ^ mStickyTransform) {
case NATIVE_WINDOW_TRANSFORM_ROT_90: {
// Rotate 270 degrees
Rect flippedRect{top, width - right, bottom, width - left};
flippedRegion.orSelf(flippedRect);
break;
}
case NATIVE_WINDOW_TRANSFORM_ROT_180: {
// Rotate 180 degrees
Rect flippedRect{width - right, height - bottom,
width - left, height - top};
flippedRegion.orSelf(flippedRect);
break;
}
case NATIVE_WINDOW_TRANSFORM_ROT_270: {
// Rotate 90 degrees
Rect flippedRect{height - bottom, left,
height - top, right};
flippedRegion.orSelf(flippedRect);
break;
}
default: {
Rect flippedRect{left, top, right, bottom};
flippedRegion.orSelf(flippedRect);
break;
}
}
}
input.setSurfaceDamage(flippedRegion);
}
status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
if (err != OK) {
ALOGE("queueBuffer: error queuing buffer to SurfaceTexture, %d", err);
}
...
}