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:
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.