资讯详情

Android Camera旋转角度

首先要理解 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,并未考虑到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; 其中系统内部已经帮我们处理了镜像操作(可见下面第二段代码的注释),我们只需要传入旋转角度即可。

   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);
    }
    ...
}

标签: sp2880角度传感器sp2801角度传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台