<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 java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.List;

import net.majorkernelpanic.streaming.exceptions.InvalidSurfaceException;
import net.majorkernelpanic.streaming.gl.SurfaceView;
import net.majorkernelpanic.streaming.hw.EncoderDebugger;
import net.majorkernelpanic.streaming.hw.NV21Convertor;
import net.majorkernelpanic.streaming.rtp.MediaCodecInputStream;
import android.annotation.SuppressLint;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;

import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;

/** 

	protected final static String TAG = "VideoStream";

	protected VideoQuality mQuality = VideoQuality.DEFAULT_VIDEO_QUALITY.clone();
	protected SurfaceHolder.Callback mSurfaceHolderCallback = null;
	protected SurfaceHolder mSurfaceHolder = null;
	protected int mVideoEncoder, mCameraId = 0;
	protected Camera mCamera;
	protected boolean mCameraOpenedManually = true;
	protected boolean mFlashState = false;
	protected boolean mSurfaceReady = false;
	protected boolean mUnlocked = false;
	protected boolean mPreviewStarted = false;

	/** 
	 * Don't use this class directly.
	 * Uses CAMERA_FACING_BACK by default.
	protected int mCameraImageFormat;
	protected int mMaxFps = 0;	

	/** 
	 * Don't use this class directly.
	 * Uses CAMERA_FACING_BACK by default.
	 * Don't use this class directly
	 * @param camera Can be either CameraInfo.CAMERA_FACING_BACK or CameraInfo.CAMERA_FACING_FRONT
	 */
	public VideoStream(int camera) {
		super();
		setCamera(camera);
		// TODO: Remove this when encoding with the MediaCodec API is ready
		setMode(MODE_MEDIARECORDER_API);
	}

	/**
	 * 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 start the stream.
	}

	/**
	 * Returns the id of the camera currently selected. 
	 * Can be either {@link CameraInfo#CAMERA_FACING_BACK} or 
	 * {@link CameraInfo#CAMERA_FACING_FRONT}.
	 */
	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 #start()}.
	 */
	public synchronized void setSurfaceView(SurfaceView view) {
		mSurfaceView = view;
		if (mSurfaceHolderCallback != null && mSurfaceView != null && mSurfaceView.getHolder() != null) {
			mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
		}
		if (mSurfaceView != null && mSurfaceView.getHolder() != null) {
			mSurfaceHolderCallback = new Callback() {
				@Override
				public void surfaceDestroyed(SurfaceHolder holder) {
					mSurfaceReady = false;
					stopPreview();
					Log.d(TAG,"Surface destroyed !");
				}
				@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 !");
				}
			};
			mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
			mSurfaceReady = true;
		}
	}

	/** Turns the LED on or off if phone has one. */
	public synchronized void setFlashState(boolean state) {
		// If the camera has already been opened, we apply the change immediately
		if (mCamera != null) {

			if (mStreaming && mMode == MODE_MEDIARECORDER_API) {
				lockCamera();
			}

			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(state?Parameters.FLASH_MODE_TORCH:Parameters.FLASH_MODE_OFF);
				try {
					mCamera.setParameters(parameters);
					mFlashEnabled = state;
				} catch (RuntimeException e) {
					mFlashEnabled = false;
					throw new RuntimeException("Can't turn the flash on !");
				} finally {
					if (mStreaming && mMode == MODE_MEDIARECORDER_API) {
						unlockCamera();
					}
				}
			}
		} else {
			mFlashEnabled = state;
		}
	}

	/** 
	 * Toggles the LED of the phone if it has one.
	 * You can get the current state of the flash with {@link VideoStream#getFlashState()}.
	 */
	public synchronized void toggleFlash() {
		setFlashState(!mFlashEnabled);
	}

	/** Indicates whether or not the flash of the phone is on. */
	public boolean getFlashState() {
		return mFlashEnabled;
	}

	/** 
	 * Sets the orientation of the preview.
	 * @param orientation The orientation of the preview
	 */
	public void setPreviewOrientation(int orientation) {
		mRequestedOrientation = orientation;
		mUpdated = false;
	}
	
	/** 
	 * Sets the configuration of the stream. You can call this method at any time 
	 * and changes will take effect next time you call {@link #configure()}.
	 * @param videoQuality Quality of the stream
	 */
	public void setVideoQuality(VideoQuality videoQuality) {
		if (!mRequestedQuality.equals(videoQuality)) {
			mRequestedQuality = videoQuality.clone();
			mUpdated = false;
		}
	}

	/** 
	 * Returns the quality of the stream.  
	 */
	public VideoQuality getVideoQuality() {
		return mRequestedQuality;
	}

	/**
	 * Some data (SPS and PPS params) needs to be stored when {@link #getSessionDescription()} is called 
	 * @param prefs The SharedPreferences that will be used to save SPS and PPS parameters
	 */
	public void setPreferences(SharedPreferences prefs) {
		mSettings = prefs;
	}

	/**
	 * Configures the stream. You need to call this before calling {@link #getSessionDescription()} 
	 * to apply your configuration of the stream.
	 */
	public synchronized void configure() throws IllegalStateException, IOException {
		super.configure();
		mOrientation = mRequestedOrientation;
	}	
	
	/**
	 * Starts the stream.
	 * This will also open the camera and display the preview 
	 * if {@link #startPreview()} has not already been called.
	 */
	public synchronized void start() throws IllegalStateException, IOException {
		if (!mPreviewStarted) mCameraOpenedManually = false;
		super.start();
		Log.d(TAG,"Stream configuration: FPS: "+mQuality.framerate+" Width: "+mQuality.resX+" Height: "+mQuality.resY);
	}

	/** Stops the stream. */
	public synchronized void stop() {
		if (mCamera != null) {
			if (mMode == MODE_MEDIACODEC_API) {
				mCamera.setPreviewCallbackWithBuffer(null);
			}
			if (mMode == MODE_MEDIACODEC_API_2) {
				((SurfaceView)mSurfaceView).removeMediaCodecSurface();
			}
			super.stop();
			// We need to restart the preview
			if (!mCameraOpenedManually) {
				destroyCamera();
	 */
	public void setVideoQuality(VideoQuality videoQuality) {
		if (!mQuality.equals(videoQuality)) {
			mQuality = videoQuality;
		}
	}

		}
	}

	public synchronized void startPreview() 
			throws CameraInUseException, 
			InvalidSurfaceException, 
			RuntimeException {
		
		mCameraOpenedManually = true;
		if (!mPreviewStarted) {
			createCamera();
			updateCamera();
		}
	}

	/**
	 * Stops the preview.
	 */
	public synchronized void stopPreview() {
		mCameraOpenedManually = false;
		stop();
	}

	/**
	 * Video encoding is done by a MediaRecorder.
	 */
	/** Stops the stream. */
	public synchronized void stop() {
		if (mCamera != null) {
			if (mMode == MODE_MEDIACODEC_API) {
				mCamera.setPreviewCallback(null);
			}
			super.stop();
		// Reopens the camera if needed
		destroyCamera();
		createCamera();

		// The camera must be unlocked before the MediaRecorder can use it
		unlockCamera();

		try {
			mMediaRecorder = new MediaRecorder();
			mMediaRecorder.setCamera(mCamera);
			mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
			mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
			mMediaRecorder.setVideoEncoder(mVideoEncoder);
			mMediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
			mMediaRecorder.setVideoSize(mRequestedQuality.resX,mRequestedQuality.resY);
			mMediaRecorder.setVideoFrameRate(mRequestedQuality.framerate);

			// The bandwidth actually consumed is often above what was requested 
			mMediaRecorder.setVideoEncodingBitRate((int)(mRequestedQuality.bitrate*0.8));

			// We write the output 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
			FileDescriptor fd = null;
			if (sPipeApi == PIPE_API_PFD) {
				fd = mParcelWrite.getFileDescriptor();
			} else  {
				fd = mSender.getFileDescriptor();
			}
			mMediaRecorder.setOutputFile(fd);

			mMediaRecorder.prepare();
			mMediaRecorder.start();

		} catch (Exception e) {
			throw new ConfNotSupportedException(e.getMessage());
		}

		InputStream is = null;

		if (sPipeApi == PIPE_API_PFD) {
			is = new ParcelFileDescriptor.AutoCloseInputStream(mParcelRead);
		} else  {
			is = mReceiver.getInputStream();
		}

		// This will skip the MPEG4 header if this step fails we can't stream anything :(
		try {
			byte buffer[] = new byte[4];
			// Skip all atoms preceding mdat atom
			while (!Thread.interrupted()) {
				while (is.read() != 'm');
				is.read(buffer,0,3);
				if (buffer[0] == 'd' && buffer[1] == 'a' && buffer[2] == 't') break;
			}
		} catch (IOException e) {
			Log.e(TAG,"Couldn't skip mp4 header :/");
			stop();
			throw e;
		}

		// The packetizer encapsulates the bit stream in an RTP stream and send it over the network
		mPacketizer.setInputStream(is);
		mPacketizer.start();

		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
		if (mMode == MODE_MEDIACODEC_API_2) {
			// Uses the method MediaCodec.createInputSurface to feed the encoder
			encodeWithMediaCodecMethod2();
		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
		// Starts the preview if needed
		if (!mPreviewStarted) {
			try {
				mCamera.startPreview();
				mPreviewStarted = true;
			} catch (RuntimeException e) {
				destroyCamera();
				throw e;

	}

	/**
	 * Encoding of the audio/video is done by a MediaCodec.
	 */
	@SuppressLint({ "InlinedApi", "NewApi" })
	protected void encodeWithMediaCodec() throws RuntimeException, IOException {

		// Opens the camera if needed
		createCamera();

		// Starts the preview if needed
		if (!mPreviewStarted) {
					if (bufferIndex>=0) {
						inputBuffers[bufferIndex].clear();
						if (data == null) Log.e(TAG,"Symptom of the \"Callback buffer was to small\" problem...");
						else convertor.convert(data, inputBuffers[bufferIndex]);
						mMediaCodec.queueInputBuffer(bufferIndex, 0, inputBuffers[bufferIndex].position(), now, 0);
					} else {
			}
		}

		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 !");
				}

			}
		});

		((SurfaceView)mSurfaceView).addMediaCodecSurface(surface);
		mMediaCodec.start();

		// The packetizer encapsulates the bit stream in an RTP stream and send it over the network
		mPacketizer.setInputStream(new MediaCodecInputStream(mMediaCodec));
		mPacketizer.start();

		mStreaming = true;

	}


	}

	public abstract String generateSessionDescription() throws IllegalStateException, IOException;

	protected synchronized void createCamera() throws RuntimeException, IOException {
		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 {

			Parameters parameters = mCamera.getParameters();

			if (mMode == MODE_MEDIACODEC_API) {
				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
			openCamera();
			mUpdated = false;
			mUnlocked = false;
			mCamera.setErrorCallback(new Camera.ErrorCallback() {
				@Override

			try {
				mCamera.setParameters(parameters);
				mCamera.setDisplayOrientation(mQuality.orientation);
				mCamera.setPreviewDisplay(mSurfaceHolder);
			} catch (RuntimeException e) {
				destroyCamera();
						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);
			mUnlocked = false;
			mPreviewStarted = false;
		}	
	}	
	
	/** 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);           
				}*/
			}
		}
	}

	/** 
	 * Checks if the resolution and the framerate selected are supported by the camera.
	 * If not, it modifies it by supported parameters. 
	 * FIXME: Not reliable, more or less useless :(
	 **/
	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)) {
				editor.putInt(PREF_PREFIX+"fps"+mRequestedQuality.framerate+","+mCameraImageFormat+","+mRequestedQuality.resX+mRequestedQuality.resY, mQuality.framerate);
				editor.commit();
			}
		} catch (InterruptedException e) {}

		mCamera.setPreviewCallback(null);
				}
			}
			Log.v(TAG,"Frame rate modified: "+mQuality.framerate+"->"+newFps);
			//mQuality.framerate = newFps;
		}

	}

	protected void lockCamera() {

<MSG> Better support of the MediaCodec API, but still disabled by default. Some major bug fixes in the RtpSocket and the implementation of RTCP.

<DFF> @@ -21,7 +21,10 @@
 package net.majorkernelpanic.streaming.video;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 
@@ -38,7 +41,9 @@ import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
 import android.media.MediaFormat;
 import android.media.MediaRecorder;
+import android.os.Build;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.SurfaceHolder;
 import android.view.SurfaceHolder.Callback;
 
@@ -49,17 +54,27 @@ public abstract class VideoStream extends MediaStream {
 
 	protected final static String TAG = "VideoStream";
 
+	private static HashMap<String,SparseArray<ArrayList<String>>> sSupportedColorFormats = new HashMap<String, SparseArray<ArrayList<String>>>(); 
+
 	protected VideoQuality mQuality = VideoQuality.DEFAULT_VIDEO_QUALITY.clone();
 	protected SurfaceHolder.Callback mSurfaceHolderCallback = null;
 	protected SurfaceHolder mSurfaceHolder = null;
 	protected int mVideoEncoder, mCameraId = 0;
 	protected Camera mCamera;
+	
 	protected boolean mCameraOpenedManually = true;
 	protected boolean mFlashState = false;
 	protected boolean mSurfaceReady = false;
 	protected boolean mUnlocked = false;
 	protected boolean mPreviewStarted = false;
 
+	protected CodecManager.Codecs mCodecs;
+	protected String mMimeType;
+	protected String mEncoderName;
+	protected int mEncoderColorFormat;
+	protected int mCameraImageFormat;
+	protected int mMaxFps = 0;	
+
 	/** 
 	 * Don't use this class directly.
 	 * Uses CAMERA_FACING_BACK by default.
@@ -72,13 +87,49 @@ public abstract class VideoStream extends MediaStream {
 	 * Don't use this class directly
 	 * @param camera Can be either CameraInfo.CAMERA_FACING_BACK or CameraInfo.CAMERA_FACING_FRONT
 	 */
+	@SuppressLint("InlinedApi")
 	public VideoStream(int camera) {
 		super();
 		setCamera(camera);
-		// TODO: Remove this when encoding with the MediaCodec API is ready
-		setMode(MODE_MEDIARECORDER_API);
 	}
 
+	/**
+	 * Called by subclasses. 
+	 */
+	protected void init(String mimeType) {
+		mCameraImageFormat = ImageFormat.YV12;
+		mMimeType = mimeType;
+		
+		if (sSuggestedMode == MODE_MEDIACODEC_API) {
+			mCodecs = CodecManager.Selector.findCodecsFormMimeType(mMimeType, false);
+			
+			if (mCodecs.hardwareCodec == null && mCodecs.softwareCodec == null ) {
+				mMode = MODE_MEDIARECORDER_API;
+				Log.e(TAG,"No encoder can be used on this phone, we will use the MediaRecorder API");
+				
+			} else if (Build.VERSION.SDK_INT<18) {
+				if (mCodecs.softwareCodec != null) {
+					// On android 4.1 and 4.2, hardware encoders can behave oddly...
+					mEncoderColorFormat = mCodecs.softwareColorFormat;
+					mEncoderName = mCodecs.softwareCodec;					
+				} else {
+					mMode = MODE_MEDIARECORDER_API;
+					Log.e(TAG,"Not using the hardware encoder because phone is running 4.1 or 4.2, we will use the MediaRecorder API");
+				}
+				
+			} else {
+				if (mCodecs.hardwareCodec != null) {
+					mEncoderColorFormat = mCodecs.hardwareColorFormat;
+					mEncoderName = mCodecs.hardwareCodec;
+				} else {
+					mEncoderColorFormat = mCodecs.softwareColorFormat;
+					mEncoderName = mCodecs.softwareCodec;	
+				}
+			}
+			
+		}
+	}
+	
 	/**
 	 * 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 start the stream.
@@ -243,7 +294,7 @@ public abstract class VideoStream extends MediaStream {
 	 */
 	public void setVideoQuality(VideoQuality videoQuality) {
 		if (!mQuality.equals(videoQuality)) {
-			mQuality = videoQuality;
+			mQuality = videoQuality.clone();
 		}
 	}
 
@@ -276,7 +327,7 @@ public abstract class VideoStream extends MediaStream {
 	/** Stops the stream. */
 	public synchronized void stop() {
 		if (mCamera != null) {
-			if (mMode == MODE_MEDIACODEC_API) {
+			if ((mMode&MODE_MEDIACODEC_API)!=0) {
 				mCamera.setPreviewCallback(null);
 			}
 			super.stop();
@@ -348,7 +399,9 @@ public abstract class VideoStream extends MediaStream {
 		mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
 		mMediaRecorder.setVideoSize(mQuality.resX,mQuality.resY);
 		mMediaRecorder.setVideoFrameRate(mQuality.framerate);
-		mMediaRecorder.setVideoEncodingBitRate(mQuality.bitrate);
+		
+		// The bandwidth actually consumed is often above what was requested 
+		mMediaRecorder.setVideoEncodingBitRate((int)(mQuality.bitrate*0.8));
 
 		// 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
@@ -358,6 +411,21 @@ public abstract class VideoStream extends MediaStream {
 		mMediaRecorder.prepare();
 		mMediaRecorder.start();
 
+		// This will skip the MPEG4 header if this step fails we can't stream anything :(
+		InputStream is = mReceiver.getInputStream();
+		try {
+			byte buffer[] = new byte[4];
+			// Skip all atoms preceding mdat atom
+			while (!Thread.interrupted()) {
+				while (is.read() != 'm');
+				is.read(buffer,0,3);
+				if (buffer[0] == 'd' && buffer[1] == 'a' && buffer[2] == 't') break;
+			}
+		} catch (IOException e) {
+			Log.e(TAG,"Couldn't skip mp4 header :/");
+			stop();
+		}
+
 		try {
 			// mReceiver.getInputStream contains the data from the camera
 			// the mPacketizer encapsulates this stream in an RTP stream and send it over the network
@@ -372,14 +440,28 @@ public abstract class VideoStream extends MediaStream {
 
 	}
 
+
 	/**
 	 * Encoding of the audio/video is done by a MediaCodec.
 	 */
-	@SuppressLint({ "InlinedApi", "NewApi" })
 	protected void encodeWithMediaCodec() throws RuntimeException, IOException {
+		if ((mMode&MODE_MEDIACODEC_API_2)!=0) {
+			// Uses the method MediaCodec.createInputSurface to feed the encoder
+			encodeWithMediaCodecMethod2();
+		} else {
+			// Uses dequeueInputBuffer to feed the encoder
+			encodeWithMediaCodecMethod1();
+		}
+	}	
 
-		// Opens the camera if needed
+	/**
+	 * Encoding of the audio/video is done by a MediaCodec.
+	 */
+	@SuppressLint("NewApi")
+	protected void encodeWithMediaCodecMethod1() throws RuntimeException, IOException {
+		// Reopens the camera if needed
 		createCamera();
+		updateCamera();
 
 		// Starts the preview if needed
 		if (!mPreviewStarted) {
@@ -392,31 +474,45 @@ public abstract class VideoStream extends MediaStream {
 			}
 		}
 
-		mMediaCodec = MediaCodec.createEncoderByType("video/avc");
+		final CodecManager.Translator convertor = new CodecManager.Translator(mEncoderColorFormat,mQuality.resX,mQuality.resY);
+		
+		mMediaCodec = MediaCodec.createByCodecName(mEncoderName);
 		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_COLOR_FORMAT,mEncoderColorFormat);
 		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() {
+		for (int i=0;i<10;i++) mCamera.addCallbackBuffer(new byte[convertor.getBufferSize()]);
+
+		mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
+			private long now = System.nanoTime()/1000, oldnow = 0;
+			private int bufferIndex;
 			@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 !");
+				oldnow = now;
+				now = System.nanoTime()/1000;
+				//Log.d(TAG,"Measured: "+1000000L/(now-oldnow)+" fps.");
+				try {
+					bufferIndex = mMediaCodec.dequeueInputBuffer(0);
+					if (bufferIndex>=0) {
+						byte[] buffer = convertor.translate(data);
+						int min = inputBuffers[bufferIndex].capacity()<buffer.length?inputBuffers[bufferIndex].capacity():buffer.length;
+						inputBuffers[bufferIndex].clear();
+						inputBuffers[bufferIndex].put(buffer, 0, min);
+						//Log.d(TAG, "E: "+inputBuffers[bufferIndex].capacity()+" C1: "+buffer.length+" C2: "+convertor.getBufferSize());
+						mMediaCodec.queueInputBuffer(bufferIndex, 0, min, now, 0);
+					} else {
+						Log.e(TAG,"No buffer available !");
+					}
+				} catch (IllegalStateException e) {
+				} finally {
+					mCamera.addCallbackBuffer(data);
 				}
-
 			}
 		});
 
@@ -434,6 +530,14 @@ public abstract class VideoStream extends MediaStream {
 
 	}
 
+	/**
+	 * Encoding of the audio/video is done by a MediaCodec.
+	 */
+	@SuppressLint({ "InlinedApi", "NewApi" })	
+	protected void encodeWithMediaCodecMethod2() throws RuntimeException, IOException {
+
+	}
+
 	public abstract String generateSessionDescription() throws IllegalStateException, IOException;
 
 	protected synchronized void createCamera() throws RuntimeException, IOException {
@@ -462,13 +566,6 @@ public abstract class VideoStream extends MediaStream {
 
 			Parameters parameters = mCamera.getParameters();
 
-			if (mMode == MODE_MEDIACODEC_API) {
-				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
@@ -480,7 +577,7 @@ public abstract class VideoStream extends MediaStream {
 
 			try {
 				mCamera.setParameters(parameters);
-				mCamera.setDisplayOrientation(mQuality.orientation);
+				//mCamera.setDisplayOrientation(mQuality.orientation);
 				mCamera.setPreviewDisplay(mSurfaceHolder);
 			} catch (RuntimeException e) {
 				destroyCamera();
@@ -506,50 +603,129 @@ public abstract class VideoStream extends MediaStream {
 			mUnlocked = false;
 			mPreviewStarted = false;
 		}	
-	}	
-	
-	/** Verifies if streaming using the MediaCodec API is feasable. */
+	}
+
+	protected synchronized void updateCamera() throws IOException, RuntimeException {
+		if ((mMode&MODE_MEDIACODEC_API)!=0) {
+			if (mPreviewStarted) {
+				mPreviewStarted = false;
+				mCamera.stopPreview();
+			}
+			Parameters parameters = mCamera.getParameters();
+			parameters.setPreviewFormat(mCameraImageFormat);
+			determineClosestSupportedResolution(parameters);
+			parameters.setPreviewSize(mQuality.resX, mQuality.resY);
+			determineClosestSupportedFramerate(parameters);
+			//parameters.setPreviewFpsRange((mQuality.framerate-10)*1000,(mQuality.framerate+10)*1000);
+			parameters.setPreviewFrameRate(mQuality.framerate);
+			try {
+				mCamera.setParameters(parameters);
+				mCamera.setDisplayOrientation(mQuality.orientation);
+				mCamera.setPreviewDisplay(mSurfaceHolder);
+				mCamera.startPreview();
+				mPreviewStarted = false;
+			} catch (RuntimeException e) {
+				destroyCamera();
+				throw e;
+			} catch (IOException e) {
+				destroyCamera();
+				throw e;
+			}
+		}
+	}
+
+	/** 
+	 * Returns an associative array of the supported color formats and the names of the encoders for a given mime type
+	 * The goal here will be to check if either YUV420SemiPlanar or YUV420Planar color formats 
+	 * are supported by one of the encoders of the phone.
+	 * This can take up to sec to return apparently !
+	 **/
 	@SuppressLint("NewApi")
-	private void checkMediaCodecAPI() {
+	static protected SparseArray<ArrayList<String>> findSupportedColorFormats(String mimeType) {
+		SparseArray<ArrayList<String>> list = new SparseArray<ArrayList<String>>();
+
+		if (sSupportedColorFormats.containsKey(mimeType)) {
+			return sSupportedColorFormats.get(mimeType); 
+		}
+
+		Log.v(TAG,"Searching supported color formats for mime type "+mimeType);
+
+		// We loop through the encoders, apparently this can take up to a sec (testes on a GS3)
 		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);           
+			if (!codecInfo.isEncoder()) continue;
+
+			String[] types = codecInfo.getSupportedTypes();
+			for (int i = 0; i < types.length; i++) {
+				if (types[i].equalsIgnoreCase(mimeType)) {
+					MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
+					// And through the color formats supported
+					for (int k = 0; k < capabilities.colorFormats.length; k++) {
+						int format = capabilities.colorFormats[k];
+						if (list.get(format) == null) list.put(format, new ArrayList<String>());
+						list.get(format).add(codecInfo.getName());
+					}
 				}
-				/*for (int i = 0; i < capabilities.profileLevels; i++) {
-					int format = capabilities.colorFormats[i];
-					Log.e(TAG, codecInfo.getName()+" with color format " + format);           
-				}*/
 			}
 		}
+
+		// Logs the supported color formats on the phone
+		StringBuilder e = new StringBuilder();
+		e.append("Supported color formats on this phone: ");
+		for (int i=0;i<list.size();i++) e.append(list.keyAt(i)+(i==list.size()-1?".":", "));
+		Log.v(TAG, e.toString());
+
+		sSupportedColorFormats.put(mimeType, list);
+		return list;
 	}
 
 	/** 
-	 * Checks if the resolution and the framerate selected are supported by the camera.
+	 * Checks if the requested resolution is supported by the camera.
 	 * If not, it modifies it by supported parameters. 
-	 * FIXME: Not reliable, more or less useless :(
 	 **/
-	private void getClosestSupportedQuality(Camera.Parameters parameters) {
-
-		// Resolutions
+	protected void determineClosestSupportedResolution(Camera.Parameters parameters) {
+		int minDist = Integer.MAX_VALUE, newResX = mQuality.resX, newResY = mQuality.resY;
 		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()?", ":"");
+			int dist = Math.abs(mQuality.resX - size.width);
+			if (dist<minDist) {
+				minDist = dist;
+				newResX = size.width;
+				newResY = size.height;
+			}
 		}
 		Log.v(TAG,supportedSizesStr);
+		mQuality.resX = newResX;
+		mQuality.resY = newResY;
+	}
 
+	/** 
+	 * Checks if the framerate is supported by the camera.
+	 * If not, it modifies it by supported parameters. 
+	 **/	
+	protected void determineClosestSupportedFramerate(Camera.Parameters parameters) {
 		// 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);
+		Log.v(TAG,supportedFrameRatesStr);
+
+		// Frame rates
+		String supportedFpsRangesStr = "Supported frame rates: ";
+		int maxFps = 0;
+		List<int[]> supportedFpsRanges = parameters.getSupportedPreviewFpsRange();
+		for (Iterator<int[]> it = supportedFpsRanges.iterator(); it.hasNext();) {
+			int[] interval = it.next();
+			supportedFpsRangesStr += interval[0]+"-"+interval[1]+"fps"+(it.hasNext()?", ":"");
+			if (interval[1]/1000>maxFps) maxFps = interval[1]/1000; 
+		}
+		this.mMaxFps = maxFps;
+		Log.v(TAG,supportedFpsRangesStr);
 
 		int minDist = Integer.MAX_VALUE, newFps = mQuality.framerate;
 		if (!supportedFrameRates.contains(mQuality.framerate)) {
@@ -562,9 +738,8 @@ public abstract class VideoStream extends MediaStream {
 				}
 			}
 			Log.v(TAG,"Frame rate modified: "+mQuality.framerate+"->"+newFps);
-			//mQuality.framerate = newFps;
+			mQuality.framerate = newFps;
 		}
-
 	}
 
 	protected void lockCamera() {

Better support of the MediaCodec API, but still disabled by default. Some major bug fixes in the RtpSocket and the implementation of RTCP.