scientiffic
scientiffic

Reputation: 9415

Android MediaRecorder aspect ratio incorrect

I'm having issues with the aspect ratio of a MediaRecorder in my Android app. I'm having the issue specifically with the Samsung Galaxy S II, whose video camera seems to zoom in compared to the regular camera (this is a behavior I notice when using the default camera app on the phone as well).

You can see how the aspect ratio is stretched when I switch from using the camera to using the MediaRecorder in this video:

https://www.youtube.com/watch?v=U8vCwiNjCPU

and in the screenshots below:

Camera aspect ratio (correct):

Camera aspect ratio

Video aspect ratio (incorrect):

Video aspect ratio

How can I ensure that the aspect ratio of the video preview is correct?

Here is my code:

CustomCamera activity:

public class CustomCamera extends SherlockActivity {

private boolean prepareVideoRecorder() {
        Log.d(TAG, "in prepareVideoRecorder()");
        // It is very important to unlock the camera before doing setCamera
        // or it will results in a black preview
        if (camera == null) 
        {
            camera = getCameraInstance();
        }

        if (recorder == null){
            recorder = new MediaRecorder();
        }

        //Have to stop preview before starting to record
        camera.stopPreview();
        // Step 1: Unlock and set camera to MediaRecorder
        camera.unlock();
        recorder.setCamera(camera);

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

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

        // Step 4: Set output file
        recorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).getAbsolutePath());

        // No limit. Don't forget to check the space on disk.
        recorder.setMaxDuration(50000);
        recorder.setVideoFrameRate(30);
        recorder.setVideoEncodingBitRate(3000000);
        recorder.setAudioEncodingBitRate(8000);

        // Step 5: Set the preview output
        recorder.setPreviewDisplay(cameraPreview.getHolder().getSurface());

        //Setting the camera's orientation
        int degree = 0;
        // do not rotate image, just put rotation info in
        switch (mOrientation) {
        case ORIENTATION_LANDSCAPE_INVERTED:
            degree = 180;
            break;
        case ORIENTATION_PORTRAIT_NORMAL:
            degree = 90;
            break;
        case ORIENTATION_LANDSCAPE_NORMAL:
            degree = 0;
            break;
        case ORIENTATION_PORTRAIT_INVERTED:
            degree = 270;
            break;
        }

        recorder.setOrientationHint(degree);

        // Step 6: Prepare configured MediaRecorder
        try {
            recorder.prepare();
        } catch (IllegalStateException e) {
            // This is thrown if the previous calls are not called with the
            // proper order
            e.printStackTrace();
            releaseMediaRecorder();
            return false;
        } catch (IOException e) {
            releaseMediaRecorder();
            e.printStackTrace();
            return false;
        }        
        //Everything went successfully
        return true;
    }


}

    /**
     * Method used to set the camera preview's parameters to match the
     * phone's width and set the height accordingly to assure that there are
     * no aspect ratio issues.
     */
    private void setHolderParameters() {
        Log.d(TAG, "setting camera layout parameters");
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);

        int height = metrics.heightPixels;
        int width = metrics.widthPixels;

        Size mPreviewSize = CameraPreview.getOptimalPreviewSize(camera.getParameters().getSupportedPreviewSizes(), width, height);
        double ratio = ((double)mPreviewSize.width)/mPreviewSize.height;

        FrameLayout.LayoutParams previewParams = new FrameLayout.LayoutParams(width, (int)(width*ratio));

        cameraPreview.setLayoutParams(previewParams);       
    }

    /**
     * Open the camera asynchronously to reduce the lag when opening
     * activity
     */
    public void openCameraAsync(){
        new AsyncTask<Object, Object, Object>(){
            @Override
            protected Object doInBackground(Object... arg0) {
                if (!isFinishing()){
                    //Resuming camera and display when resuming
                    if(camera == null){
                        Log.d(TAG, "Resuming with a null camera");
                        camera = getCameraInstance();
                    }
                }
                return null;
            }

            @Override
            protected void onPostExecute(Object result){
                setHolderParameters();
                cameraPreview.setCamera(camera);

                //Calling surface created so that the preview of the camera is correct
                cameraPreview.surfaceCreated(cameraPreview.getHolder());
            }
        }.execute();
    }

CameraPreview.java:

public static Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
        Log.d(TAG, "getOptimalPreviewSize");
        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;

        // Find 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);
            }
        }

        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);
                }
            }
        }
        Log.d(TAG, "targetRatio: " + targetRatio);
        Log.d(TAG, "optimalSize: " + optimalSize);
        return optimalSize;
    }

    public void setRecorder(MediaRecorder recorder){
        this.recorder = recorder;
    }

Upvotes: 2

Views: 4640

Answers (2)

ddog
ddog

Reputation: 670

On some phones on measure returns different width and height than DisplayMatrics provide and that is why you get shrink-ed or widened picture when you press record.

specifically these values :

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    measuredWidth = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    measuredHeight = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        setMeasuredDimension(measuredWidth, measuredHeight);
        //setMeasuredDimension(mPreviewSize.height, mPreviewSize.width);
    }
}

from your surface view may be different than:

public static Camera.Size getDeviceSpecificOptimalPreviewSize(Context context, Camera camera, int w, int h) {
    List<Camera.Size> mSupportedPreviewSizes = camera.getParameters().getSupportedPreviewSizes();
    if (mSupportedPreviewSizes != null) {
        final double ASPECT_TOLERANCE = 0.1;
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        int width = metrics.widthPixels;
        int height = metrics.heightPixels;
        double targetRatio = (double) height / width;

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

        for (Camera.Size size : mSupportedPreviewSizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - h) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - h);
            }
        }

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

these values that return Camera.Size object. mPreviewSize in my case. You also need to set them on camera object in onSurfaceCreated and in onSurfaceChanged method in your SurfaceView class.

Be careful when setting the values because onMeasure on some phones returns height and width in reverse order.

Upvotes: 0

user2743753
user2743753

Reputation: 69

Are setting any Camera.Parameters? You need to use setPreviewSize(int width, int height) and set it to the width and height of your video.

In your MediaRecorder, you may also need to use setVideoSize(int,int) and (again) set to the size of your video.

I was having the same issue you are having and to get the correct aspect ratio for your video, the layout size, Camera Preview size, and MediaRecorder size should have the same aspect ratio. Errors usually occur when one of these is off.

Upvotes: 3

Related Questions