How to match Android Camera Preview correctly?

799 views Asked by At

I am trying to do an app that opens a preview of the camera and, with a button, it allows the user to record a video. The app is thought to be available in two orientations: portrait and landscape. In order to do this I used the following code:

CameraPreview

package com.recomovie.app;

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

import android.content.Context;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;

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)

    // 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) {
    // 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.setDisplayOrientation(90);
        Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

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

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

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

    } catch (Exception e){
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
    if (height > width ) {
        setMeasuredDimension(width, height);
        if (mSupportedPreviewSizes != null) {
            mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
        }

        float ratio;
        if(mPreviewSize.height >= mPreviewSize.width)
            ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
        else
            ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;

        float camHeight = (int) (width * ratio);
        float newCamHeight;
        float newHeightRatio;

        if (camHeight < height) {
            newHeightRatio = (float) height / (float) mPreviewSize.height;
            newCamHeight = (newHeightRatio * camHeight);
            setMeasuredDimension((int) (width * newHeightRatio), (int) newCamHeight);
        } else {
            newCamHeight = camHeight;
            setMeasuredDimension(width, (int) newCamHeight);
        }
    }
    else {
        setMeasuredDimension(width, height);
        if (mSupportedPreviewSizes != null) {
            mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
        }

        float ratio;
        if(mPreviewSize.width >= mPreviewSize.height)
            ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;
        else
            ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;

        float camWidth = (int) (height * ratio);
        float newCamWidth;
        float newWidthRatio;

        if (camWidth < width) {
            newWidthRatio = (float) width / (float) mPreviewSize.width;
            newCamWidth = (newWidthRatio * camWidth);
            setMeasuredDimension( (int) newCamWidth,(int) (height * newWidthRatio));
        } else {
            newCamWidth = camWidth;
            setMeasuredDimension((int) newCamWidth,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;
}
}

CameraFragment

package com.recomovie.app;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;

import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PictureCallback;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;

public class FragCamera extends Fragment{
    private Camera mCamera;
    private CameraPreview mPreview;
    private MediaRecorder mMediaRecorder;
    private boolean isRecording = false;

        /** Called when the activity is first created. */
    private Context context;
    private Activity act;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        context = getActivity().getApplicationContext();
        act = this.getActivity();
        View visor = inflater.inflate(R.layout.camera, container, false);
        // Create an instance of Camera
        if (checkCameraHardware(context)) {
            mCamera = getCameraInstance();
        }
        // Create our Preview view and set it as the content of our activity.
        mPreview = new CameraPreview(context, mCamera);
        FrameLayout preview = (FrameLayout) visor.findViewById(R.id.camera_preview);
        preview.addView(mPreview);
         //   List<Size> sizes = parameters.getSupportedPreviewSizes();
          //  Size optimalSize = getOptimalPreviewSize(sizes, width, height);
           // parameters.setPreviewSize(optimalSize.width, optimalSize.height);
            Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

            if(display.getRotation() == Surface.ROTATION_0)
            {               LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) preview.getLayoutParams();

                //params.setMargins(0, -218, 0, 0);
                //preview.setLayoutParams(new FrameLayout.LayoutParams(300, 49));
                preview.setLayoutParams(params);

            }

            if(display.getRotation() == Surface.ROTATION_90)
            {           
                LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) preview.getLayoutParams();

                //params.setMargins(0, -218, 0, 0);
                //preview.setLayoutParams(new FrameLayout.LayoutParams(300, 49));params.setMargins(0, -218, 0, 0);
                preview.setLayoutParams(params);               
            }

            if(display.getRotation() == Surface.ROTATION_180)
            {           LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) preview.getLayoutParams();

                //params.setMargins(0, -218, 0, 0);
                //preview.setLayoutParams(new FrameLayout.LayoutParams(300, 49));
                preview.setLayoutParams(params);            
            }

            if(display.getRotation() == Surface.ROTATION_270)
            {           LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) preview.getLayoutParams();

                //params.setMargins(0, -218, 0, 0);
                //preview.setLayoutParams(new FrameLayout.LayoutParams(300, 49));
                preview.setLayoutParams(params);
            }

        final ImageButton captureButton = (ImageButton) visor.findViewById(R.id.button_capture2);


         // Add a listener to the Capture button
         captureButton.setOnClickListener(
             new View.OnClickListener() {
                 public void onClick(View v) {
                     if (isRecording) {
                         // stop recording and release camera
                         mMediaRecorder.stop();  // stop the recording
                         releaseMediaRecorder(); // release the MediaRecorder object
                         mCamera.lock();         // take camera access back from MediaRecorder

                         // inform the user that recording has stopped
                         //captureButton.setText("Record Video");
                         isRecording = false;
                     } else {
                         // initialize video camera
                         if (prepareVideoRecorder()) {
                             // Camera is available and unlocked, MediaRecorder is prepared,
                             // now you can start recording
                             mMediaRecorder.start();

                             // inform the user that recording has started
                             //captureButton.setText("Stop");
                             isRecording = true;
                         } else {
                             // prepare didn't work, release the camera
                             releaseMediaRecorder();
                             // inform user
                         }
                     }
                 }
             }
         );
         return visor;
    }

    /** Check if this device has a camera */
    private boolean checkCameraHardware(Context context) {
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
            // this device has a camera
            return true;
        } else {
            // no camera on this device
            return false;
        }
    }

    /** A safe way to get an instance of the Camera object. */
    public static Camera getCameraInstance(){
        Camera c = null;
        try {
            c = Camera.open(); // attempt to get a Camera instance
        }
        catch (Exception e){
            // Camera is not available (in use or does not exist)
        }
        return c; // returns null if camera is unavailable
    }

    private boolean prepareVideoRecorder(){

        //mCamera = getCameraInstance();
        mMediaRecorder = new MediaRecorder();

        // Step 1: Unlock and set camera to MediaRecorder
        mCamera.unlock();
        mMediaRecorder.setCamera(mCamera);

        // Step 2: Set sources
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
        mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

        // Step 4: Set output file
        mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());

        // Step 5: Set the preview output
        mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());

        // Step 6: Prepare configured MediaRecorder
        try {
            mMediaRecorder.prepare();
        } catch (IllegalStateException e) {
            releaseMediaRecorder();
            return false;
        } catch (IOException e) {
            releaseMediaRecorder();
            return false;
        }
        return true;
    }

    @Override
    public void onPause() {
        super.onPause();    
        if (mCamera != null) {         
            mCamera.stopPreview(); 
            mCamera.setPreviewCallback(null);
            mPreview.getHolder().removeCallback(mPreview);
            mCamera.release();
            mCamera = null;
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        try
        {
            if (mCamera==null  ) {
                context = getActivity().getApplicationContext();
                act = this.getActivity();
                //act.setContentView(R.layout.camera);
                mCamera = getCameraInstance();
                //mCamera.setPreviewCallback(null);
                mPreview = new CameraPreview(context, mCamera);//set preview
                FrameLayout preview = (FrameLayout) this.act.findViewById(R.id.camera_preview);
                preview.addView(mPreview);
            }

        } catch (Exception e){
        }
    }   



    private void releaseMediaRecorder(){
        if (mMediaRecorder != null) {
            mMediaRecorder.reset();   // clear recorder configuration
            mMediaRecorder.release(); // release the recorder object
            mMediaRecorder = null;
            mCamera.lock();           // lock camera for later use
        }
    }


    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;

    /** Create a file Uri for saving an image or video */
    private static Uri getOutputMediaFileUri(int type){
        return Uri.fromFile(getOutputMediaFile(type));
    }

    /** Create a File for saving an image or video */
    private static File getOutputMediaFile(int type){
        // To be safe, you should check that the SDCard is mounted
        // using Environment.getExternalStorageState() before doing this.

        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                  Environment.DIRECTORY_MOVIES), "MyCameraApp");
        // This location works best if you want the created images to be shared
        // between applications and persist after your app has been uninstalled.

        // Create the storage directory if it does not exist
        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyCameraApp", "failed to create directory");
                return null;
            }
        }

        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE){
            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
            "IMG_"+ timeStamp + ".jpg");
        } else if(type == MEDIA_TYPE_VIDEO) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
            "VID_"+ timeStamp + ".mp4");
        } else {
            return null;
        }

        return mediaFile;
    }

}

CameraFragment XML

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >    

    <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent" >  
    <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </FrameLayout>
    </LinearLayout>
    <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/rect169bueno"
    android:orientation="vertical" >    
        <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="horizontal" >


    </LinearLayout>

            <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="horizontal" >

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal" >

        </LinearLayout>

        <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal" >
    <ImageButton
        android:id="@+id/button_capture2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:layout_centerHorizontal="true"
                    android:adjustViewBounds="true"
                    android:scaleType="matrix" >
    </ImageButton>
    <ImageView
                    android:id="@+id/imageView1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:layout_centerHorizontal="true"
                    android:adjustViewBounds="true"
                    android:scaleType="fitCenter"
                    android:src="@drawable/infoimage" />
        </RelativeLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal" >
        </LinearLayout>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="horizontal" >

    </LinearLayout>
</LinearLayout>
</FrameLayout>

In my mobile, the app works well, as you can see in the following screens: Portrait Landscape

However, I have tried in other mobile phones and:

  • In ones, the aspect ratio showed in the preview is correct, but the preview has a lot of zoom.

  • In others, the aspect ratio is correct, but the preview does not match with the entire screen.

  • In others, when the "Recording" button is clicked, the aspect ratio changes and then the preview has not the correct aspect ratio (However, the video is saved with good aspect ratio).

Can anyone help me, please? I do not understand why these problems are done in other devices.

0

There are 0 answers