How to fix the right aspect ratio of the camera preview (surfaceview) in Android?

4.6k Views Asked by At

I am creating the Android app with the camera functionality. The camera screen contains the toolbar on the top, the surfaceview (camera preview) below the toolbar, and the camera control buttons on the bottom of the screen. The screen is always in portrait.

[Some lines of the code do not related to the issue are deleted]

This is my fragment FragmentCamera

import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.dmc.R;
import com.dmc.entities.Preview;

public class CameraFragment implements View.OnClickListener, View.OnTouchListener {

    public static final String ARG_CAMERA_MODE = "camera.mode";
    public static final String TYPE_CAMERA_MODE_IMAGE = "image";
    public static final String TYPE_CAMERA_MODE_VIDEO = "video";
    public MediaRecorder mrec = new MediaRecorder();

    private Camera camera;
    private String mCameraMode = TYPE_CAMERA_MODE_IMAGE; //or video
    private com.dmc.entities.Preview preview;
    private ImageView btnStopRecording;
    private SurfaceView surfaceView;
    private View view;

    public static FrCamera getInstance(String cameraMode) {
        CameraFragment fragment = new CameraFragment();
        Bundle bundle = new Bundle(1);
        bundle.putString(ARG_CAMERA_MODE, cameraMode);
        return fragment.setArguments(bundle);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCameraMode = getArguments().getString(ARG_CAMERA_MODE);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.fragment_camera, container, false);
        view.setOnTouchListener(this);
        btnStopRecording = (ImageView) view.findViewById(R.id.btnStopRecording);
        if (!mCameraMode.equals(TYPE_CAMERA_MODE_IMAGE)) {
            btnStopRecording.setOnClickListener(this);
        }
        surfaceView = (SurfaceView) view.findViewById(R.id.surfaceView);
        view.findViewById(R.id.imgCameraTakePicture).setOnClickListener(this);

        preview = new Preview(getActivity(), (SurfaceView) view.findViewById(R.id.surfaceView));
        preview.setKeepScreenOn(true);
        return view;
    }

    @Override
    public void onStart() {
        super.onStart();

        int numCams = Camera.getNumberOfCameras();
        if (numCams > 0) {
            try {
                camera = Camera.open(0);
                preview.setCamera(camera);
                camera.startPreview();
            } catch (RuntimeException ex) {
            }
        }
    }

    @Override
    public void onPause() {
        if (camera != null) {
            camera.stopPreview();
            preview.setCamera(null);
            camera.release();
            camera = null;
        }
        super.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
        camera.startPreview();
    }

    private void startVideoRecording() {
        try {
            mrec = new MediaRecorder();
            mrec.setCamera(camera);
            mrec.setVideoSource(MediaRecorder.VideoSource.CAMERA);
            mrec.setAudioSource(MediaRecorder.AudioSource.MIC);
            CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
            mrec.setProfile(profile);
            camera.lock();
            camera.unlock();
            mrec.setPreviewDisplay(preview.mHolder.getSurface());
            mrec.setOutputFile(outVideoFile.getPath());
            mrec.setOrientationHint(Preview.rotate);
            mrec.prepare();
            mrec.start();
        } catch (Exception ex) {
            Log.e(getClass().getName(), ex.getMessage());
        }
    }

    protected void stopRecording() {
        if (mrec != null) {
            mrec.stop();
            mrec.release();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.imgCameraTakenPicture:
                // Save image
                break;
            case R.id.btnStopRecording:
                stopRecording();
                break;
            case R.id.imgCameraTakePicture:
                if (mCameraMode.equals(TYPE_CAMERA_MODE_IMAGE)) {
                    camera.takePicture(shutterCallback, rawCallback, jpegCallback);
                } else
                    startVideoRecording();
                break;
        }
    }
}

This is the Preview

import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;

import java.io.IOException;
import java.util.List;

public class Preview extends ViewGroup implements SurfaceHolder.Callback {
    public static final float RATIO = 0.75f;
    public static int rotate;

    public SurfaceView mSurfaceView;
    public SurfaceHolder mHolder;
    Size mPreviewSize;
    List<Size> mSupportedPreviewSizes;
    Camera mCamera;

    public Preview(Context context, SurfaceView sv) {
        super(context);
        mSurfaceView = sv;
        mHolder = mSurfaceView.getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void setCamera(Camera camera) {
        mCamera = camera;
        if (mCamera != null) {
            mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
            requestLayout();
            // get Camera parameters
            Camera.Parameters params = mCamera.getParameters();

            List<String> focusModes = params.getSupportedFocusModes();
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
                params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            } else {
                params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            }

            params.setJpegThumbnailQuality(100);
            params.setJpegQuality(100);

            // Configure image format. RGB_565 is the most common format.
            List<Integer> formats = params.getSupportedPictureFormats();
            if (formats.contains(PixelFormat.RGB_565))
                params.setPictureFormat(PixelFormat.RGB_565);
            else if (formats.contains(PixelFormat.JPEG))
                params.setPictureFormat(PixelFormat.JPEG);
            else params.setPictureFormat(formats.get(0));

            Camera.CameraInfo camInfo = new Camera.CameraInfo();
            Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, camInfo);
            int cameraRotationOffset = camInfo.orientation;

            Camera.Parameters parameters = mCamera.getParameters();

            int rotation = ((Activity) getContext()).getWindowManager().getDefaultDisplay().getRotation();
            int degrees = 0;
            switch (rotation) {
                case Surface.ROTATION_0:
                    degrees = 0;
                    break; // Natural orientation
                case Surface.ROTATION_90:
                    degrees = 90;
                    break; // Landscape left
                case Surface.ROTATION_180:
                    degrees = 180;
                    break;// Upside down
                case Surface.ROTATION_270:
                    degrees = 270;
                    break;// Landscape right
            }
            int displayRotation;
            if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                displayRotation = (cameraRotationOffset + degrees) % 360;
                //displayRotation = (360 - displayRotation) % 360; // compensate the mirror
            } else { // back-facing
                displayRotation = (cameraRotationOffset - degrees + 360) % 360;
            }
            mCamera.setDisplayOrientation(displayRotation);

            if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                rotate = (360 + cameraRotationOffset + degrees) % 360;
            } else {
                rotate = (360 + cameraRotationOffset - degrees) % 360;
            }

            parameters.set("orientation", "portrait");
            parameters.setRotation(rotate);

            mCamera.setParameters(params);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed && getChildCount() > 0) {
            final View child = getChildAt(0);

            final int width = r - l;
            final int height = b - t;

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

            // Center the child SurfaceView within the parent.
            if (width * previewHeight > height * previewWidth) {
                final int scaledChildWidth = previewWidth * height / previewHeight;
                child.layout((width - scaledChildWidth) / 2, 0,
                        (width + scaledChildWidth) / 2, height);
            } else {
                final int scaledChildHeight = previewHeight * width / previewWidth;
                child.layout(0, (height - scaledChildHeight) / 2,
                        width, (height + scaledChildHeight) / 2);
            }
        }
    }

    public void surfaceCreated(SurfaceHolder holder) {
        try {
            if (mCamera != null) {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            }
        } catch (IOException exception) {}
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
        }
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (mHolder.getSurface() == null) {
            return;
        }
        stopPreview();
        setCamera(mCamera);
        startPreview();
        mCamera.startPreview();
    }

    public void startPreview() {
        try {
            if (mCamera != null) {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();
            } 
        } catch (Exception e) {}
    }

    public void stopPreview() {
        try {
            if (mCamera != null)
                mCamera.stopPreview();
        } catch (Exception e) {}
    }
}

The expected result is on the image on left side. The obtained result is on the image on the right side. The camera preview is stretched. How to fix the right aspect ratio of the camera preview? enter image description here

2

There are 2 best solutions below

0
On

Below is the solution of Camera Preview Stretched

  1. It's worked on making my custom camera activity
  2. I set the framelayout which holds the surface view that displays the camera's parameters.
 public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        
            private static final String TAG = "CameraPreview";
        
            private Context mContext;
            private SurfaceHolder mHolder;
            private Camera mCamera;
            private List<Camera.Size> mSupportedPreviewSizes;
            private Camera.Size mPreviewSize;
        
            public CameraPreview(Context context, Camera camera) {
                super(context);
                mContext = context;
                mCamera = camera;
        
                // supported preview sizes
                mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
                for(Camera.Size str: mSupportedPreviewSizes)
                        Log.e(TAG, str.width + "/" + str.height);
        
                // Install a SurfaceHolder.Callback so we get notified when the
                // underlying surface is created and destroyed.
                mHolder = getHolder();
                mHolder.addCallback(this);
                // deprecated setting, but required on Android versions prior to 3.0
                mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            }
        
            public void surfaceCreated(SurfaceHolder holder) {
                // empty. surfaceChanged will take care of stuff
            }
        
            public void surfaceDestroyed(SurfaceHolder holder) {
                // empty. Take care of releasing the Camera preview in your activity.
            }
        
            public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
                Log.e(TAG, "surfaceChanged => w=" + w + ", h=" + 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
                // start preview with new settings
                try {
                    Camera.Parameters parameters = mCamera.getParameters();
                    parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
                    mCamera.setParameters(parameters);
                    mCamera.setDisplayOrientation(90);
                    mCamera.setPreviewDisplay(mHolder);
                    mCamera.startPreview();
        
                } catch (Exception e){
                    Log.d(TAG, "Error starting camera preview: " + e.getMessage());
                }
            }
        
            @Override
            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
                final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        
                if (mSupportedPreviewSizes != null) {
                    mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
                }
        
                if (mPreviewSize!=null) {
                    float ratio;
                    if(mPreviewSize.height >= mPreviewSize.width)
                        ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
                    else
                        ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;
        
                    // One of these methods should be used, second method squishes preview slightly
                    setMeasuredDimension(width, (int) (width * ratio));
          //        setMeasuredDimension((int) (width * ratio), height);
                }
            }
        
            private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
                final double ASPECT_TOLERANCE = 0.1;
                double targetRatio = (double) h / w;
        
                if (sizes == null)
                    return null;
        
                Camera.Size optimalSize = null;
                double minDiff = Double.MAX_VALUE;
        
                int targetHeight = h;
        
                for (Camera.Size size : sizes) {
                    double ratio = (double) size.height / size.width;
                    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;
            }
        }

Reference link: Android Camera Preview Stretched

0
On

Source code for Camera1 and Android 4-5:

@Override
protected void onResume() {
    super.onResume();
    camera = Camera.open(CAMERA_ID);
    setPreviewSize();
}

@Override
protected void onPause() {
    super.onPause();
    if (camera != null)
        camera.release();
    camera = null;
}
...
void setPreviewSize() {
    // получаем размеры экрана
    Display display = getWindowManager().getDefaultDisplay();
    int w1 = display.getWidth();
    int h1 = display.getHeight();
    boolean widthIsMax = display.getWidth() > display.getHeight();

    // определяем размеры превью камеры
    Camera.Size size = camera.getParameters().getPreviewSize();

    RectF rectDisplay = new RectF();
    RectF rectPreview = new RectF();

    // RectF экрана, соотвествует размерам экрана
    rectDisplay.set(0, 0, w1, h1);

    // подготовка матрицы преобразования
    Matrix matrix = new Matrix();
    // RectF первью
    if (widthIsMax) {
        // превью в горизонтальной ориентации
        rectPreview.set(0, 0, size.width, size.height);

        // если экран будет "втиснут" в превью (третий вариант из урока)
        matrix.setRectToRect(rectPreview, rectDisplay,
                Matrix.ScaleToFit.START);
    } else {
        // превью в вертикальной ориентации
        rectPreview.set(0, 0, size.height, size.width);

        // если превью будет "втиснут" в экран (второй вариант из урока)
        matrix.setRectToRect(rectPreview, rectDisplay,
                Matrix.ScaleToFit.START);
    }

    // преобразование
    matrix.mapRect(rectPreview);

    // установка размеров surface из получившегося преобразования
    h0 = (int) (rectPreview.bottom);
    w0 = (int) (rectPreview.right);

    surfaceView.getLayoutParams().height = h0;
    surfaceView.getLayoutParams().width = w0;
}

See http://startandroid.ru/ru/uroki/vse-uroki-spiskom/264-urok-132-kamera-vyvod-izobrazhenija-na-ekran-obrabotka-povorota.html