Search code examples
androidvideoandroid-video-playerandroid-mediacodec

Android MediaCodec Decoder: Slow Down Video Playback


I've searched but still can not find the answer.

I'm making a simple video player using androids MediaCodec (using decoder and surface) in API 21. But, The Video is playing very fast. How I can play the video at normal speed?

Here is my code:

package com.bd.mediacodec;

import java.io.IOException;
import java.nio.ByteBuffer;

import android.app.Activity;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodec.CodecException;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class DecodeActivity extends Activity implements SurfaceHolder.Callback {
	private static final String SAMPLE = Environment.getExternalStorageDirectory() + "/video.mp4";
	private Surface surface;
	private MediaExtractor extractor;
	private MediaCodec decoder;
	boolean isEOS = false;
	long extractorSampleTime = 0;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		SurfaceView sv = new SurfaceView(this);
		sv.getHolder().addCallback(this);
		setContentView(sv);
	}

	protected void onDestroy() {
		super.onDestroy();
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		
		extractor = new MediaExtractor();
		try {
			extractor.setDataSource(SAMPLE);
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		for (int i = 0; i < extractor.getTrackCount(); i++) {
			MediaFormat mediaFormat = extractor.getTrackFormat(i);
			String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
			if (mime.startsWith("video/")) {
				extractor.selectTrack(i);
				try {
					decoder = MediaCodec.createDecoderByType(mime);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				surface = holder.getSurface();
				decoder.configure(mediaFormat, surface, null, 0);
				break;
			}
		}

		if (decoder == null) {
			Log.e("DecodeActivity", "Can't find / Open Video");
			return;
		}
		// Adding Callback
		decoder.setCallback(mDecoderCallback);
		decoder.start();		    		
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		
	}

	@Override
	protected void onPause() {
		// TODO Auto-generated method stub
		super.onPause();
		
		decoder.stop();
		decoder.release();
		extractor.release();    		
	}

	MediaCodec.Callback mDecoderCallback = new MediaCodec.Callback() {
		
		@Override
		public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onOutputBufferAvailable(MediaCodec codec, int index,
				BufferInfo info) {
			// TODO Auto-generated method stub
			// Release output buffer.
			codec.releaseOutputBuffer(index, true);    			    			
		}
		
		@Override
		public void onInputBufferAvailable(MediaCodec codec, int index) {
			// TODO Auto-generated method stub

			if(!isEOS){
				ByteBuffer buffer = codec.getInputBuffer(index);
				int sampleSize = extractor.readSampleData(buffer, 0);
				if (sampleSize < 0) {
					
					Log.d("DecodeActivity", "InputBuffer BUFFER_FLAG_END_OF_STREAM");
					decoder.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
					isEOS = true;
				} else {
					extractorSampleTime = extractor.getSampleTime();
					decoder.queueInputBuffer(index, 0, sampleSize, extractorSampleTime, 0);
					extractor.advance();
				}
			}    			    		
		}
		
		@Override
		public void onError(MediaCodec codec, CodecException e) {
			// TODO Auto-generated method stub
			
		}
	};
	
}


Solution

  • There are a pair of simple video players in Grafika, one for SurfaceView, one for TextureView. They both use use the SpeedControlCallback class to manage the playback speed. The key is to use the presentation time stamp for each frame to determine how long to wait before displaying the next frame.

    Using a fixed value for the playback speed only makes sense if the video uses a fixed frame rate. See the generated movies in Grafika for an example of variable-frame-rate video. One of the players has a "play at 60fps" button that causes the player to ignore the timestamps so you can observe the difference.

    BTW, putting the playback loop inside surfaceChanged() is not a good idea. Use the callbacks to trigger activity, don't build the entire player inside them.