Android Camera旋转角度分析

开发过Android自定义相机的朋友估计都被相机的各种乱七八糟的旋转角度适配坑过,本文将对Camera的各种角度进行解析。

一、适配目标

根据相机旋转角度以及屏幕显示旋转角度选择相机预览数据显示到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)
Android Camera旋转角度分析_第1张图片
后置_portrait
90
后置 90 Surface.ROTATION_90 (landscape)
Android Camera旋转角度分析_第2张图片
后置_landscape
0
后置 90 Surface.ROTATION_180 (reverse-portrait)
Android Camera旋转角度分析_第3张图片
后置_reverse-portrait
270
后置 90 Surface.ROTATION_270 (reverse-landscape)
Android Camera旋转角度分析_第4张图片
后置_reverse-landscape
180
前置 270 Surface.ROTATION_0 (portrait)
Android Camera旋转角度分析_第5张图片
前置_portrait
内部镜像)90
前置 270 Surface.ROTATION_90 (landscape)
Android Camera旋转角度分析_第6张图片
前置_landscape
内部镜像)0
前置 270 Surface.ROTATION_180 (reverse-portrait)
Android Camera旋转角度分析_第7张图片
前置_reverse-portrait
内部镜像)270
前置 270 Surface.ROTATION_270 (reverse-landscape)
Android Camera旋转角度分析_第8张图片
前置_reverse-landscape
内部镜像)180
  • 内部镜像:
    对于后置摄像头,预览数据需要经过旋转后才能显示成正常效果,以竖屏情况为例:

    原始图 预览数据显示旋转角度 效果图
    Android Camera旋转角度分析_第9张图片
    后置_portrait
    90
    Android Camera旋转角度分析_第10张图片
    正常预览

    而对于前置摄像头,在旋转之前,我们要先进行左右镜像再旋转才能得到期望结果,也以竖屏情况为例:

    原始图 镜像图 预览数据显示旋转角度 效果图
    Android Camera旋转角度分析_第11张图片
    前置_portrait
    Android Camera旋转角度分析_第12张图片
    镜像后数据
    90
    Android Camera旋转角度分析_第13张图片
    正常

三、总结

遍历分析了以上的所有情况后,我们也得出了以下结果:

  • 映射关系

    相机 相机旋转角度 屏幕显示旋转角度 预览数据显示旋转角度
    后置 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,并未考虑到cameraIdcameraOrientation,我们重新思考下:

    • 后置摄像头
      对于后置摄像头,旋转角度是90度,且不考虑镜像关系,也就是说:

      • rotationSurface.ROTATION_0时,预览数据需顺时针旋转90度(cameraOrientation)
      • rotationSurface.ROTATION_90时,预览数据不需要旋转,即旋转0度(cameraOrientation - 90)
      • rotationSurface.ROTATION_180时,预览数据需顺时针旋转270度(cameraOrientation - 180) + 360
      • rotationSurface.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度,也就是说:

      • rotationSurface.ROTATION_0时,预览数据需在左右镜像后顺时针旋转270度(cameraOrientation)
      • rotationSurface.ROTATION_90时,预览数据需在左右镜像后不需要旋转,即旋转0度(cameraOrientation + 90) - 360
      • rotationSurface.ROTATION_180时,预览数据需在左右镜像后顺时针旋转90度(cameraOrientation + 180) - 360
      • rotationSurface.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);
    }
    ...
}

四、示例代码

  • 示例代码
    https://github.com/wangshengyang1996/GLCameraDemo
    可在Demo的CameraHelper类中查看相关适配操作

你可能感兴趣的:(Android Camera旋转角度分析)