3

I've found many questions here about android camera preview orientation problems. All the fixes involve fixing the screen orientation to landscape or calling camera.setDisplayOrientation(90) or calling params.setRotation(90). I can't find the combination of settings which actually make it work when held in landscape orientation.

The activity is fixed to landscape mode with android:screenOrientation="landscape".

My problem is that if I hold the camera in portrait orientation and start up the application it works fine (by which I mean that it properly shows the image as a landscape). If I hold the camera in landscape orientation when starting the application the picture is all messed up (sort of interlaced). If I use camera.setDisplayOrientation(90); the picture is no longer messed up, but the image is oriented sideways.

Strangely, if I remove the android:screenOrientation="landscape" and allow the screen to rotate, I still have the same problems, but if I rotate the phone to portrait it looks fine in portrait. If I rotate it back to landscape then it looks fine in landscape! My only problem is that it won't work correctly when it first starts up.

import java.util.List;

import android.content.Context;
import android.graphics.Color;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.util.AttributeSet;
import android.util.Log;
import android.view.*;


public class CameraView extends SurfaceView
{    
    //Callback for the surfaceholder
    SurfaceHolder.Callback surfaceHolderListener = new SurfaceHolder.Callback() {
        public void surfaceCreated(SurfaceHolder holder) {
            try {
                camera=Camera.open();
            } catch(Exception e) {
                Log.e("CameraView","Couldn't open the camera.",e);
            }
            try {
                camera.setPreviewDisplay(previewHolder);
            } catch (Throwable e) {
                Log.e("CameraView","Couldn't call setPreviewDisplay.",e);
            }
        }

        public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int w, int h) {
            if(camera!=null) {
                Parameters params = camera.getParameters();

                List<Size> sizes = params.getSupportedPreviewSizes();
                Size optimalSize = getOptimalPreviewSize(sizes, w, h);
                params.setPreviewSize(optimalSize.width, optimalSize.height);

                camera.setParameters(params);
                camera.startPreview();
            }
        }

        public void surfaceDestroyed(SurfaceHolder arg0) {
            if(camera!=null) {
                camera.setPreviewCallback(null);
                camera.stopPreview();
                camera.release();
            }
        }

        private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
            final double ASPECT_TOLERANCE = 0.05;
            double targetRatio = (double) w / h;
            if (sizes == null) return null;

            Size optimalSize = null;
            double minDiff = Double.MAX_VALUE;

            int targetHeight = h;

            // Try to find an size match aspect ratio and size
            for (Size size : sizes) {
                double ratio = (double) size.width / size.height;
                if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }

            // Cannot find the one match the aspect ratio, ignore the requirement
            if (optimalSize == null) {
                minDiff = Double.MAX_VALUE;
                for (Size size : sizes) {
                    if (Math.abs(size.height - targetHeight) < minDiff) {
                        optimalSize = size;
                        minDiff = Math.abs(size.height - targetHeight);
                    }
                }
            }
            return optimalSize;
        }
    };


    public CameraView(Context ctx) {
        super(ctx);

        previewHolder = this.getHolder();
        previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        previewHolder.addCallback(surfaceHolderListener);
        setBackgroundColor(Color.TRANSPARENT);
    }

    public CameraView(Context ctx, AttributeSet attrs) {
        super(ctx, attrs);
    }

    private Camera camera;
    private SurfaceHolder previewHolder;
}

EDIT: The real problem seems to be that something happens after surfaceChanged which screws things up. I found that if I start the camera preview and then add a debug point at the end of surfaceChanged the camera looks fine. Then if I step forward about 20 steps it suddenly looks screwed up.

I solved this (in a way which I think is a complete hack) by using the orientation change listener to update the orientation exactly one time and then disable itself. I just needed something which would activate after the view was initially set up. I could probably have done it elsewhere.

If anyone has any idea how to fix this without having it be this completely stupid, please help!

/** This works fine for me, but it's a hack. */

import java.util.List;

import android.content.Context;
import android.graphics.Color;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.OrientationEventListener;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class CameraView3
    extends SurfaceView
{
    private static Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.05;
        double targetRatio = (double) w / h;
        if (sizes == null) return null;

        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        // Try to find an size match aspect ratio and size
        for (Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        // Cannot find the one match the aspect ratio, ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }


    private class CameraSurfaceHolder
        extends OrientationEventListener
        implements SurfaceHolder.Callback
    {
        CameraSurfaceHolder(Context ctx) {
            super(ctx);
        }

        public void surfaceCreated(SurfaceHolder holder) {
            try {
                camera= Camera.open();
            } catch(Exception e) {
                Log.e("CameraView","Couldn't open the camera.",e);
            }
            try {
                camera.setPreviewDisplay(previewHolder);
            } catch (Throwable e) {
                Log.e("CameraView","Couldn't call setPreviewDisplay.",e);
            }
        }

        public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int w, int h) {
        }

        public void surfaceDestroyed(SurfaceHolder arg0) {
            if(camera!=null) {
                camera.setPreviewCallback(null);
                camera.stopPreview();
                camera.release();
            }
        }

        @Override
        public void onOrientationChanged(int orientation) {
            if(camera!=null) {
                DisplayMetrics dm = ctx.getResources().getDisplayMetrics();
                camera.stopPreview();
                CameraView3.this.layout(0, 0, dm.widthPixels-1, dm.heightPixels-1);
                camera.startPreview();
                disable();
                return;
            }
        }
    }


    public CameraView3(Context ctx) {
        super(ctx);
        this.ctx = ctx;

        previewHolder = this.getHolder();
        previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        surfaceHolderListener = new CameraSurfaceHolder(ctx);
        previewHolder.addCallback(surfaceHolderListener);
        surfaceHolderListener.enable();

        setBackgroundColor(Color.TRANSPARENT);
    }

    private Context ctx;
    private Camera camera;
    private SurfaceHolder previewHolder;
    private CameraSurfaceHolder surfaceHolderListener;
    private Size optimalSize;
}

In summary, the first version I posted worked fine on my Nexus One. But, it doesn't work on my Skyrocket. I need to use the second version to make it work there.

HappyEngineer
  • 4,017
  • 9
  • 46
  • 60
  • Why, in your working code, do you use the width and height minus 1? Without the -1 my code doesn't work on the Skyrocket but with the -1 it does. As you said, this feels like a total hack, but it is the only solution I have found for this weird behavior :( I'd like to not have the minus 1 because it doesn't fill the screen entirely... just trying to understand. – Daniel Smith Jan 12 '13 at 02:15

1 Answers1

0

Strangely, if I remove the android:screenOrientation="landscape" and allow the screen to rotate, I still have the same problems, but if I rotate the phone to portrait it looks fine in portrait.

First of all note that camera preview orientation has nothing to do with Activity orientation. Both are different. When you are changing device orientation, you have to call camera.setDisplayOrientation to set the orientation of the preview according to direction.

This orientation you have to set in degrees. This you can get from setting the OrientationEventListener in your camera perview. In onOrientationChanged(int orientation), you will get the exact rotation angle of device in degrees.

Don't forget to calculate rotation as API onOrientationChanged(int orientation) will be called for even a small degree angular change in device orientation. You can see this question as a reference.

Community
  • 1
  • 1
AndroDev
  • 3,236
  • 8
  • 35
  • 49
  • How would that help? The current code actually starts working when the orientation changes. It only fails when it first starts up and specifying the rotation values isn't working. – HappyEngineer Nov 22 '12 at 04:40
  • Method onOrientationChanged will get called even for first time also. You can try with this. – AndroDev Nov 22 '12 at 05:44
  • 1
    The orientation listener works, but not for any good reason that I can see. Something is happening after surfaceChanged which screws up the camera. I posted code that does work above, but I don't like it. – HappyEngineer Nov 24 '12 at 02:21