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;
- }