在纵向模式下设置相机setDisplayOrientation()会破坏宽高比。

15

我正试图让相机预览在纵向模式下正常工作,其中活动本身允许正常更改方向(即不锁定为横向)。

然而,使用setDisplayOrientation()会严重破坏预览的行为。

谷歌自己的ApiDemos可以证明这一点。第一幅图像基于android-17版本的ApiDemos示例项目中的CameraPreview,我所做的唯一更改是从此活动的清单中删除了android:orientation="landscape" 。以下是Nexus 4上运行Android 4.2,摄像头对准8.5英寸的方形纸张时显示的截图:

Nexus 4 ApiDemos 预览,纵向模式

从那个预览中不显然的是它被旋转了90度。您在图像右侧看到的穿袜子的脚实际上在方格下面。

至少对于横向模式,解决方案是使用setDisplayOrientation()。但是,如果您修改CameraPreviewPreview内部类以具有mCamera.setDisplayOrientation(90);,您将得到以下结果:

Nexus 4 ApiDemos 预览,纵向模式,使用 setDisplayOrientation(90)

值得注意的是,方块不再是方形。

这与之前使用相同的SurfaceView大小。我已经在ApiDemos之外的一些其他代码中重现了此场景,并且我在其中的TextureView中也获得了相同的行为。

我短暂地认为问题可能是getDisplaySupportedPreviewSizes()可能基于setDisplayOrientation()返回不同的值,但是快速测试表明这并非如此。

有人知道如何在纵向模式下使用setDisplayOrientation()而不破坏推送到预览中的图像的纵横比吗?

谢谢!


我发现了这个链接:https://dev59.com/7lnUa4cB1Zd3GeqPY0jj。如果有必要的话,告诉我一声,我会写一个完整的答案。 - idan
@idan:抱歉,在“ApiDemos”中的“CameraPreview”上没有操作栏。它已经使用了“Window.FEATURE_NO_TITLE”。不过还是谢谢你! - CommonsWare
1
我最近一直在自己努力工作。需要记住的一件事(也许你已经知道了)是预览大小在旋转到纵向时不会交换尺寸。如果默认相机方向是横向的,则宽度将是横向手机的宽度,而不管您实际上是否处于横向状态,因此在检查正确的预览大小时可能需要反转宽度/高度参数。 - Kevin Coppock
1
@kcoppock:嗯,你的措辞有些帮助。我之前看到过那个评论,但我认为它意味着反转setPreviewSize()的高度/宽度,这是不起作用的(除非反转后的大小恰好是有效的预览大小)。然而,通过反转预览View本身(例如,SurfaceView)的高度/宽度,我离正确的纵横比更接近了一些。它还不够准确,但它不像上面展示的那样过分了。谢谢! - CommonsWare
没问题!设置相机预览要比它应该的复杂得多(这可能是为什么大多数条形码扫描器应用程序似乎锁定为横向)。还有一个问题,即在从横向到反向横向时不会获得onCreate / onDestroy事件,因此在这种情况下您最终会获得倒置预览,至今我还没有找到(非hackish)解决方法。如果您找到了这个方面的可靠解决方案,我很乐意听取。祝你好运! - Kevin Coppock
3个回答

15

好的,我想我已经弄清楚了。这个谜题有几个要素,我感谢@kcoppock提供的帮助,帮助我解决了其中一些问题。

首先,在确定选择哪种预览尺寸时,需要考虑方向。例如,来自ApiDemosCameraPreview中的getOptimalPreviewSize()由于他们的应用程序版本将方向锁定为横向,所以并不知道方向。如果您希望让方向浮动,则需要反转目标宽高比以匹配。因此,getOptimalPreviewSize()中有:

double targetRatio=(double)width / height;

你还需要:

if (displayOrientation == 90 || displayOrientation == 270) {
  targetRatio=(double)height / width;
}

其中displayOrientation是一个0到360的值,我从大约100行一些非常丑陋的代码中确定了这个值,这就是为什么我正在将所有这些内容包装在一个可重用的组件中,很快我会发布它。该代码基于setDisplayOrientation() JavaDocs这个StackOverflow答案

(顺便说一句,如果您在2013年7月1日之后阅读此内容,并且您在此答案中看不到链接,则请发表评论提醒我,因为我忘记了)

其次,在控制您正在使用的SurfaceView/TextureView的纵横比时,您需要考虑显示方向。来自ApiDemosCameraPreview活动具有其自己的PreviewViewGroup来处理纵横比,在这里,您需要反转纵横比以供竖屏使用:

    if (displayOrientation == 90
        || displayOrientation == 270) {
      previewWidth=mPreviewSize.height;
      previewHeight=mPreviewSize.width;
    }
    else {
      previewWidth=mPreviewSize.width;
      previewHeight=mPreviewSize.height;
    }

其中displayOrientation是相同的值(90270分别是纵向和反向纵向,注意我没有尝试让反向纵向或反向横向工作,因此可能需要进行更多微调)。

第三个问题——也是我发现令人恼火的问题——您必须在调用Camera.Parameters上的setPictureSize()之前启动预览。否则,就好像图片的宽高比应用于预览帧,会弄乱事情。

因此,我曾经有过类似以下代码的代码:

Camera.Parameters parameters=camera.getParameters();
Camera.Size pictureSize=getHost().getPictureSize(parameters);

parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
parameters.setPictureSize(pictureSize.width, pictureSize.height);
parameters.setPictureFormat(ImageFormat.JPEG);

camera.setParameters(parameters);
camera.startPreview();

那是错误的。你真正需要的是:

Camera.Parameters parameters=camera.getParameters();
Camera.Size pictureSize=getHost().getPictureSize(parameters);

parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);

camera.setParameters(parameters);
camera.startPreview();

parameters=camera.getParameters();
parameters.setPictureSize(pictureSize.width, pictureSize.height);
parameters.setPictureFormat(ImageFormat.JPEG);
camera.setParameters(parameters);

如果不进行这个更改,即使在横屏模式下也会出现拉伸的纵横比问题。对于ApiDemosCameraPreview来说,这并不是一个问题,因为它们没有拍照,所以从未调用setPictureSize()

但是,到目前为止,在Nexus 4上,我现在有了竖屏和横屏的正方形纵横比,带有浮动方向(即不锁定为横屏)。

如果我遇到其他设备需要的其他技巧,并且发布了我的CameraView/CameraFragment组件,我会尝试记得修改此问题并链接到它们。


更新 #1

当然,它肯定不可能这么简单。:-)

解决 setPictureSize() 搞砸长宽比的问题的方法是……直到你拍照为止。那时,预览会切换到错误的长宽比。

一种可能的方法是将图片限制为与预览相同(或非常接近)长宽比的图片,以便不存在任何问题。我不喜欢这个答案,因为用户应该能够获取相机提供的任何图片尺寸。

您可以通过以下方式限制损坏的范围:

  • 仅在拍照之前更新 Camera.Parameters 的图片大小等信息
  • 在拍照后恢复到拍照之前的 Camera.Parameters

这仍然会在拍照时出现错误的长宽比。我认为,长期的解决方案将是在拍照时暂时用静态图像(最后一个预览帧)替换相机预览。我会尝试这样做。


更新 #2: 我的CWAC-Camera库 的 v0.0.1 版本现已发布,包含上述代码。


1
哇,是的,我们可能误解了它们,或者相机API确实非常糟糕。setDisplayOrientation()的文档包括确定正确值的代码(为什么不将其添加到实际的Android源代码中,而不是留在Javadoc中?)这有点证实了它很混乱。感谢您提供的信息。 :) - Kevin Coppock
你好,做得很好!我想知道你的应用程序是否也可以解决我的问题:https://dev59.com/auo6XIcBkEYKwwoYNBdQ. 我现在无法使用你的代码进行测试 :( - Paul
1
@Paul:是的,我看到了你的问题,但是我对它一无所知,很抱歉。 - CommonsWare
@CommonsWare非常感谢您分享这些有用的信息和您的时间,它帮助我设置图片大小,您能否指导我如何在纵向模式下设置相机预览?我在这里发布了我的问题https://dev59.com/VOo6XIcBkEYKwwoYLhTO,但到目前为止还没有运气。 - swiftBoy
@CommonsWare,我很高兴邀请您访问此页面并查看我的问题,非常感谢:http://stackoverflow.com/questions/33950044/setdisplayorientation-in-camera-make-wrong-orientation-when-save-image?noredirect=1#comment55660353_33950044 - nguoixanh

1

大家好,我最近两周一直在为这个恶心的问题苦苦挣扎。

有几件事情需要知道:

  • 有时候你会发现黑色边框,如果ActionBar或软按钮可见,在某些设备上可能出现,我的建议是:不要去关心它或者你会变得疯狂,或者裁剪预览图,这两种方法都不好……

@CommonsWare的更改非常完美,但不能直接应用于API Level 8样本中的CameraPreview(以下是链接),因此这里是差异:

为了正确的纵向宽高比,请执行以下操作:
方法相同,只需应用差异

public void setCamera(Camera camera, boolean portrait) {
    mCamera = camera;
    this.portrait = portrait;
    if (mCamera != null) {
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        requestLayout();
    }
}

public void switchCamera(Camera camera, boolean portrait) throws IOException {
   setCamera(camera, portrait);
   camera.setPreviewDisplay(mHolder);       
   Camera.Parameters parameters = camera.getParameters();
   parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
   requestLayout();

   camera.setParameters(parameters);
}

in:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {…}

更改:

int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null) {
      previewWidth = mPreviewSize.width;
      previewHeight = mPreviewSize.height;
}

到:

if (portrait && mPreviewSize != null) {
      previewWidth = mPreviewSize.height;
      previewHeight = mPreviewSize.width;
}
else if (mPreviewSize != null){
      previewWidth = mPreviewSize.width;
      previewHeight = mPreviewSize.height;
}

in:

private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {…}

更改:

double targetRatio = (double) w / h;

至:

double targetRatio = 1;
if (portrait) {
    targetRatio= (double) w / h;
}

最终使用类似以下方式启动相机进入人像模式:
try {
     mCamera = openFrontFacingCameraGingerbread();
     if (portraitMode) {
       mCamera.setDisplayOrientation(90);
     }
     preview.setCamera(mCamera, portraitMode);
} catch (Exception e) {
     e.printStackTrace();
     handleCameraUnavailable();
}

我终于在预览时没有遇到问题,保存图片也没有问题了。由于我的活动位置被锁定,我通过OrientationChangedListener获取显示方向,以便使用正确的方向保存图像。 使用类似于以下内容: http://www.androidzeitgeist.com/2013/01/fixing-rotation-camera-picture.html 但是它们已经正确地预览和保存了。 我希望能够帮助许多人,这里有API Level 8的原始代码: https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.java 应用了更改。

-1
请在您的代码中更改相机方向,并使用setDisplayorientation()而不是getDisplayOrientation()
if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) 
{   
  camera.setDisplayOrientation(0);

} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接