18

i created a camera app based on tutorial. the preview class i use is from api-Demos "CameraPreview". I added a modification from here (preview was always rotated by 90°). So this is how i set preview size:

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    // Now that the size is known, set up the camera parameters and begin
    // the preview.
    Camera.Parameters parameters = mCamera.getParameters();
    Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

    if (display.getRotation() == Surface.ROTATION_0) {
        parameters.setPreviewSize(mPreviewSize.height, mPreviewSize.width);
        mCamera.setDisplayOrientation(90);
    }

    if (display.getRotation() == Surface.ROTATION_90) {
        parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
    }

    if (display.getRotation() == Surface.ROTATION_180) {
        parameters.setPreviewSize(mPreviewSize.height, mPreviewSize.width);
    }

    if (display.getRotation() == Surface.ROTATION_270) {
        parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
        mCamera.setDisplayOrientation(180);
    }
    parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);

    requestLayout();

    mCamera.setParameters(parameters);
    mCamera.startPreview();
}

But the Preview is displayed with wrong aspect ratio. Is it because of the code above or probably because of the layout i use?:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<Button
    android:id="@+id/button_capture"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true" 
    android:text="@string/capture" />

<FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="100dp"
    android:layout_height="match_parent"/>

So how to get the correct aspect ratio? Thanks in advance.

P.S. i read the answer from: Android camera preview look strange But this isn't working for me.

Community
  • 1
  • 1
dermoritz
  • 12,519
  • 25
  • 97
  • 185
  • maybe you could close this issue ? – Poutrathor Jul 08 '15 at 10:51
  • 2
    first you don't have to believe, 2nd i don't have time to try the other solutions (posted a year after posting the question). As soon as i tried the other answers i will give feedback. Until then all is fine... – dermoritz Jul 08 '15 at 11:55

5 Answers5

27

Try changing the preview sizes with adding this function:

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

    if (sizes==null) return null;

    Camera.Size optimalSize = null;

    double minDiff = Double.MAX_VALUE;

    int targetHeight = h;

    // Find size
    for (Camera.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);
        }
    }

    if (optimalSize == null) {
        minDiff = Double.MAX_VALUE;
        for (Camera.Size size : sizes) {
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
    }
    return optimalSize;
}

And the setting the sizes from these optimized values:

List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
Camera.Size optimalSize = getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);

parameters.setPreviewSize(optimalSize.width, optimalSize.height);
halfer
  • 19,824
  • 17
  • 99
  • 186
Henric
  • 782
  • 1
  • 9
  • 23
  • 1
    as i stated in my question i am using this code (it is from Api-Demos "CameraPreview"). I think the problem relies to my layout and how i adding the preview? – dermoritz Jul 23 '13 at 08:26
  • @dermoritz It's not a layout problem! you need to set the parameters one of the `mCamera`-object one line above `mCamera.startPreview()` in the `CameraPreivew.java`-class. Works for me. This answer should be accepted! – 000000000000000000000 Oct 20 '15 at 09:47
5

The following code modifies the width/height of the camera preview container to match the aspect ratio of the camera preview.

    Camera.Size size = camera.getParameters().getPreviewSize();

    //landscape
    float ratio = (float)size.width/size.height;

    //portrait
    //float ratio = (float)size.height/size.width;

    preview = (FrameLayout) findViewById(R.id.camera_preview);

    int new_width=0, new_height=0;
    if(preview.getWidth()/preview.getHeight()<ratio){
        new_width = Math.round(preview.getHeight()*ratio);
        new_height = cameraPreview.getHeight();
    }else{
        new_width = preview.getWidth();
        new_height = Math.round(preview.getWidth()/ratio);
    }
    preview.setLayoutParams(new FrameLayout.LayoutParams(new_width, new_height));
gtsouk
  • 5,208
  • 1
  • 28
  • 35
  • 1
    Nice idea, but several mistakes should be cleaned.... 1. You should divide float by float in your "if" -- now you are dividing integers. 2. cameraPreview should be preview. 3. you should make sure the views are laid out to perform this. – rommex Oct 07 '18 at 07:21
3

Using the above solution, using the method private Size getOptimalPreviewSize(List sizes, int w, int h). Worked fine! I was having problems with the aspect ratio on portrait orientation: Here it´s my solution using. Mixing it with android's documentation:

  public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
            // If your preview can change or rotate, take care of those events here.
            // Make sure to stop the preview before resizing or reformatting it.

            if (mHolder.getSurface() == null){
              // preview surface does not exist
              return;
            }


            // stop preview before making changes
            try {
                mCamera.stopPreview();
            } catch (Exception e){
              // ignore: tried to stop a non-existent preview
            }

            // set preview size and make any resize, rotate or
            // reformatting changes here
            Camera.Parameters params = mCamera.getParameters();
            params.set("orientation", "portrait");
            Size optimalSize=getOptimalPreviewSize(params.getSupportedPreviewSizes(),  getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);
            params.setPreviewSize(optimalSize.width, optimalSize.height);
            mCamera.setParameters(params);
            // start preview with new settings
            try {

                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();

            } catch (Exception e){
                Log.d(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }
    }
supersabbath
  • 364
  • 3
  • 8
3

problem is really in way you layout things. there is overriden onLayout in Preview class. idea of its work is to set child SurfaceView size according to found optimal Size. but it doesn't take rotation into account, so you need to do it by yourself:

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

instead of

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

Trick is to swap width and height which is done because of 90 degree rotation achieved by

  mCamera.setDisplayOrientation(90);

You might also consider forking child preview size setting depending on orientation that you set in

  public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
      //...
  }

(in provided by me code it is always for 90 degree rotation, for 180 you don't have to do anyhing and when you don't set any rotation, there is no need to swap width and height)

Another thing worth mentioning - when calculating getOptimalPreviewSize for case when you have rotation and you swap child width and height you also should pass parent(Preview) width and height swapped in onMeasure:

if (mSupportedPreviewSizes != null) {
  //noinspection SuspiciousNameCombination
  final int previewWidth = height;
  //noinspection SuspiciousNameCombination
  final int previewHeight = width;
  mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, previewWidth, previewHeight);
}
w1ld.b0ar
  • 31
  • 3
1

Henric's answer didn't work for me, so I've created another method which determines the optimal preview size for any camera given the target view current width and height and also the activity orientation:

public static Size getOptimalPreviewSize(List<Camera.Size> cameraPreviewSizes, int targetWidth, int targetHeight, boolean isActivityPortrait) {
    if (CommonUtils.isEmpty(cameraPreviewSizes)) {
        return null;
    }

    int optimalHeight = Integer.MIN_VALUE;
    int optimalWidth = Integer.MIN_VALUE;

    for (Camera.Size cameraPreviewSize : cameraPreviewSizes) {
        boolean isCameraPreviewHeightBigger = cameraPreviewSize.height > cameraPreviewSize.width;
        int actualCameraWidth = cameraPreviewSize.width;
        int actualCameraHeight = cameraPreviewSize.height;

        if (isActivityPortrait) {
            if (!isCameraPreviewHeightBigger) {
                int temp = cameraPreviewSize.width;
                actualCameraWidth = cameraPreviewSize.height;
                actualCameraHeight = temp;
            }
        } else {
            if (isCameraPreviewHeightBigger) {
                int temp = cameraPreviewSize.width;
                actualCameraWidth = cameraPreviewSize.height;
                actualCameraHeight = temp;
            }
        }

        if (actualCameraWidth > targetWidth || actualCameraHeight > targetHeight) {
            // finds only smaller preview sizes than target size
            continue;
        }

        if (actualCameraWidth > optimalWidth && actualCameraHeight > optimalHeight) {
            // finds only better sizes
            optimalWidth = actualCameraWidth;
            optimalHeight = actualCameraHeight;
        }
    }

    Size optimalSize = null;

    if (optimalHeight != Integer.MIN_VALUE && optimalWidth != Integer.MIN_VALUE) {
        optimalSize = new Size(optimalWidth, optimalHeight);
    }

    return optimalSize;
}

This uses a custom Size object, because Android's Size is available after API 21.

public class Size {

    private int width;
    private int height;

    public Size(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

}

You can determine the width and height of a view by listening for its global layout changes and then you can set the new dimensions. This also shows how to programmatically determine activity orientation:

cameraPreviewLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                // gets called after layout has been done but before display.
                cameraPreviewLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);

                boolean isActivityPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
                Size optimalCameraPreviewSize = CustomUtils.getOptimalPreviewSize(cameraPreview.getCameraSizes(), cameraPreviewLayout.getWidth(), cameraPreviewLayout.getHeight(), isActivityPortrait);
                if (optimalCameraPreviewSize != null) {
                    LinearLayout.LayoutParams cameraPreviewLayoutParams = new LinearLayout.LayoutParams(optimalCameraPreviewSize.getWidth(), optimalCameraPreviewSize.getHeight());
                    cameraPreviewLayout.setLayoutParams(cameraPreviewLayoutParams);
                }
            }
        });
Galya
  • 6,294
  • 6
  • 27
  • 45