Camera setDisplayOrientation() in Portrait Mode Br

2019-02-01 10:16发布

问题:

I am trying to get camera previews to work properly in portrait mode, where the activity itself is allowed to change orientation normally (i.e., not be locked to landscape).

The use of setDisplayOrientation() seriously breaks the behavior of the previews, though.

This can be demonstrated by Google's own ApiDemos. The first image is based on the CameraPreview from the android-17 edition of the ApiDemos sample project, where the only change I made was to remove android:orientation="landscape" from this activity's entry in the manifest. The following image is a screenshot of what appears on the display of a Nexus 4, running Android 4.2, with the camera pointed at a 8.5" square of paper:

What is not obvious from that preview is that it is rotated 90 degrees. The sock-clad feet that you see on the far right of the image were actually below the square.

The solution for this, at least for landscape mode, is to use setDisplayOrientation(). But, if you modify the Preview inner class of CameraPreview to have mCamera.setDisplayOrientation(90);, you get this:

Notably, the square is no longer square.

This uses the same SurfaceView size as before. I have reproduced this scenario with some other code, outside of ApiDemos, and I get the same behavior with TextureView in that code.

I thought briefly that the issue might be that getSupportedPreviewSizes() might return different values based upon setDisplayOrientation(), but a quick test suggests that this is not the case.

Anyone have any idea how to get setDisplayOrientation() to work in portrait mode without wrecking the aspect ratio of the imagery pushed over to the preview?

Thanks!

回答1:

OK, I think that I figured this out. There were a few pieces to the proverbial puzzle, and I thank @kcoppock for the insight that helped me solve some of this.

First, you need to take the orientation into account when determining what preview size to choose. For example, the getOptimalPreviewSize() from the CameraPreview of ApiDemos is oblivious to orientation, simply because their version of that app has the orientation locked to landscape. If you wish to let the orientation float, you need to reverse the target aspect ratio to match. So, where getOptimalPreviewSize() has:

double targetRatio=(double)width / height;

you also need:

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

where displayOrientation is a value from 0 to 360, that I am determining from about 100 lines of some seriously ugly code, which is why I am wrapping all of this up in a reusable component that I'll publish shortly. That code is based on the setDisplayOrientation() JavaDocs and this StackOverflow answer.

(BTW, if you are reading this after 1 July 2013, and you don't see a link in this answer to that component, post a comment to remind me, as I forgot)

Second, you need to take that display orientation into account when controlling the aspect ratio of the SurfaceView/TextureView that you are using. The CameraPreview activity from ApiDemos has its own Preview ViewGroup that handles the aspect ratio, and there you need to reverse the aspect ratio for use in portrait:

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

where displayOrientation is that same value (90 and 270 being portrait and reverse-portrait respectively, and note that I haven't tried getting reverse-portrait or reverse-landscape to work, so there may be more tweaking required).

Third -- and one that I find infuriating -- you must start the preview before calling setPictureSize() on the Camera.Parameters. Otherwise, it's as if the aspect ratio of the picture is applied to the preview frames, screwing things up.

So I used to have code resembling the following:

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

and that's wrong. What you really need is:

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

Without that change, I'd get a stretched aspect ratio, even in landscape. This wasn't a problem for CameraPreview of ApiDemos, as they were not taking pictures, and so they never called setPictureSize().

But, so far, on a Nexus 4, I now have square aspect ratios for portrait and landscape, with a floating orientation (i.e., not locked to landscape).

I'll try to remember to amend this question if I run into other hacks required by other devices, plus to link to my CameraView/CameraFragment components when they are released.


UPDATE #1

Well, of course, it couldn't possibly by nearly this simple. :-)

The fix for the problem where setPictureSize() screws up the aspect ratio works... until you take a picture. At that point, the preview switches to the wrong aspect ratio.

One possibility would be to limit pictures to ones with the same (or very close) aspect ratio to the preview, so the hiccup does not exist. I don't like that answer, as the user should be able to get whatever picture size the camera offers.

You can limit the scope of the damage by:

  • Only updating the picture size, etc. of the Camera.Parameters just before taking the picture
  • Reverting to the pre-picture-taking Camera.Parameters after taking the picture

This still presents the wrong aspect ratio during the moments while the picture is being taken. The long-term solution for this -- I think -- will be to temporarily replace the camera preview with a still image (the last preview frame) while the picture is being taken. I'll try this eventually.


UPDATE #2: v0.0.1 of my CWAC-Camera library is now available, incorporating the aforementioned code.



回答2:

Ok guys, i was really going crazy the last two weeks about this f*** problem. so okay, few things to know:

  • sometimes you just get black borders, if actionbar or soft buttons are visible, just possible on some devices, my advice: *don't care* or you'll get crazy, or cut previews, both not good...

changes by @CommonsWare are just perfect, but cannot be applied directly to the CameraPreview from API Level 8 Samples (link below), so here is the diff:

for correct aspect ratio in portrait do:
methods are the same, just apply 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);
}

in:

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

change:

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

to:

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) {…}

change:

double targetRatio = (double) w / h;

to:

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

finally starting camera for portrait mode with something like:

try {
     mCamera = openFrontFacingCameraGingerbread();
     if (portraitMode) {
       mCamera.setDisplayOrientation(90);
     }
     preview.setCamera(mCamera, portraitMode);
} catch (Exception e) {
     e.printStackTrace();
     handleCameraUnavailable();
}

WUUHUUUU! For me finally nor problems in preview, and no problems on saving images. Because my location of activity is locked, i get the display orientation via OrientationChangedListener for saving images with correct orientation.

using something like:
http://www.androidzeitgeist.com/2013/01/fixing-rotation-camera-picture.html

But they are correctly previewed and saved.

I hope I can help many people with that, there is the original code from API Level 8:
https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.java where the changes were applied.



回答3:

Please change the camera orientation on your code and you should setDisplayorientation() not getDisplayOrientation()

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

}