<NME> VideoStream.java
<BEF> /*
 * Copyright (C) 2011-2015 GUIGUI Simon, fyhertz@gmail.com
 *
 * This file is part of libstreaming (https://github.com/fyhertz/libstreaming)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.majorkernelpanic.streaming.video;

package net.majorkernelpanic.streaming.video;

import java.io.IOException;

import net.majorkernelpanic.streaming.MediaStream;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.media.MediaRecorder;
import android.util.Log;
import android.view.SurfaceHolder;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
	protected VideoQuality mQuality = VideoQuality.defaultVideoQualiy.clone();
	protected SurfaceHolder.Callback mSurfaceHolderCallback = null;
	protected SurfaceHolder mSurfaceHolder = null;
	protected boolean mFlashState = false,  mQualityHasChanged = false;
	protected int mVideoEncoder, mCameraId = 0;
	protected Camera mCamera;

	/** 
	 * Don't use this class directly.
	protected SharedPreferences mSettings = null;
	protected int mVideoEncoder, mCameraId = 0;
	public VideoStream() {
		this(CameraInfo.CAMERA_FACING_BACK);
	}	
	
	/** 
	 * Don't use this class directly
	 * @param camera Can be either CameraInfo.CAMERA_FACING_BACK or CameraInfo.CAMERA_FACING_FRONT
	protected boolean mSurfaceReady = false;
	protected boolean mUnlocked = false;
	protected boolean mPreviewStarted = false;
	protected boolean mUpdated = false;
	
	protected String mMimeType;
	protected String mEncoderName;
	 * Sets the camera that will be used to capture video.
	 * You can call this method at any time and changes will take effect next time you call {@link #prepare()}.
	 * @param camera Can be either CameraInfo.CAMERA_FACING_BACK or CameraInfo.CAMERA_FACING_FRONT
	 */	
	public void setCamera(int camera) {
		CameraInfo cameraInfo = new CameraInfo();
		int numberOfCameras = Camera.getNumberOfCameras();
	 */
	public VideoStream() {
		this(CameraInfo.CAMERA_FACING_BACK);
	}	

			}
		}
	}
	
	/**
	 * Sets a Surface to show a preview of recorded media (video). 
	 * You can call this method at any time and changes will take effect next time you call {@link #prepare()}.
	 */
	public void setPreviewDisplay(SurfaceHolder surfaceHolder) {
		if (mSurfaceHolderCallback != null && mSurfaceHolder != null) {
			mSurfaceHolder.removeCallback(mSurfaceHolderCallback);
		}
		mSurfaceHolderCallback = new Callback() {
			@Override
			public void surfaceDestroyed(SurfaceHolder holder) {
				if (VideoStream.this.isStreaming()) {
					VideoStream.this.stop();
					Log.d(TAG,"Surface destroyed: video streaming stopped.");
				}
			}
			@Override
			public void surfaceCreated(SurfaceHolder holder) {
				
			}
			@Override
			public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
				
			}
		};
		mSurfaceHolder = surfaceHolder;
		mSurfaceHolder.addCallback(mSurfaceHolderCallback);
	}

	/** Turns the LED on or off if phone has one. */
	public void setFlashState(boolean state) {
		mFlashState = state;
	}
	
	/** 
	 * Modifies the resolution of the stream. You can call this method at any time 
	 * and changes will take effect next time you call {@link #prepare()}.
					}
				}
			}
		} else {
			mFlashEnabled = state;
		if (mQuality.resX != width || mQuality.resY != height) {
			mQuality.resX = width;
			mQuality.resY = height;
			mQualityHasChanged = true;
		}
	}

	 */
	public synchronized void toggleFlash() {
		setFlashState(!mFlashEnabled);
	}

	/** Indicates whether or not the flash of the phone is on. */
	public void setVideoFramerate(int rate) {
		if (mQuality.framerate != rate) {
			mQuality.framerate = rate;
			mQualityHasChanged = true;
		}
	}

	 * @param orientation The orientation of the preview
	 */
	public void setPreviewOrientation(int orientation) {
		mRequestedOrientation = orientation;
		mUpdated = false;
	}
	public void setVideoEncodingBitrate(int bitrate) {
		if (mQuality.bitrate != bitrate) {
			mQuality.bitrate = bitrate;
			mQualityHasChanged = true;
		}
	}

	public void setVideoQuality(VideoQuality videoQuality) {
		if (!mRequestedQuality.equals(videoQuality)) {
			mRequestedQuality = videoQuality.clone();
			mUpdated = false;
		}
	public void setVideoQuality(VideoQuality videoQuality) {
		if (!mQuality.equals(videoQuality)) {
			mQuality = videoQuality;
			mQualityHasChanged = true;
		}
	}

		return mRequestedQuality;
	}

	/**
	 * Some data (SPS and PPS params) needs to be stored when {@link #getSessionDescription()} is called 
	protected void setVideoEncoder(int videoEncoder) {
		this.mVideoEncoder = videoEncoder;
	}
	
	/** Stops the stream. */
	public synchronized void stop() {
		super.stop();
		if (mCamera != null) {
			try {
				mCamera.reconnect();
			} catch (Exception e) {
				Log.e(TAG,e.getMessage()!=null?e.getMessage():"unknown error");
			}
			mCamera.stopPreview();
			try {
				mCamera.release();
			} catch (Exception e) {
				Log.e(TAG,e.getMessage()!=null?e.getMessage():"unknown error");
			}
			mCamera = null;
		}
	}

	/**
	 * Prepare the VideoStream, you can then call {@link #start()}.
	 * The underlying Camera will be opened and configured when you call this method so don't forget to deal with the RuntimeExceptions !
	 * Camera.open, Camera.setParameter, Camera.unlock may throw one !
	 */
	public void prepare() throws IllegalStateException, IOException {
		
		// Resets the recorder in case it is in a bad state
		mMediaRecorder.reset();
		
		if (mSurfaceHolder == null) 
			throw new IllegalStateException("Invalid surface holder !");
		
		if (mCamera == null) {
			mCamera = Camera.open(mCameraId);
			mCamera.setErrorCallback(new Camera.ErrorCallback() {
	public synchronized void stop() {
		if (mCamera != null) {
			if (mMode == MODE_MEDIACODEC_API) {
				mCamera.setPreviewCallbackWithBuffer(null);
			}
						// In this case the application must release the camera and instantiate a new one
						Log.e(TAG,"Media server died !");
						// We don't know in what thread we are so stop needs to be synchronized
						stop();
					} else {
						Log.e(TAG,"Error unknown with the camera: "+error);
					}	
				}
			});
		}

		// If an exception is thrown after the camera was open, we must absolutly release it !
		try {

			Parameters parameters = mCamera.getParameters();
			if (mFlashState) {
				if (parameters.getFlashMode()==null) {
					// The phone has no flash or the choosen camera can not toggle the flash
					throw new IllegalStateException("Can't turn the flash on !");
				} else {
					parameters.setFlashMode(mFlashState?Parameters.FLASH_MODE_TORCH:Parameters.FLASH_MODE_OFF);
					mCamera.setParameters(parameters);
				}
			}
			mCamera.setDisplayOrientation(mQuality.orientation);
			mCamera.unlock();
			
			mMediaRecorder.setCamera(mCamera);
			mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
			mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
			
			if (mode==MODE_DEFAULT) {
				mMediaRecorder.setMaxDuration(1000);
				mMediaRecorder.setMaxFileSize(Integer.MAX_VALUE);
			} else if (mModeDefaultWasUsed) {
				// On some phones a RuntimeException might be thrown :/
				try {
					mMediaRecorder.setMaxDuration(0);
					mMediaRecorder.setMaxFileSize(Integer.MAX_VALUE); 
				} catch (RuntimeException e) {
					Log.e(TAG,"setMaxDuration or setMaxFileSize failed !");
				}
			}
			
			mMediaRecorder.setVideoEncoder(mVideoEncoder);
			mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
			mMediaRecorder.setVideoSize(mQuality.resX,mQuality.resY);
			mMediaRecorder.setVideoFrameRate(mQuality.framerate);
			mMediaRecorder.setVideoEncodingBitRate(mQuality.bitrate);

			super.prepare();

			// Quality has been updated
			mQualityHasChanged = false;

		} catch (RuntimeException e) {
			mCamera.release();
			mCamera = null;
			throw e;
		} catch (IOException e) {
			mCamera.release();
			mCamera = null;
			throw e;
		}

	}

	public abstract String generateSessionDescription() throws IllegalStateException, IOException;

	/** 
	 * Releases ressources associated with the {@link VideoStream}. 
	 * The object can't be reused once this function has been called. 
	 **/
	public void release() {
		stop();
		super.release();
	}

}
	 * @throws RuntimeException Might happen if another app is already using the camera.
	 */
	private void openCamera() throws RuntimeException {
		final Semaphore lock = new Semaphore(0);
		final RuntimeException[] exception = new RuntimeException[1];
		mCameraThread = new Thread(new Runnable() {
			@Override
			public void run() {
				Looper.prepare();
				mCameraLooper = Looper.myLooper();
				try {
					mCamera = Camera.open(mCameraId);
				} catch (RuntimeException e) {
					exception[0] = e;
				} finally {
					lock.release();
					Looper.loop();
				}
			}
		});
		mCameraThread.start();
		lock.acquireUninterruptibly();
		if (exception[0] != null) throw new CameraInUseException(exception[0].getMessage());
	}

	protected synchronized void createCamera() throws RuntimeException {
		if (mSurfaceView == null)
			throw new InvalidSurfaceException("Invalid surface !");
		if (mSurfaceView.getHolder() == null || !mSurfaceReady) 
			throw new InvalidSurfaceException("Invalid surface !");

		if (mCamera == null) {
			openCamera();
			mUpdated = false;
			mUnlocked = false;
			mCamera.setErrorCallback(new Camera.ErrorCallback() {
				@Override
				public void onError(int error, Camera camera) {
					// On some phones when trying to use the camera facing front the media server will die
					// Whether or not this callback may be called really depends on the phone
					if (error == Camera.CAMERA_ERROR_SERVER_DIED) {
						// In this case the application must release the camera and instantiate a new one
						Log.e(TAG,"Media server died !");
						// We don't know in what thread we are so stop needs to be synchronized
						mCameraOpenedManually = false;
						stop();
					} else {
						Log.e(TAG,"Error unknown with the camera: "+error);
					}	
				}
			});

			try {

				// If the phone has a flash, we turn it on/off according to mFlashEnabled
				// setRecordingHint(true) is a very nice optimization if you plane to only use the Camera for recording
				Parameters parameters = mCamera.getParameters();
				if (parameters.getFlashMode()!=null) {
					parameters.setFlashMode(mFlashEnabled?Parameters.FLASH_MODE_TORCH:Parameters.FLASH_MODE_OFF);
				}
				parameters.setRecordingHint(true);
				mCamera.setParameters(parameters);
				mCamera.setDisplayOrientation(mOrientation);

				try {
					if (mMode == MODE_MEDIACODEC_API_2) {
						mSurfaceView.startGLThread();
						mCamera.setPreviewTexture(mSurfaceView.getSurfaceTexture());
					} else {
						mCamera.setPreviewDisplay(mSurfaceView.getHolder());
					}
				} catch (IOException e) {
					throw new InvalidSurfaceException("Invalid surface !");
				}

			} catch (RuntimeException e) {
				destroyCamera();
				throw e;
			}

		}
	}

	protected synchronized void destroyCamera() {
		if (mCamera != null) {
			if (mStreaming) super.stop();
			lockCamera();
			mCamera.stopPreview();
			try {
				mCamera.release();
			} catch (Exception e) {
				Log.e(TAG,e.getMessage()!=null?e.getMessage():"unknown error");
			}
			mCamera = null;
			mCameraLooper.quit();
			mUnlocked = false;
			mPreviewStarted = false;
		}	
	}

	protected synchronized void updateCamera() throws RuntimeException {
		
		// The camera is already correctly configured
		if (mUpdated) return;
		
		if (mPreviewStarted) {
			mPreviewStarted = false;
			mCamera.stopPreview();
		}

		Parameters parameters = mCamera.getParameters();
		mQuality = VideoQuality.determineClosestSupportedResolution(parameters, mQuality);
		int[] max = VideoQuality.determineMaximumSupportedFramerate(parameters);
		
		double ratio = (double)mQuality.resX/(double)mQuality.resY;
		mSurfaceView.requestAspectRatio(ratio);
		
		parameters.setPreviewFormat(mCameraImageFormat);
		parameters.setPreviewSize(mQuality.resX, mQuality.resY);
		parameters.setPreviewFpsRange(max[0], max[1]);

		try {
			mCamera.setParameters(parameters);
			mCamera.setDisplayOrientation(mOrientation);
			mCamera.startPreview();
			mPreviewStarted = true;
			mUpdated = true;
		} catch (RuntimeException e) {
			destroyCamera();
			throw e;
		}
	}

	protected void lockCamera() {
		if (mUnlocked) {
			Log.d(TAG,"Locking camera");
			try {
				mCamera.reconnect();
			} catch (Exception e) {
				Log.e(TAG,e.getMessage());
			}
			mUnlocked = false;
		}
	}

	protected void unlockCamera() {
		if (!mUnlocked) {
			Log.d(TAG,"Unlocking camera");
			try {	
				mCamera.unlock();
			} catch (Exception e) {
				Log.e(TAG,e.getMessage());
			}
			mUnlocked = true;
		}
	}


	/**
	 * Computes the average frame rate at which the preview callback is called.
	 * We will then use this average frame rate with the MediaCodec.  
	 * Blocks the thread in which this function is called.
	 */
	private void measureFramerate() {
		final Semaphore lock = new Semaphore(0);

		final Camera.PreviewCallback callback = new Camera.PreviewCallback() {
			int i = 0, t = 0;
			long now, oldnow, count = 0;
			@Override
			public void onPreviewFrame(byte[] data, Camera camera) {
				i++;
				now = System.nanoTime()/1000;
				if (i>3) {
					t += now - oldnow;
					count++;
				}
				if (i>20) {
					mQuality.framerate = (int) (1000000/(t/count)+1);
					lock.release();
				}
				oldnow = now;
			}
		};

		mCamera.setPreviewCallback(callback);

		try {
			lock.tryAcquire(2,TimeUnit.SECONDS);
			Log.d(TAG,"Actual framerate: "+mQuality.framerate);
			if (mSettings != null) {
				Editor editor = mSettings.edit();
				editor.putInt(PREF_PREFIX+"fps"+mRequestedQuality.framerate+","+mCameraImageFormat+","+mRequestedQuality.resX+mRequestedQuality.resY, mQuality.framerate);
				editor.commit();
			}
		} catch (InterruptedException e) {}

		mCamera.setPreviewCallback(null);

	}	

}

<MSG> libstreaming v3.0beta ready. MediaCodec API support on the way. Important changes in RtpSocket. New RtspClient class for wowza users.

<DFF> @@ -21,11 +21,22 @@
 package net.majorkernelpanic.streaming.video;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
 
 import net.majorkernelpanic.streaming.MediaStream;
+import net.majorkernelpanic.streaming.rtp.MediaCodecInputStream;
+import android.annotation.SuppressLint;
+import android.graphics.ImageFormat;
 import android.hardware.Camera;
 import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
 import android.media.MediaRecorder;
 import android.util.Log;
 import android.view.SurfaceHolder;
@@ -41,9 +52,12 @@ public abstract class VideoStream extends MediaStream {
 	protected VideoQuality mQuality = VideoQuality.defaultVideoQualiy.clone();
 	protected SurfaceHolder.Callback mSurfaceHolderCallback = null;
 	protected SurfaceHolder mSurfaceHolder = null;
-	protected boolean mFlashState = false,  mQualityHasChanged = false;
 	protected int mVideoEncoder, mCameraId = 0;
 	protected Camera mCamera;
+	protected boolean mCameraOpenedManually = true;
+	protected boolean mFlashState = false;
+	protected boolean mSurfaceReady = false;
+	protected boolean mUnlocked = false;
 
 	/** 
 	 * Don't use this class directly.
@@ -52,7 +66,7 @@ public abstract class VideoStream extends MediaStream {
 	public VideoStream() {
 		this(CameraInfo.CAMERA_FACING_BACK);
 	}	
-	
+
 	/** 
 	 * Don't use this class directly
 	 * @param camera Can be either CameraInfo.CAMERA_FACING_BACK or CameraInfo.CAMERA_FACING_FRONT
@@ -66,7 +80,7 @@ public abstract class VideoStream extends MediaStream {
 	 * Sets the camera that will be used to capture video.
 	 * You can call this method at any time and changes will take effect next time you call {@link #prepare()}.
 	 * @param camera Can be either CameraInfo.CAMERA_FACING_BACK or CameraInfo.CAMERA_FACING_FRONT
-	 */	
+	 */
 	public void setCamera(int camera) {
 		CameraInfo cameraInfo = new CameraInfo();
 		int numberOfCameras = Camera.getNumberOfCameras();
@@ -78,41 +92,112 @@ public abstract class VideoStream extends MediaStream {
 			}
 		}
 	}
-	
+
+	/**	Switch between the front facing and the back facing camera of the phone. 
+	 * If {@link startPreview()} has been called, the preview will be  briefly interrupted. 
+	 * If {@link start()} has been called, the stream will be  briefly interrupted.
+	 * You should not call this method from the main thread if you are already streaming. 
+	 * @throws IOException 
+	 * @throws RuntimeException 
+	 **/
+	public void switchCamera() throws RuntimeException, IOException {
+		if (Camera.getNumberOfCameras() == 1) throw new IllegalStateException("Phone only has one camera !");
+		boolean streaming = mStreaming;
+		boolean previewing = mCamera!=null && mCameraOpenedManually; 
+		mCameraId = (mCameraId == CameraInfo.CAMERA_FACING_BACK) ? CameraInfo.CAMERA_FACING_FRONT : CameraInfo.CAMERA_FACING_BACK; 
+		setCamera(mCameraId);
+		stopPreview();
+		if (previewing) startPreview();
+		if (streaming) start(); 
+	}
+
+	public int getCamera() {
+		return mCameraId;
+	}
+
 	/**
 	 * Sets a Surface to show a preview of recorded media (video). 
 	 * You can call this method at any time and changes will take effect next time you call {@link #prepare()}.
 	 */
-	public void setPreviewDisplay(SurfaceHolder surfaceHolder) {
+	public synchronized void setPreviewDisplay(SurfaceHolder surfaceHolder) {
 		if (mSurfaceHolderCallback != null && mSurfaceHolder != null) {
 			mSurfaceHolder.removeCallback(mSurfaceHolderCallback);
 		}
-		mSurfaceHolderCallback = new Callback() {
-			@Override
-			public void surfaceDestroyed(SurfaceHolder holder) {
-				if (VideoStream.this.isStreaming()) {
-					VideoStream.this.stop();
-					Log.d(TAG,"Surface destroyed: video streaming stopped.");
+		if (surfaceHolder != null) {
+			mSurfaceHolderCallback = new Callback() {
+				@Override
+				public void surfaceDestroyed(SurfaceHolder holder) {
+					mSurfaceReady = false;
+					if (VideoStream.this.mStreaming) {
+						VideoStream.this.stop();
+						Log.d(TAG,"Surface destroyed: video streaming stopped.");
+					}
+					if (mCamera != null) stopPreview();
+				}
+				@Override
+				public void surfaceCreated(SurfaceHolder holder) {
+					mSurfaceReady = true;
 				}
+				@Override
+				public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+					Log.d(TAG,"Surface Changed !");
+				}
+			};
+			mSurfaceHolder = surfaceHolder;
+			mSurfaceHolder.addCallback(mSurfaceHolderCallback);
+			mSurfaceReady = true;
+		}
+	}
+
+	/** Turns the LED on or off if phone has one. */
+	public synchronized void setFlashState(boolean state) {
+
+		// FIXME: Is it possible to toggle the flash while streaming on android 2.3 ?
+		// FIXME: It works on android 4.2 and 4.3
+		
+		mFlashState = state;
+
+		// If the camera has already been opened, we apply the change immediately
+		// FIXME: Will this work on Android 2.3 ?
+		if (mCamera != null) {
+
+			// Needed on Android 2.3
+			if (mStreaming && mMode == MODE_STREAMING_LEGACY) {
+				lockCamera();
 			}
-			@Override
-			public void surfaceCreated(SurfaceHolder holder) {
-				
+
+			Parameters parameters = mCamera.getParameters();
+
+			// We test if the phone has a flash
+			if (parameters.getFlashMode()==null) {
+				// The phone has no flash or the choosen camera can not toggle the flash
+				throw new RuntimeException("Can't turn the flash on !");
+			} else {
+				parameters.setFlashMode(mFlashState?Parameters.FLASH_MODE_TORCH:Parameters.FLASH_MODE_OFF);
+				try {
+					mCamera.setParameters(parameters);
+				} catch (RuntimeException e) {
+					throw new RuntimeException("Can't turn the flash on !");	
+				}
 			}
-			@Override
-			public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-				
+
+			// Needed on Android 2.3
+			if (mStreaming && mMode == MODE_STREAMING_LEGACY) {
+				unlockCamera();
 			}
-		};
-		mSurfaceHolder = surfaceHolder;
-		mSurfaceHolder.addCallback(mSurfaceHolderCallback);
+
+		}
 	}
 
-	/** Turns the LED on or off if phone has one. */
-	public void setFlashState(boolean state) {
-		mFlashState = state;
+	/** Toggle the LED of the phone if it has one. */
+	public void toggleFlash() {
+		setFlashState(!mFlashState);
 	}
-	
+
+	public boolean getFlashState() {
+		return mFlashState;
+	}
+
 	/** 
 	 * Modifies the resolution of the stream. You can call this method at any time 
 	 * and changes will take effect next time you call {@link #prepare()}.
@@ -124,7 +209,6 @@ public abstract class VideoStream extends MediaStream {
 		if (mQuality.resX != width || mQuality.resY != height) {
 			mQuality.resX = width;
 			mQuality.resY = height;
-			mQualityHasChanged = true;
 		}
 	}
 
@@ -137,7 +221,6 @@ public abstract class VideoStream extends MediaStream {
 	public void setVideoFramerate(int rate) {
 		if (mQuality.framerate != rate) {
 			mQuality.framerate = rate;
-			mQualityHasChanged = true;
 		}
 	}
 
@@ -150,7 +233,6 @@ public abstract class VideoStream extends MediaStream {
 	public void setVideoEncodingBitrate(int bitrate) {
 		if (mQuality.bitrate != bitrate) {
 			mQuality.bitrate = bitrate;
-			mQualityHasChanged = true;
 		}
 	}
 
@@ -162,7 +244,6 @@ public abstract class VideoStream extends MediaStream {
 	public void setVideoQuality(VideoQuality videoQuality) {
 		if (!mQuality.equals(videoQuality)) {
 			mQuality = videoQuality;
-			mQualityHasChanged = true;
 		}
 	}
 
@@ -174,39 +255,27 @@ public abstract class VideoStream extends MediaStream {
 	protected void setVideoEncoder(int videoEncoder) {
 		this.mVideoEncoder = videoEncoder;
 	}
-	
+
 	/** Stops the stream. */
 	public synchronized void stop() {
+		if (mMode == MODE_STREAMING_JB) {
+			mCamera.setPreviewCallback(null);
+		}
 		super.stop();
-		if (mCamera != null) {
-			try {
-				mCamera.reconnect();
-			} catch (Exception e) {
-				Log.e(TAG,e.getMessage()!=null?e.getMessage():"unknown error");
-			}
-			mCamera.stopPreview();
-			try {
-				mCamera.release();
-			} catch (Exception e) {
-				Log.e(TAG,e.getMessage()!=null?e.getMessage():"unknown error");
-			}
-			mCamera = null;
+		if (mMode == MODE_STREAMING_LEGACY) {
+			lockCamera();
+		}
+		if (!mCameraOpenedManually) {
+			stopPreview();
 		}
+		mCameraOpenedManually = true;
 	}
 
-	/**
-	 * Prepare the VideoStream, you can then call {@link #start()}.
-	 * The underlying Camera will be opened and configured when you call this method so don't forget to deal with the RuntimeExceptions !
-	 * Camera.open, Camera.setParameter, Camera.unlock may throw one !
-	 */
-	public void prepare() throws IllegalStateException, IOException {
-		
-		// Resets the recorder in case it is in a bad state
-		mMediaRecorder.reset();
-		
-		if (mSurfaceHolder == null) 
+	public synchronized void startPreview() throws RuntimeException, IOException {
+
+		if (mSurfaceHolder == null || mSurfaceHolder.getSurface() == null || !mSurfaceReady)
 			throw new IllegalStateException("Invalid surface holder !");
-		
+
 		if (mCamera == null) {
 			mCamera = Camera.open(mCameraId);
 			mCamera.setErrorCallback(new Camera.ErrorCallback() {
@@ -218,79 +287,253 @@ public abstract class VideoStream extends MediaStream {
 						// In this case the application must release the camera and instantiate a new one
 						Log.e(TAG,"Media server died !");
 						// We don't know in what thread we are so stop needs to be synchronized
+						mCameraOpenedManually = false;
 						stop();
 					} else {
 						Log.e(TAG,"Error unknown with the camera: "+error);
 					}	
 				}
 			});
-		}
-
-		// If an exception is thrown after the camera was open, we must absolutly release it !
-		try {
 
 			Parameters parameters = mCamera.getParameters();
+
+			if (mMode == MODE_STREAMING_JB) {
+				getClosestSupportedQuality(parameters);
+				parameters.setPreviewFormat(ImageFormat.YV12);
+				parameters.setPreviewSize(mQuality.resX, mQuality.resY);
+				parameters.setPreviewFrameRate(mQuality.framerate);
+			}
+
 			if (mFlashState) {
 				if (parameters.getFlashMode()==null) {
 					// The phone has no flash or the choosen camera can not toggle the flash
 					throw new IllegalStateException("Can't turn the flash on !");
 				} else {
 					parameters.setFlashMode(mFlashState?Parameters.FLASH_MODE_TORCH:Parameters.FLASH_MODE_OFF);
-					mCamera.setParameters(parameters);
 				}
 			}
+
+			mCamera.setParameters(parameters);
 			mCamera.setDisplayOrientation(mQuality.orientation);
-			mCamera.unlock();
-			
-			mMediaRecorder.setCamera(mCamera);
-			mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-			mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-			
-			if (mode==MODE_DEFAULT) {
-				mMediaRecorder.setMaxDuration(1000);
-				mMediaRecorder.setMaxFileSize(Integer.MAX_VALUE);
-			} else if (mModeDefaultWasUsed) {
-				// On some phones a RuntimeException might be thrown :/
-				try {
-					mMediaRecorder.setMaxDuration(0);
-					mMediaRecorder.setMaxFileSize(Integer.MAX_VALUE); 
-				} catch (RuntimeException e) {
-					Log.e(TAG,"setMaxDuration or setMaxFileSize failed !");
-				}
-			}
-			
-			mMediaRecorder.setVideoEncoder(mVideoEncoder);
-			mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
-			mMediaRecorder.setVideoSize(mQuality.resX,mQuality.resY);
-			mMediaRecorder.setVideoFrameRate(mQuality.framerate);
-			mMediaRecorder.setVideoEncodingBitRate(mQuality.bitrate);
+			mCamera.setPreviewDisplay(mSurfaceHolder);
+
+			mCamera.startPreview();
+
+		}
+
+	}
 
-			super.prepare();
+	public synchronized void stopPreview() {
 
-			// Quality has been updated
-			mQualityHasChanged = false;
+		if (mStreaming) super.stop();
 
-		} catch (RuntimeException e) {
-			mCamera.release();
+		if (mCamera != null) {
+			lockCamera();
+			mCamera.stopPreview();
+			try {
+				mCamera.release();
+			} catch (Exception e) {
+				Log.e(TAG,e.getMessage()!=null?e.getMessage():"unknown error");
+			}
 			mCamera = null;
-			throw e;
+			mUnlocked = false;
+		}		
+	}
+
+	/**
+	 * Starts the stream.
+	 * This will also open the camera and dispay the preview if {@link #startPreview()}
+	 * has not laready been called.
+	 */
+	public synchronized void start() throws IllegalStateException, IOException {
+		super.start();
+	}
+
+	/**
+	 * Encoding of the audio/video is done by a MediaRecorder.
+	 */
+	protected void encodeWithMediaRecorder() throws IOException {
+
+		// We need a local socket to forward data output by the camera to the packetizer
+		createSockets();
+
+		// Opens the camera if needed
+		if (mCamera == null) {
+			mCameraOpenedManually = false;
+			// Will start the preview if not already started !
+			Log.d(TAG,"Preview must be started to record video !");
+			startPreview();
+		}
+
+		unlockCamera();
+	
+		mMediaRecorder = new MediaRecorder();
+		mMediaRecorder.setCamera(mCamera);
+		mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+		mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+		mMediaRecorder.setVideoEncoder(mVideoEncoder);
+		mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
+		mMediaRecorder.setVideoSize(mQuality.resX,mQuality.resY);
+		mMediaRecorder.setVideoFrameRate(mQuality.framerate);
+		mMediaRecorder.setVideoEncodingBitRate(mQuality.bitrate);
+
+		// We write the ouput of the camera in a local socket instead of a file !			
+		// This one little trick makes streaming feasible quiet simply: data from the camera
+		// can then be manipulated at the other end of the socket
+		mMediaRecorder.setOutputFile(mSender.getFileDescriptor());
+
+		mMediaRecorder.prepare();
+		mMediaRecorder.start();
+
+		try {
+			// mReceiver.getInputStream contains the data from the camera
+			// the mPacketizer encapsulates this stream in an RTP stream and send it over the network
+			mPacketizer.setDestination(mDestination, mRtpPort, mRtcpPort);
+			mPacketizer.setInputStream(mReceiver.getInputStream());
+			mPacketizer.start();
+			mStreaming = true;
 		} catch (IOException e) {
-			mCamera.release();
-			mCamera = null;
-			throw e;
+			stop();
+			throw new IOException("Something happened with the local sockets :/ Start failed !");
+		}
+
+	}
+
+	/**
+	 * Encoding of the audio/video is done by a MediaCodec.
+	 */
+	@SuppressLint({ "InlinedApi", "NewApi" })
+	protected void encodeWithMediaCodec() throws RuntimeException, IOException {
+
+		// Opens the camera if needed
+		if (mCamera == null) {
+			mCameraOpenedManually = false;
+			// Will start the preview if not already started !
+			startPreview();
+		}
+
+		mMediaCodec = MediaCodec.createEncoderByType("video/avc");
+		MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", mQuality.resX, mQuality.resY);
+		mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, mQuality.bitrate);
+		mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, mQuality.framerate);	
+		mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
+		mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 4);
+		mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+		mMediaCodec.start();
+
+		final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
+
+		mCamera.setPreviewCallback(new Camera.PreviewCallback() {
+			@Override
+			public void onPreviewFrame(byte[] data, Camera camera) {
+				long now = System.nanoTime()/1000, timeout = 1000000/mQuality.framerate;
+				int bufferIndex = mMediaCodec.dequeueInputBuffer(timeout);
+
+				if (bufferIndex>=0) {
+					inputBuffers[bufferIndex].clear();
+					inputBuffers[bufferIndex].put(data, 0, data.length);
+					mMediaCodec.queueInputBuffer(bufferIndex, 0, data.length, System.nanoTime()/1000, 0);
+				} else {
+					Log.e(TAG,"No buffer available !");
+				}
+
+			}
+		});
+
+		try {
+			// mReceiver.getInputStream contains the data from the camera
+			// the mPacketizer encapsulates this stream in an RTP stream and send it over the network
+			mPacketizer.setDestination(mDestination, mRtpPort, mRtcpPort);
+			mPacketizer.setInputStream(new MediaCodecInputStream(mMediaCodec));
+			mPacketizer.start();
+			mStreaming = true;
+		} catch (IOException e) {
+			stop();
+			throw new IOException("Something happened with the local sockets :/ Start failed !");
 		}
 
 	}
 
 	public abstract String generateSessionDescription() throws IllegalStateException, IOException;
 
+	/** Verifies if streaming using the MediaCodec API is feasable. */
+	@SuppressLint("NewApi")
+	private void checkMediaCodecAPI() {
+		for(int j = MediaCodecList.getCodecCount() - 1; j >= 0; j--){
+			MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(j);
+			if (codecInfo.isEncoder()) {
+				MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType("video/avc");
+				for (int i = 0; i < capabilities.colorFormats.length; i++) {
+					int format = capabilities.colorFormats[i];
+					Log.e(TAG, codecInfo.getName()+" with color format " + format);           
+				}
+				/*for (int i = 0; i < capabilities.profileLevels; i++) {
+					int format = capabilities.colorFormats[i];
+					Log.e(TAG, codecInfo.getName()+" with color format " + format);           
+				}*/
+			}
+		}
+	}
+
 	/** 
-	 * Releases ressources associated with the {@link VideoStream}. 
-	 * The object can't be reused once this function has been called. 
+	 * Checks if the resolution and the framerate selected are supported by the camera.
+	 * If not, it modifies it by supported parameters. 
 	 **/
-	public void release() {
-		stop();
-		super.release();
+	private void getClosestSupportedQuality(Camera.Parameters parameters) {
+
+		// Resolutions
+		String supportedSizesStr = "Supported resolutions: ";
+		List<Size> supportedSizes = parameters.getSupportedPreviewSizes();
+		for (Iterator<Size> it = supportedSizes.iterator(); it.hasNext();) {
+			Size size = it.next();
+			supportedSizesStr += size.width+"x"+size.height+(it.hasNext()?", ":"");
+		}
+		Log.v(TAG,supportedSizesStr);
+
+		// Frame rates
+		String supportedFrameRatesStr = "Supported frame rates: ";
+		List<Integer> supportedFrameRates = parameters.getSupportedPreviewFrameRates();
+		for (Iterator<Integer> it = supportedFrameRates.iterator(); it.hasNext();) {
+			supportedFrameRatesStr += it.next()+"fps"+(it.hasNext()?", ":"");
+		}
+		//Log.v(TAG,supportedFrameRatesStr);
+
+		int minDist = Integer.MAX_VALUE, newFps = mQuality.framerate;
+		if (!supportedFrameRates.contains(mQuality.framerate)) {
+			for (Iterator<Integer> it = supportedFrameRates.iterator(); it.hasNext();) {
+				int fps = it.next();
+				int dist = Math.abs(fps - mQuality.framerate);
+				if (dist<minDist) {
+					minDist = dist;
+					newFps = fps;
+				}
+			}
+			Log.v(TAG,"Frame rate modified: "+mQuality.framerate+"->"+newFps);
+			//mQuality.framerate = newFps;
+		}
+
+	}
+	
+	protected void lockCamera() {
+		if (mUnlocked) {
+			try {
+				mCamera.reconnect();
+			} catch (Exception e) {
+				Log.e(TAG,e.getMessage());
+			}
+			mUnlocked = false;
+		}
 	}
 
+	protected void unlockCamera() {
+		if (!mUnlocked) {
+			try {	
+				mCamera.unlock();
+			} catch (Exception e) {
+				Log.e(TAG,e.getMessage());
+			}
+			mUnlocked = true;
+		}
+	}
+	
 }

libstreaming v3.0beta ready. MediaCodec API support on the way. Important changes in RtpSocket. New RtspClient class for wowza users.