Android Camera旋转角度

首先理解一下 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。

  • 预览数据显示旋转角度:根据相机旋转角度屏幕显示旋转角度,我们即可计算预览数据显示旋转角度。顺时针旋转方向。

二、发现规律

分别选择后置和前置摄像头,将手机以各个角度握持,获得相机旋转角度屏幕显示旋转角度预览数据如下,那么对于预览数据后前后置摄像头的镜像差别,我们可以总结出预览数据显示旋转角度

相机相机旋转角度屏幕显示旋转角度预览帧数据预览数据显示旋转角度
后置90Surface.ROTATION_0 (portrait)

后置_portrait

90
后置90Surface.ROTATION_90 (landscape)

 后置_landscape

0
后置90Surface.ROTATION_180 (reverse-portrait)

后置_reverse-portrait

270
后置90Surface.ROTATION_270 (reverse-landscape)

 后置_reverse-landscape

180
前置270Surface.ROTATION_0 (portrait)

 (采集后的未镜像图像)前置_portrait

(先内部镜像)90
前置270Surface.ROTATION_90 (landscape)

  (采集后的未镜像图像)前置_landscape

内部镜像)0
前置270Surface.ROTATION_180 (reverse-portrait)

  (采集后的未镜像图像)前置_reverse-portrait

内部镜像)270
前置270Surface.ROTATION_270 (reverse-landscape)

(采集后的未镜像图像)前置_reverse-landscape

内部镜像)180
  • 内部镜像:
    对于后置摄像头,预览数据需要经过旋转后才能显示成正常效果,以竖屏情况为例:

    原始图预览数据显示旋转角度效果图

     后置_portrait

    90

    正常预览

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

    原始图镜像图预览数据显示旋转角度效果图

    前置_portrait

    ​镜像后数据

    90

    正常

三、总结

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

  • 映射关系

    相机相机旋转角度屏幕显示旋转角度预览数据显示旋转角度
    后置90Surface.ROTATION_0 (portrait)90
    后置90Surface.ROTATION_90 (landscape)0
    后置90Surface.ROTATION_180 (reverse-portrait)270
    后置90Surface.ROTATION_270 (reverse-landscape)180
    前置270Surface.ROTATION_0 (portrait)90
    前置270Surface.ROTATION_90 (landscape)0
    前置270Surface.ROTATION_180 (reverse-portrait)270
    前置270Surface.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.
     *
     * <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);
    }
    ...
}

  • 8
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值