首页 > 解决方案 > Android Camera2为什么旋转后旋转后我的预览比例会发生变化?

问题描述

在我的应用程序中,我在 SurfaceView 上有一个相机预览,它几乎完全按照我的意愿工作,但有一个奇怪的烦人问题:

当我在我的索尼 Xperia 或我的测试三星 S7 上进入相机视图(模拟器看起来不错)时,它会正确显示预览。当我旋转它时,预览会正确旋转,当我将它旋转回来时,预览会失去比例并且视图会变形。在第一次旋转之后,每次它旋转回来我都会有同样的扭曲。

到目前为止有效的方法是我使用 SurfaceView 的缩放:

    FrameLayout.LayoutParams newScale = new FrameLayout.LayoutParams(width, height, Gravity.CENTER );
    mTextureView.setLayoutParams(newScale);
    mTextureView.setScaleX( xScale);
    mTextureView.setScaleY( yScale);

然后我创建了 captureSession:

  mCaptureSession = cameraCaptureSession;
            try {

                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

               mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, cropRectangle);
                mPreviewRequest = mPreviewRequestBuilder.build();
                mCaptureSession.setRepeatingRequest(mPreviewRequest, getCaptureCallback(), mBackgroundHandler);
            } catch (CameraAccessException e) { 
                // and so on.
            }

显然,此时我已经非常沮丧,所以我一直在记录我的轮换,这就是我变得超级困惑的地方。

这看起来不错:

========================
View Dimensions (720, 1184)
Sensor Dimensions: (3984, 5512)
Final size: 720, 1280
========================
View ratio: 1.7777778 previewRatio: 1.6444445 scale: 1.0
Image preview size is (720, 1184) scale is: (1.0) image size is (720, 1280)
Image scale is (1.3835341, 0.925) Max Image is (3984, 5512) cropRectangle is (441, 0 -> 3543, 5512)

然后我旋转它:

========================
View Dimensions (1184, 720)
Sensor Dimensions: (5512, 3984)
Final size: 1280, 720
========================
View ratio: 0.5625 previewRatio: 0.6081081 scale: 1.0
Image preview size is (1184, 720) scale is: (1.0) view size is (1280, 720)
Image scale is (0.925, 1.3835342) Max Image is (5512, 3984) cropRectangle is (0, 441 -> 5512, 3543)

现在我将它旋转回来:

========================
View Dimensions (720, 1184)
Sensor Dimensions: (3984, 5512)
Final size: 720, 1280
========================
View ratio: 1.7777778 previewRatio: 1.6444445 scale: 1.0
Image preview size is (720, 1184) scale is: (1.0) view size is (720, 1280)
Image scale is (1.3835341, 0.925) Max Image is (3984, 5512) cropRectangle is (441, 0 -> 3543, 5512)

那些比例和那个裁剪矩形看起来和我完全一样,但由于某种原因,最后一张图像被扭曲得更宽了,好像 x 比例更接近 1.8 或类似。我可以向任一方向旋转回到横向,它看起来很好,然后再转回纵向,一切又变胖了。

我想知道这是否是相机试图自动更正的一个特征,但我对如何进一步解决这个问题缺乏想法。我如何说服我的手机在旋转两次后显示出与最初显示的相同比例?

更新:如果我使用 setXScale 并在第二次运行时将其设置为 1.0 而不是第一次运行时的值,它看起来是正确的。但是当我比较 SurfaceView 的变换矩阵时,图像比例不好和好的时候,它们是相同的,所以这看起来像是对预览中不良缩放的补偿。我可以更改缩放的行为方式,因此初始预览太窄并且旋转后比例正确,但这并没有真正的帮助,因为我需要能够在纵向和横向之间交换而不会扭曲图像,这似乎应该是默认行为。在 Landscape 中打开视图的行为方式大致相同 - 第一次打开时会变形,然后就好了。

CaptureSession 似乎每次都正确地重新创建。

第二次更新:调用dumpsys SurfaceFlinger显示可能的原因:

-------------------------------------------------------------------------------
Layer name
           Z |  Comp Type |   Disp Frame (LTRB) |          Source Crop (LTRB)
-------------------------------------------------------------------------------
SurfaceView - com.myapp.CameraActivity#0   4294967294 |     Device |    0    0  720 1184 |    0.0    0.0  960.0  720.0
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

所以这里看起来问题是源裁剪对于显示帧的大小来说太小了,因此拉伸的图像。我的设备认为它正在获取 1280x720 的图像,但由于某种原因,源裁剪显示的尺寸较小。这可能与传感器的比例有关吗?我是否只需要从与传感器比率匹配的尺寸中挑选?

标签: androidandroid-camera2

解决方案


与 FrameLayout 相比,使用自定义纹理视图,然后通过 Surface 访问它,它将解决您的问题。

 private void createCameraPreviewSession() {
        try {
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            assert texture != null;
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());


            Surface surface = new Surface(texture);


            mPreviewRequestBuilder
                    = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);


            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                    new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            // The camera is already closed
                            if (null == mCameraDevice) {
                                return;
                            }

                            mCaptureSession = cameraCaptureSession;
                            try {
                               mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

                                setAutoFlash(mPreviewRequestBuilder);
                                mPreviewRequest = mPreviewRequestBuilder.build();
                                mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                        mCaptureCallback, mBackgroundHandler);
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void onConfigureFailed(
                                @NonNull CameraCaptureSession cameraCaptureSession) {
                            showToast("Failed");
                        }
                    }, null
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
}

并根据相机输出从特定设备检索旋转

 /**
     * Retrieves the JPEG orientation from the specified screen rotation.
     *
     * @param rotation The screen rotation.
     * @return The JPEG orientation (one of 0, 90, 270, and 360)
     */
    private int getOrientation(int rotation) {
        return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
}

推荐阅读