首先理解一下 info.orientation 官方解釋
官方定義:orientation 表示相機影像的方向,它的值是相機影像順時針旋轉到設備自然方向一致時的角度,例如假設設備是豎屏的,后置相機傳感器是橫屏安裝的,當你面向螢屏時,如果后置相機傳感器頂邊的和設備自然方向的右邊是平行的,則后置相機的 orientation 是 90,如果前置相機傳感器頂邊和設備自然方向的右邊是平行的,則前置相機的 orientation 是 270,
畫了一個簡單的草圖,我用兩部手機進行測驗,一部是華為榮耀 6plus 、另一個是定做的 T6A,測驗的時候把手機固定為了豎屏應用

另一款 Android 機 T6A ,攝像頭位置比較特別,我獲取的 info.orientation = 0; 那意味著我不用旋轉,捕獲的資料,都是與螢屏方向一致的
特別說明:
對于后置相機,只需要旋轉后置相機的 orientation 即 90即可和螢屏方向保持一致;
對于前置相機的預覽方向,相機預覽的影像是相機采集到的影像的鏡像,由于系統對前置相機采集到的影像做了鏡像,因此需要旋轉 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.
*
* <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原始碼看看內部的實作:
- 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> 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<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);
}
...
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/291736.html
標籤:其他
上一篇:影像處理-----基本運算






