15

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:

Nexus 4 ApiDemos Preview, Portrait Mode

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:

Nexus 4 ApiDemos Preview, Portrait Mode, with setDisplayOrientation(90)

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!

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • i find this http://stackoverflow.com/questions/6710573/camera-display-doesnt-maintain-aspect-ratio-seems-skewed tell me if it is, i will write a full answer – idan Jun 15 '13 at 19:28
  • @idan: Sorry, but there is no action bar on `CameraPreview` in the `ApiDemos`. It already uses `Window.FEATURE_NO_TITLE`. Thanks, though! – CommonsWare Jun 15 '13 at 19:38
  • 1
    I've been working on this a lot myself recently. One thing to keep in mind (and maybe you already are) is that the preview sizes do not swap dimensions when you rotate to portrait. If the default camera orientation is landscape, the width will be the width of the phone in landscape, regardless of whether you're actually *in* landscape, so you may have to reverse the width/height parameters when you're checking for the correct preview size. – Kevin Coppock Jun 15 '13 at 20:34
  • 1
    @kcoppock: Well, something in how you phrased that helped a bit. I had seen that comment before, but I thought it meant reversing the height/width for `setPreviewSize()`, which doesn't work (unless the reversed size happens to be a valid preview size). However, by reversing the height/width of the preview `View` itself (e.g., `SurfaceView`), I get a fair bit closer to the proper aspect ratio. It's not yet accurate, but it's not as egregious as what I show above. Thanks! – CommonsWare Jun 15 '13 at 20:48
  • No problem! Setting up a camera preview is way more complicated than it should be (probably why most barcode scanner apps seem to lock in landscape). There's also the issue that you don't get an onCreate/onDestroy event when going from landscape to reverse landscape, so you end up with an upside-down preview in that case, which I haven't found a (non-hackish) solution for as of yet. If you do find a solid solution for this aspect ratio issue, I'd love to hear it. Good luck! – Kevin Coppock Jun 15 '13 at 23:54

3 Answers3

15

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.

Community
  • 1
  • 1
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 1
    Wow, yeah either we're misunderstanding them, or the Camera APIs are really, really bad. The fact that the documentation for [`setDisplayOrientation()`](http://developer.android.com/reference/android/hardware/Camera.html) includes code to determine the proper value (why not add this to the actual Android source instead of leaving it in a Javadoc?) kind of confirms that it's a mess. Thanks for the info. :) – Kevin Coppock Jun 17 '13 at 02:17
  • Hi. great work! I am wonder if your app can also solve my issue: http://stackoverflow.com/questions/17348614/make-a-surfaceview-larger-than-the-screen-fitting-a-camera-preview-to-a-surface/. I cannot test this using your code right now :( – Paul Jul 08 '13 at 18:28
  • 1
    @Paul: Yeah, I saw your question there, and I haven't a clue on it, sorry. – CommonsWare Jul 08 '13 at 18:29
  • @CommonsWare Thank you so much for sharing this useful info and your time, **it helped me for setting picture size**, would you please guide me **how can set Camera preview in Portrait mode?**, I posted my problem here http://stackoverflow.com/questions/19176038/camera-setdisplayorientation-function-is-not-working-for-samsung-galaxy-ace-wi but so far no luck. – swiftBoy Nov 13 '13 at 09:48
  • @CommonsWare, I am pleasure invite you visit this page and take a look at my problem, thank you very much: http://stackoverflow.com/questions/33950044/setdisplayorientation-in-camera-make-wrong-orientation-when-save-image?noredirect=1#comment55660353_33950044 – nguoixanh Nov 27 '15 at 07:22
1

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.

cV2
  • 5,229
  • 3
  • 43
  • 53
-1

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

} 
jbr
  • 6,198
  • 3
  • 30
  • 42