硬件编解码(二)MediaCodec

MediaCodec



MediaCodec的介绍



    MediaCodec是Android平台提供的硬件编解码器,它由一组API构成。这样说还是太抽象了,下面用一个很简单的开源例子MediaCodecDemo来说明MediaCodec怎么使用。

    MediaCodecDemo:https://github.com/vecio/MediaCodecDemo



MediaCodec的实例



    MediaCodecDemo的源代码文件只有一个,比较简单,主要的功能是读取一个本地的MP4文件,然后对它进行解码,最后显示在屏幕上

    1、创建显示区域SurfaceView
    2、把显示区域绑定到播放线程上
    3、创建解复用器MediaExtractor,它的功能是分离音频和视频
    4、设置解复用器的数据源(即MP4文件的路径)
    5、遍历数据源的track的信息,track是MP4文件中的数据的集合,分析该track的格式,因为我们的目的是处理视频,因此,我们需要找到视频所在的track
    6、找到视频所在的track后,让解复用器选择它,表示接下来我们要处理这个track;然后根据格式信息创建解码器MediaCodec
    7、对解码器进行配置
    8、启动解码器
    9、得到解码器的输入队列和输出队列;队列里面存放的是一个一个的缓冲区;把缓冲区组成队列的目的是为了复用这里缓冲区,例如,一个缓冲区用完之后,可以把它放进队列尾部,让解码器重新使用;输入队列中的缓冲区用于存放从MP4文件读取的数据;输出队列的缓冲区存放的是已经解码的数据
    10、进入循环
        (1)获取一个空闲的输入缓冲区,然后使用解复用器读取数据,放进缓冲区中,再把这个缓冲区添加到解码器的输入队列的尾部,让解码器进行解码
        (2)获取一个有效的输出缓冲区,这个缓冲区存放的是已经解码完成的数据
        (3)解码器内部会把解码的数据显示在SurfaceView上


完整的代码如下:

package io.vec.demo.mediacodec;

import java.nio.ByteBuffer;

import android.app.Activity;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
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 {
	
	// MP4文件的路径
	private static final String SAMPLE = Environment.getExternalStorageDirectory() + "/video.mp4";
	
	// 播放线程
	private PlayerThread mPlayer = null;

	@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) {
		if (mPlayer == null) {
			mPlayer = new PlayerThread(holder.getSurface());
			mPlayer.start();
		}
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		if (mPlayer != null) {
			mPlayer.interrupt();
		}
	}

	/*
	** 播放线程
	** 从文件中读取媒体数据,然后解码,再显示到屏幕上
	*/
	private class PlayerThread extends Thread {
		private MediaExtractor extractor; // 解复用器,将audio和video分离
		private MediaCodec decoder; // 解码器
		private Surface surface; // 显示区域

		public PlayerThread(Surface surface) {
			this.surface = surface;
		}

		@Override
		public void run() {
			extractor = new MediaExtractor();
			
			// 设置样本源,就是文件名
			extractor.setDataSource(SAMPLE);

			// 读取MP4中track(track是样本的集合,也就是数据),注意和trak box的不同
			for (int i = 0; i < extractor.getTrackCount(); i++) {
				// 媒体格式
				MediaFormat format = extractor.getTrackFormat(i);
				String mime = format.getString(MediaFormat.KEY_MIME);
				
				// 找到视频
				if (mime.startsWith("video/")) {
					extractor.selectTrack(i);
					// 创建解码器
					decoder = MediaCodec.createDecoderByType(mime);
					
					// 设置解码数据显示的地方
					decoder.configure(format, surface, null, 0);
					break;
				}
			}

			if (decoder == null) {
				Log.e("DecodeActivity", "Can't find video info!");
				return;
			}

			// 开始解码
			decoder.start();

			// 输入缓存区数组,就是放置未解码数据的地方
			ByteBuffer[] inputBuffers = decoder.getInputBuffers();
			// 输出缓存区数组,放置已经解码的数据的地方
			ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
			BufferInfo info = new BufferInfo();
			boolean isEOS = false;
			long startMs = System.currentTimeMillis();

			while (!Thread.interrupted()) {
				if (!isEOS) {
					// 取得一个空闲的输入缓存区的索引,因为输入缓存区是以队列的方式被重复数据,出队的是空闲的缓存区
					int inIndex = decoder.dequeueInputBuffer(10000);
					if (inIndex >= 0) {
						// 根据索引得到输入缓存区
						ByteBuffer buffer = inputBuffers[inIndex];
						// 读取数据
						int sampleSize = extractor.readSampleData(buffer, 0);
						if (sampleSize < 0) {
							// We shouldn't stop the playback at this point, just pass the EOS
							// flag to decoder, we will get it again from the
							// dequeueOutputBuffer
							Log.d("DecodeActivity", "InputBuffer BUFFER_FLAG_END_OF_STREAM");
							decoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
							isEOS = true;
						} else {
							// 数据入队
							decoder.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
							
							// 移动到下一帧
							extractor.advance();
						}
					}
				}

				// 读取解码的输出
				int outIndex = decoder.dequeueOutputBuffer(info, 10000);
				switch (outIndex) {
				case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: // 输出缓冲区已经改变
					Log.d("DecodeActivity", "INFO_OUTPUT_BUFFERS_CHANGED");
					outputBuffers = decoder.getOutputBuffers();
					break;
				case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: // 格式已经更改
					Log.d("DecodeActivity", "New format " + decoder.getOutputFormat());
					break;
				case MediaCodec.INFO_TRY_AGAIN_LATER: // 请重试
					Log.d("DecodeActivity", "dequeueOutputBuffer timed out!");
					break;
				default:
				
				// 得到输出缓冲区,就是一帧
					ByteBuffer buffer = outputBuffers[outIndex];
					Log.v("DecodeActivity", "We can't use this buffer but render it due to the API limit, " + buffer);

					// We use a very simple clock to keep the video FPS, or the video
					// playback will be too fast
					
					// 休眠一段时间
					while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
						try {
							sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
							break;
						}
					}
					
					// 输出数据已经使用完毕,那么可以释放它,这样解码器就可以重复使用它了
					decoder.releaseOutputBuffer(outIndex, true);
					break;
				}

				// All decoded frames have been rendered, we can stop playing now
				if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
					Log.d("DecodeActivity", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
					break;
				}
			}

			// 解码器停止
			decoder.stop();
			// 释放解码器
			decoder.release();
			// 释放流复用器
			extractor.release();
		}
	}
}

下面按照步骤进行详细讲解。



创建显示区域SurfaceView


	// 创建SurfaceView
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		SurfaceView sv = new SurfaceView(this);
		sv.getHolder().addCallback(this);
		setContentView(sv);
	}


把显示区域绑定到播放线程上


	// 显示区域和解码线程绑定
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		if (mPlayer == null) {
			mPlayer = new PlayerThread(holder.getSurface());
			mPlayer.start();
		}
	}



创建解复用器


    解复用器的作用是分离音频和视频

			// 创建解复用器
			extractor = new MediaExtractor();
			
			// 设置样本源,就是文件名
			extractor.setDataSource(SAMPLE);


根据视频媒体的格式创建解码器


    遍历解复用器的Track(Track是数据样本的集合),选择视频样本,然后取得媒体格式,根据媒体格式创建解码器

			// 读取MP4中track(track是样本的集合,也就是数据),注意和trak box的不同
			for (int i = 0; i < extractor.getTrackCount(); i++) {
				// 媒体格式
				MediaFormat format = extractor.getTrackFormat(i);
				String mime = format.getString(MediaFormat.KEY_MIME);
				
				// 找到视频
				if (mime.startsWith("video/")) {
					extractor.selectTrack(i);
					// 创建解码器
					decoder = MediaCodec.createDecoderByType(mime);
					
					// 设置解码数据显示的地方
					decoder.configure(format, surface, null, 0);
					break;
				}
			}



启动解码器


			// 开始解码
			decoder.start();


获取空闲缓冲区然后读取MP4数据


    这些数据是压缩的数据

				if (!isEOS) {
					// 取得一个空闲的输入缓存区的索引,因为输入缓存区是以队列的方式被重复数据,出队的是空闲的缓存区
					int inIndex = decoder.dequeueInputBuffer(10000);
					if (inIndex >= 0) {
						// 根据索引得到输入缓存区
						ByteBuffer buffer = inputBuffers[inIndex];
						// 读取数据
						int sampleSize = extractor.readSampleData(buffer, 0);
						if (sampleSize < 0) {
							// We shouldn't stop the playback at this point, just pass the EOS
							// flag to decoder, we will get it again from the
							// dequeueOutputBuffer
							Log.d("DecodeActivity", "InputBuffer BUFFER_FLAG_END_OF_STREAM");
							decoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
							isEOS = true;
						} else {
							// 数据入队
							decoder.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
							
							// 移动到下一帧
							extractor.advance();
						}
					}
				}


取得解码器的输出


    这些数据是解压后的数据,可以用来显示

				// 读取解码的输出
				int outIndex = decoder.dequeueOutputBuffer(info, 10000);
				switch (outIndex) {
				case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: // 输出缓冲区已经改变
					Log.d("DecodeActivity", "INFO_OUTPUT_BUFFERS_CHANGED");
					outputBuffers = decoder.getOutputBuffers();
					break;
				case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: // 格式已经更改
					Log.d("DecodeActivity", "New format " + decoder.getOutputFormat());
					break;
				case MediaCodec.INFO_TRY_AGAIN_LATER: // 请重试
					Log.d("DecodeActivity", "dequeueOutputBuffer timed out!");
					break;
				default:
				
				// 得到输出缓冲区,就是一帧
					ByteBuffer buffer = outputBuffers[outIndex];
					Log.v("DecodeActivity", "We can't use this buffer but render it due to the API limit, " + buffer);

					// We use a very simple clock to keep the video FPS, or the video
					// playback will be too fast
					
					// 休眠一段时间
					while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
						try {
							sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
							break;
						}
					}
					
					// 输出数据已经使用完毕,那么可以释放它,这样解码器就可以重复使用它了
					decoder.releaseOutputBuffer(outIndex, true);
					break;
				}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值