Friday, September 2, 2011

Recodring Video on Android device: freeze problem solution

Recording a video can in some cases be problematic, and cause camera freeze and lock, and device restart.
I worked on Acer A500 HoneyComb 3.1 device and found a good solution for it, I did it by examining the device log while recording using the camera app (currently, at the time of the post, honeycomb source code is still not available), in the log a found this info message:
09-02 21:48:36.920: INFO/OMXCodec(84): [OMX.Nvidia.h264.decode] stopped in state 1
which hint me that the recording of the camera app is using the h264 codec and its very possible that this is done at hardware level.
This information may be relevant for you if you don't have a tegra2 device, examine the log when recording using the camera app to find the best codec for your recording.

So, here is the java code and layout that works perfectly on my Acer Iconia A500 HoneyComb device:

======================CustomVideoCameraExample=====================


package example;

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

import android.app.Activity;

import android.util.Log;
import android.view.View.OnClickListener;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.Toast;

public class CustomVideoCameraExample extends Activity implements SurfaceHolder.Callback{

	

	private SurfaceView surfaceView;
	private SurfaceHolder surfaceHolder;
	private Camera camera;
	private boolean previewRunning;
	
	private MediaRecorder mediaRecorder;
	private final int maxDurationInMs = 20000;
	private final long maxFileSizeInBytes = 500000;
	private final int videoFramesPerSecond = 20;
	private boolean recording = false;
	// front =  1, rear = 0
	private int cameraType = 1;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.custom_video_camera_example);
		surfaceView = (SurfaceView) findViewById(R.id.surface2);
		surfaceHolder = surfaceView.getHolder();
		surfaceHolder.addCallback(this);
		surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
		
		Button button = (Button)findViewById(R.id.Button01_rec_start_stop);
		button.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				Log.d(Prefs.TAG, "record start/ stop clicked");
				if (recording) {
					recording = false;
					stopRecording();
				} else {
					recording = true;
					if (startRecording12()) {
						Log.d(Prefs.TAG, "recording ...");
					}
					else {
						Log.d(Prefs.TAG, " recording failed.");
					}
				}
				
			}
		});
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		camera = Camera.open(cameraType);
		if (camera != null){
			Camera.Parameters p = camera.getParameters();
			//p.set("orientation", "portrait");
			
			camera.setParameters(p);
			camera.setDisplayOrientation(90);
			
		
			Log.d(Prefs.TAG, "Focus Mode: " + p.getFocusMode());
		}
		else {
			Toast.makeText(getApplicationContext(), "Camera not available!", Toast.LENGTH_LONG).show();
			finish();
		}
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		if (previewRunning){
			camera.stopPreview();
		}
		
		// does not have effect
		//Surface.setOrientation(Display.DEFAULT_DISPLAY, Surface.ROTATION_90);
		
		Camera.Parameters p = camera.getParameters();
		
		List list = p.getSupportedPreviewSizes ();
		//They are ordered from largest to smallest, so the largest will be...
		for (Camera.Size i : list) {
			Log.d(Prefs.TAG, "i.height: " + i.height + " i.width" + i.width);
		}
		//Camera.Size size = list.get(0);

		// works with rear and should be the default
		//p.setPreviewSize(480, 640);
		
		// works with front
		//p.setPreviewSize(176, 144);
		// sometimes fail
		p.setPreviewSize(320, 240);
		
		// works with rear
		//p.setPreviewSize(640, 480);
		
		//p.setPreviewSize(320, 240);
		//p.setPreviewSize(480, 640);
		//p.setPreviewSize(1280, 720 );
		
		
		//p.setPreviewSize(size.width, size.height);
	
		camera.setParameters(p);

		try {
			camera.setPreviewDisplay(holder);
			camera.startPreview();
			camera.autoFocus(null);
			previewRunning = true;
		}
		catch (IOException e) {
			Log.e(Prefs.TAG,e.getMessage(), e);
			
		}
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		camera.stopPreview();
		previewRunning = false;
		camera.release();
	}
	
	
	
	
	// working with 320x240 and with 640x480
	public boolean startRecording12(){
		try {
			Log.d(Prefs.TAG, "Start record...");
			
			camera.unlock();

			mediaRecorder = new MediaRecorder();

			mediaRecorder.setCamera(camera);
			//mediaRecorder.setAudioSource(MediaRecorder.AudioSource.);
			mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
			mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
			
			mediaRecorder.setMaxDuration(maxDurationInMs);
		
			mediaRecorder.setOutputFile("/sdcard/pictell.3gp");
			
			// not making any effect
			mediaRecorder.setVideoFrameRate(20);
		
			// works!
			//mediaRecorder.setVideoSize(176, 144); 
			
			// works!
			//mediaRecorder.setVideoSize(320, 240); 
			
			// works!
			mediaRecorder.setVideoSize(640, 480); 
			

			//mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
			mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
			
			//mediaRecorder.setAudioChannels(1); 
			
			
			mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());

			mediaRecorder.setMaxFileSize(maxFileSizeInBytes);
			
			// for some player this hint works and 
			// they will rotate the video, iPhone, Honeycomb works,
			// On nexus one with 2.2 its not working (will not rotate the video) with the default player.
			mediaRecorder.setOrientationHint(270);
			
			Log.d(Prefs.TAG, "Sleeping x msecs for camera sync");
			try {Thread.sleep(300);} catch (InterruptedException e) {}
			Log.d(Prefs.TAG, "call prepare");
			
            mediaRecorder.prepare();
            try {Thread.sleep(300);} catch (InterruptedException e) {}
			mediaRecorder.start();

			return true;
		} catch (IllegalStateException e) {
			Log.e(Prefs.TAG,e.getMessage(), e);
			
			return false;
		} catch (IOException e) {
			Log.e(Prefs.TAG,e.getMessage(), e);
			
			return false;
		}
	}
	
	
	public void stopRecording(){
		Log.d(Prefs.TAG, "Stop record...");
		mediaRecorder.stop();
		Log.d(Prefs.TAG, "recorder Stoped.");
		//try {Thread.sleep(300);} catch (InterruptedException e) {}
		//mediaRecorder.reset(); 
		try {
			camera.reconnect();
		} catch (IOException e) {
			Log.d(Prefs.TAG, e.getMessage(), e);
		}
	}
	
	
}
 
 
======================CustomVideoCameraExample=====================
 
 
================================custom_video_camera_example.xml =================

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/bg">
	
	<SurfaceView android:id="@+id/surface2" xmlns:android="http://schemas.android.com/apk/res/android"
		android:layout_width="480dip" android:layout_height="640dip" android:layout_gravity="center_horizontal"
		android:layout_centerInParent="true"
		android:layout_weight="1">
	</SurfaceView>
	
	<Button android:id="@+id/Button01_rec_start_stop" android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:text="record start/stop"
			android:layout_gravity="center_horizontal">
  	</Button>
</RelativeLayout>
================================custom_video_camera_example.xml =================