相机在纵向模式下设置 Display Orientation() 会破坏纵横比

Posted

技术标签:

【中文标题】相机在纵向模式下设置 Display Orientation() 会破坏纵横比【英文标题】:Camera setDisplayOrientation() in Portrait Mode Breaks Aspect Ratio 【发布时间】:2013-06-12 04:10:36 【问题描述】:

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

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

这可以通过 Google 自己的 ApiDemos 来演示。第一个图像基于android-17 示例项目的android-17 版本中的CameraPreview,其中我所做的唯一更改是从清单中此活动的条目中删除android:orientation="landscape"。下图是运行 Android 4.2 的 Nexus 4 显示屏上显示的屏幕截图,摄像头指向 8.5 英寸正方形纸:

从该预览中不明显的是它旋转了 90 度。您在图片最右侧看到的穿着袜子的脚实际上在正方形的下方。

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

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

这使用与以前相同的SurfaceView 大小。我已经用ApiDemos 之外的一些其他代码重现了这种情况,并且在该代码中我得到了与TextureView 相同的行为。

我短暂地认为问题可能是getSupportedPreviewSizes() 可能会根据setDisplayOrientation() 返回不同的值,但快速测试表明情况并非如此。

有人知道如何让setDisplayOrientation() 在纵向模式下工作,而不会破坏推送到预览的图像的纵横比吗?

谢谢!

【问题讨论】:

我找到这个***.com/questions/6710573/…告诉我如果是,我会写一个完整的答案 @idan:抱歉,ApiDemos 中的CameraPreview 上没有操作栏。它已经使用Window.FEATURE_NO_TITLE。不过谢谢! 我自己最近在这方面做了很多工作。要记住的一件事(也许您已经记住了)是,当您旋转到纵向时,预览尺寸不会交换尺寸。如果默认相机方向是横向,则宽度将是手机在横向中的宽度,无论您是否实际上是 in 横向,因此当您使用时可能需要反转宽度/高度参数'正在检查正确的预览尺寸。 @kcoppock:嗯,你的措辞有点帮助。我之前看过该评论,但我认为这意味着反转setPreviewSize() 的高度/宽度,这是行不通的(除非反转的尺寸恰好是有效的预览尺寸)。但是,通过反转预览View 本身的高度/宽度(例如,SurfaceView),我可以更接近正确的纵横比。它还不准确,但并不像我上面展示的那样令人震惊。谢谢! 没问题!设置相机预览比它应该的要复杂得多(这可能是大多数条形码扫描仪应用程序似乎锁定在横向的原因)。还有一个问题是在从横向切换到反向横向时您没有收到 onCreate/onDestroy 事件,因此在这种情况下您最终会得到一个颠倒的预览,我还没有找到一个(非骇客的)解决方案到目前为止。如果您确实找到了解决此纵横比问题的可靠解决方案,我很想听听。祝你好运! 【参考方案1】:

好的,我想我想通了。众所周知的难题有几个部分,我感谢@kcoppock 的洞察力帮助我解决了其中的一些问题。

首先,在确定要选择的预览尺寸时,您需要考虑方向。例如,ApiDemosCameraPreview 中的 getOptimalPreviewSize() 忽略了方向,仅仅是因为他们的应用程序版本将方向锁定为横向。如果您希望让方向浮动,则需要反转目标纵横比以匹配。所以,getOptimalPreviewSize() 有:

double targetRatio=(double)width / height;

你还需要:

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

displayOrientation 是一个从 0 到 360 的值,我从大约 100 行一些 seriously 丑陋的代码中确定,这就是为什么我将所有这些都包装在一个可重用的组件中我很快就会发布。该代码基于the setDisplayOrientation() JavaDocs 和this *** answer。

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

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

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

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

第三个——也是我觉得很气愤的一个——你必须在调用setPictureSize()Camera.Parameters 之前开始预览。否则,就好像 图片 的纵横比应用于预览帧,搞砸了。

所以我曾经有类似以下的代码:

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

如果没有这种变化,我会得到一个拉伸的纵横比,即使在横向上也是如此。这对CameraPreviewApiDemos 来说不是问题,因为他们没有拍照,所以他们从来没有打电话给setPictureSize()

但是,到目前为止,在 Nexus 4 上,我现在有纵向和横向的方形纵横比,具有浮动方向(即未锁定为横向)。

如果我遇到其他设备所需的其他黑客攻击,我会尽量记住修改这个问题,并在它们发布时链接到我的CameraView/CameraFragment 组件。


更新 #1

当然,它不可能几乎这么简单。 :-)

解决setPictureSize() 搞砸纵横比的问题的解决方法... 直到您拍照为止。此时,预览会切换到错误的纵横比。

一种可能性是将图片限制为与预览具有相同(或非常接近)纵横比的图片,因此不存在打嗝。我不喜欢这个答案,因为用户应该能够获得相机提供的任何图片尺寸。

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

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

在拍摄照片的瞬间,这仍然会显示错误的纵横比。对此的长期解决方案 - 我认为 - 将在拍摄照片时暂时用静止图像(最后一个预览帧)替换相机预览。我最终会试试这个。


更新 #2:my CWAC-Camera library 的 v0.0.1 现已推出,包含上述代码。

【讨论】:

哇,是的,要么我们误解了它们,要么相机 API 非常非常糟糕。 setDisplayOrientation() 的文档包含确定正确值的代码(为什么不将其添加到实际的 Android 源代码而不是将其留在 Javadoc 中?)这一事实证实了它是一团糟。谢谢(你的)信息。 :) 嗨。做得好!我想知道您的应用是否也可以解决我的问题:***.com/questions/17348614/…。我现在无法使用您的代码对此进行测试:( @Paul:是的,我在那里看到了你的问题,但我对此一无所知,抱歉。 @CommonsWare 非常感谢您分享这些有用的信息和您的时间,它帮助我设置图片大小,请您指导我如何设置相机在纵向模式下预览?,我在这里***.com/questions/19176038/… 发布了我的问题,但到目前为止还没有运气。 @CommonsWare,我很高兴邀请您访问此页面并查看我的问题,非常感谢您:***.com/questions/33950044/…【参考方案2】:

好吧,伙计们, 在过去的两周里,我真的为这个 f*** 问题发疯了。 好吧,有几件事要知道:

有时您只是得到黑色边框,如果操作栏或软按钮可见,这在某些设备上是可能的,我的建议: *不在乎* 不然你会发疯,或者剪掉预览,都不好...

@CommonsWare 所做的更改非常完美,但不能直接应用于 API 级别 8 示例(下面的链接)的 CameraPreview,所以这里是 diff

要获得正确的纵向纵横比,请执行以下操作: 方法都是一样的,只是应用diff

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

在:

@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;

在:

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 级别 8 的原始代码:https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.java 应用更改的位置。

【讨论】:

【参考方案3】:

请更改代码中的摄像头方向,您应该setDisplayorientation() 而不是getDisplayOrientation()

if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) 
   
  camera.setDisplayOrientation(0);

 

【讨论】:

以上是关于相机在纵向模式下设置 Display Orientation() 会破坏纵横比的主要内容,如果未能解决你的问题,请参考以下文章

纵向模式下的相机横向预览

替代私有 -[UIDevice setOrientation:] 用于强制相机覆盖进入纵向模式

如何将 Android 默认相机应用设置为仅纵向

即使我的活动在 android 2.3.1 中处于纵向模式,如何将 framelayout 设置为横向

相机仅在人像模式下快速拍照

相机预览处于纵向模式,但拍摄的图像已旋转