首先要理解 info.orientation 官方解释
官方定义:orientation 表示相机图像的方向。当相机图像顺时针旋转到设备自然方向一致时,其值为角度。例如,假设设备是垂直屏幕。横屏安装后置相机传感器。当你面向屏幕时,如果后置相机传感器顶边的和设备自然方向的右边是平行的,则后置相机的 orientation 是 90。如果前置相机传感器的顶部与设备自然方向的右侧平行,则前置相机 orientation 是 270。
我画了一简单的草图,我用两部手机测试,一部是华为荣耀 6plus 、另一个是定制的 T6A,测试时,将手机固定在垂直屏幕应用程序上
另一款 Android 机 T6A ,相机的位置很特别,我得到的 info.orientation = 0; 这意味着我不需要旋转。捕获的数据与屏幕方向一致
特别说明:
对于后置相机,只需旋转后置相机 orientation 即 可与屏幕方向保持一致;
对于前置相机的预览方向,相机预览的图像是相机收集的图像的镜像。由于系统对前置相机收集的图像进行了镜像,因此需要旋转 270-180,也是 90 与屏幕方向保持一致。
一、适应目标
根据以及选择相机预览数据显示View上的,眼睛直接看到的真实画面与手机屏幕上显示的画面效果相同。
-
相机成像相对于手机的旋转角度,如果设备已经安装在相机上,则相机相对于设备的旋转角度是固定的。
Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(cameraId, info); Log.i(TAG, "orientation: " info.orientation);
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
; 其中系统内部已经帮我们处理了镜像操作(可见下面第二段代码的注释),我们只需要传入旋转角度即可。
- 在
-
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. , 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.
*
* <p>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.
*
* <p>If you want to make the camera image show in the same orientation as
* the display, you can use the following code.
* <pre>
* 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);
* }
* </pre>
*
* <p>Starting from API level 14, this method can be called when preview is
* active.
*
* <p><b>Note: </b>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.</p>
*
* @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源码看看内部的实现:
public native final void setDisplayOrientation(int degrees);
static void android_hardware_Camera_setDisplayOrientation(JNIEnv *env, jobject thiz,
jint value)
{
ALOGV("setDisplayOrientation");
sp<Camera> 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");
}
}
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;
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;
}
/*
* 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);
}
// ANativeWindow::perform 函数指向本地的 hook_perform 函数
Surface::Surface(
const sp<IGraphicBufferProducer>& 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);
}
...
}