MediaCodec的介绍及实例

来源:互联网 发布:奥林巴斯25mm1.8知乎 编辑:程序博客网 时间:2024/06/07 18:17

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上


完整的代码如下:

[java] view plain copy
  1. package io.vec.demo.mediacodec;  
  2.   
  3. import java.nio.ByteBuffer;  
  4.   
  5. import android.app.Activity;  
  6. import android.media.MediaCodec;  
  7. import android.media.MediaCodec.BufferInfo;  
  8. import android.media.MediaExtractor;  
  9. import android.media.MediaFormat;  
  10. import android.os.Bundle;  
  11. import android.os.Environment;  
  12. import android.util.Log;  
  13. import android.view.Surface;  
  14. import android.view.SurfaceHolder;  
  15. import android.view.SurfaceView;  
  16.   
  17. public class DecodeActivity extends Activity implements SurfaceHolder.Callback {  
  18.       
  19.     // MP4文件的路径  
  20.     private static final String SAMPLE = Environment.getExternalStorageDirectory() + "/video.mp4";  
  21.       
  22.     // 播放线程  
  23.     private PlayerThread mPlayer = null;  
  24.   
  25.     @Override  
  26.     protected void onCreate(Bundle savedInstanceState) {  
  27.         super.onCreate(savedInstanceState);  
  28.         SurfaceView sv = new SurfaceView(this);  
  29.         sv.getHolder().addCallback(this);  
  30.         setContentView(sv);  
  31.     }  
  32.   
  33.     protected void onDestroy() {  
  34.         super.onDestroy();  
  35.     }  
  36.   
  37.     @Override  
  38.     public void surfaceCreated(SurfaceHolder holder) {  
  39.     }  
  40.   
  41.     @Override  
  42.     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {  
  43.         if (mPlayer == null) {  
  44.             mPlayer = new PlayerThread(holder.getSurface());  
  45.             mPlayer.start();  
  46.         }  
  47.     }  
  48.   
  49.     @Override  
  50.     public void surfaceDestroyed(SurfaceHolder holder) {  
  51.         if (mPlayer != null) {  
  52.             mPlayer.interrupt();  
  53.         }  
  54.     }  
  55.   
  56.     /* 
  57.     ** 播放线程 
  58.     ** 从文件中读取媒体数据,然后解码,再显示到屏幕上 
  59.     */  
  60.     private class PlayerThread extends Thread {  
  61.         private MediaExtractor extractor; // 解复用器,将audio和video分离  
  62.         private MediaCodec decoder; // 解码器  
  63.         private Surface surface; // 显示区域  
  64.   
  65.         public PlayerThread(Surface surface) {  
  66.             this.surface = surface;  
  67.         }  
  68.   
  69.         @Override  
  70.         public void run() {  
  71.             extractor = new MediaExtractor();  
  72.               
  73.             // 设置样本源,就是文件名  
  74.             extractor.setDataSource(SAMPLE);  
  75.   
  76.             // 读取MP4中track(track是样本的集合,也就是数据),注意和trak box的不同  
  77.             for (int i = 0; i < extractor.getTrackCount(); i++) {  
  78.                 // 媒体格式  
  79.                 MediaFormat format = extractor.getTrackFormat(i);  
  80.                 String mime = format.getString(MediaFormat.KEY_MIME);  
  81.                   
  82.                 // 找到视频  
  83.                 if (mime.startsWith("video/")) {  
  84.                     extractor.selectTrack(i);  
  85.                     // 创建解码器  
  86.                     decoder = MediaCodec.createDecoderByType(mime);  
  87.                       
  88.                     // 设置解码数据显示的地方  
  89.                     decoder.configure(format, surface, null0);  
  90.                     break;  
  91.                 }  
  92.             }  
  93.   
  94.             if (decoder == null) {  
  95.                 Log.e("DecodeActivity""Can't find video info!");  
  96.                 return;  
  97.             }  
  98.   
  99.             // 开始解码  
  100.             decoder.start();  
  101.   
  102.             // 输入缓存区数组,就是放置未解码数据的地方  
  103.             ByteBuffer[] inputBuffers = decoder.getInputBuffers();  
  104.             // 输出缓存区数组,放置已经解码的数据的地方  
  105.             ByteBuffer[] outputBuffers = decoder.getOutputBuffers();  
  106.             BufferInfo info = new BufferInfo();  
  107.             boolean isEOS = false;  
  108.             long startMs = System.currentTimeMillis();  
  109.   
  110.             while (!Thread.interrupted()) {  
  111.                 if (!isEOS) {  
  112.                     // 取得一个空闲的输入缓存区的索引,因为输入缓存区是以队列的方式被重复数据,出队的是空闲的缓存区  
  113.                     int inIndex = decoder.dequeueInputBuffer(10000);  
  114.                     if (inIndex >= 0) {  
  115.                         // 根据索引得到输入缓存区  
  116.                         ByteBuffer buffer = inputBuffers[inIndex];  
  117.                         // 读取数据  
  118.                         int sampleSize = extractor.readSampleData(buffer, 0);  
  119.                         if (sampleSize < 0) {  
  120.                             // We shouldn't stop the playback at this point, just pass the EOS  
  121.                             // flag to decoder, we will get it again from the  
  122.                             // dequeueOutputBuffer  
  123.                             Log.d("DecodeActivity""InputBuffer BUFFER_FLAG_END_OF_STREAM");  
  124.                             decoder.queueInputBuffer(inIndex, 000, MediaCodec.BUFFER_FLAG_END_OF_STREAM);  
  125.                             isEOS = true;  
  126.                         } else {  
  127.                             // 数据入队  
  128.                             decoder.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);  
  129.                               
  130.                             // 移动到下一帧  
  131.                             extractor.advance();  
  132.                         }  
  133.                     }  
  134.                 }  
  135.   
  136.                 // 读取解码的输出  
  137.                 int outIndex = decoder.dequeueOutputBuffer(info, 10000);  
  138.                 switch (outIndex) {  
  139.                 case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: // 输出缓冲区已经改变  
  140.                     Log.d("DecodeActivity""INFO_OUTPUT_BUFFERS_CHANGED");  
  141.                     outputBuffers = decoder.getOutputBuffers();  
  142.                     break;  
  143.                 case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: // 格式已经更改  
  144.                     Log.d("DecodeActivity""New format " + decoder.getOutputFormat());  
  145.                     break;  
  146.                 case MediaCodec.INFO_TRY_AGAIN_LATER: // 请重试  
  147.                     Log.d("DecodeActivity""dequeueOutputBuffer timed out!");  
  148.                     break;  
  149.                 default:  
  150.                   
  151.                 // 得到输出缓冲区,就是一帧  
  152.                     ByteBuffer buffer = outputBuffers[outIndex];  
  153.                     Log.v("DecodeActivity""We can't use this buffer but render it due to the API limit, " + buffer);  
  154.   
  155.                     // We use a very simple clock to keep the video FPS, or the video  
  156.                     // playback will be too fast  
  157.                       
  158.                     // 休眠一段时间  
  159.                     while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {  
  160.                         try {  
  161.                             sleep(10);  
  162.                         } catch (InterruptedException e) {  
  163.                             e.printStackTrace();  
  164.                             break;  
  165.                         }  
  166.                     }  
  167.                       
  168.                     // 输出数据已经使用完毕,那么可以释放它,这样解码器就可以重复使用它了  
  169.                     decoder.releaseOutputBuffer(outIndex, true);  
  170.                     break;  
  171.                 }  
  172.   
  173.                 // All decoded frames have been rendered, we can stop playing now  
  174.                 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {  
  175.                     Log.d("DecodeActivity""OutputBuffer BUFFER_FLAG_END_OF_STREAM");  
  176.                     break;  
  177.                 }  
  178.             }  
  179.   
  180.             // 解码器停止  
  181.             decoder.stop();  
  182.             // 释放解码器  
  183.             decoder.release();  
  184.             // 释放流复用器  
  185.             extractor.release();  
  186.         }  
  187.     }  
  188. }  

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



创建显示区域SurfaceView


[java] view plain copy
  1. // 创建SurfaceView  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     super.onCreate(savedInstanceState);  
  4.     SurfaceView sv = new SurfaceView(this);  
  5.     sv.getHolder().addCallback(this);  
  6.     setContentView(sv);  
  7. }  


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


[java] view plain copy
  1. // 显示区域和解码线程绑定  
  2. public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {  
  3.     if (mPlayer == null) {  
  4.         mPlayer = new PlayerThread(holder.getSurface());  
  5.         mPlayer.start();  
  6.     }  
  7. }  



创建解复用器


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

[java] view plain copy
  1. // 创建解复用器  
  2. extractor = new MediaExtractor();  
  3.   
  4. // 设置样本源,就是文件名  
  5. extractor.setDataSource(SAMPLE);  


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


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

[java] view plain copy
  1. // 读取MP4中track(track是样本的集合,也就是数据),注意和trak box的不同  
  2. for (int i = 0; i < extractor.getTrackCount(); i++) {  
  3.     // 媒体格式  
  4.     MediaFormat format = extractor.getTrackFormat(i);  
  5.     String mime = format.getString(MediaFormat.KEY_MIME);  
  6.       
  7.     // 找到视频  
  8.     if (mime.startsWith("video/")) {  
  9.         extractor.selectTrack(i);  
  10.         // 创建解码器  
  11.         decoder = MediaCodec.createDecoderByType(mime);  
  12.           
  13.         // 设置解码数据显示的地方  
  14.         decoder.configure(format, surface, null0);  
  15.         break;  
  16.     }  
  17. }  



启动解码器


[java] view plain copy
  1. // 开始解码  
  2. decoder.start();  


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


    这些数据是压缩的数据

[java] view plain copy
  1. if (!isEOS) {  
  2.     // 取得一个空闲的输入缓存区的索引,因为输入缓存区是以队列的方式被重复数据,出队的是空闲的缓存区  
  3.     int inIndex = decoder.dequeueInputBuffer(10000);  
  4.     if (inIndex >= 0) {  
  5.         // 根据索引得到输入缓存区  
  6.         ByteBuffer buffer = inputBuffers[inIndex];  
  7.         // 读取数据  
  8.         int sampleSize = extractor.readSampleData(buffer, 0);  
  9.         if (sampleSize < 0) {  
  10.             // We shouldn't stop the playback at this point, just pass the EOS  
  11.             // flag to decoder, we will get it again from the  
  12.             // dequeueOutputBuffer  
  13.             Log.d("DecodeActivity""InputBuffer BUFFER_FLAG_END_OF_STREAM");  
  14.             decoder.queueInputBuffer(inIndex, 000, MediaCodec.BUFFER_FLAG_END_OF_STREAM);  
  15.             isEOS = true;  
  16.         } else {  
  17.             // 数据入队  
  18.             decoder.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);  
  19.               
  20.             // 移动到下一帧  
  21.             extractor.advance();  
  22.         }  
  23.     }  
  24. }  


取得解码器的输出


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

[java] view plain copy
  1. // 读取解码的输出  
  2. int outIndex = decoder.dequeueOutputBuffer(info, 10000);  
  3. switch (outIndex) {  
  4. case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: // 输出缓冲区已经改变  
  5.     Log.d("DecodeActivity""INFO_OUTPUT_BUFFERS_CHANGED");  
  6.     outputBuffers = decoder.getOutputBuffers();  
  7.     break;  
  8. case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: // 格式已经更改  
  9.     Log.d("DecodeActivity""New format " + decoder.getOutputFormat());  
  10.     break;  
  11. case MediaCodec.INFO_TRY_AGAIN_LATER: // 请重试  
  12.     Log.d("DecodeActivity""dequeueOutputBuffer timed out!");  
  13.     break;  
  14. default:  
  15.   
  16. // 得到输出缓冲区,就是一帧  
  17.     ByteBuffer buffer = outputBuffers[outIndex];  
  18.     Log.v("DecodeActivity""We can't use this buffer but render it due to the API limit, " + buffer);  
  19.   
  20.     // We use a very simple clock to keep the video FPS, or the video  
  21.     // playback will be too fast  
  22.       
  23.     // 休眠一段时间  
  24.     while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {  
  25.         try {  
  26.             sleep(10);  
  27.         } catch (InterruptedException e) {  
  28.             e.printStackTrace();  
  29.             break;  
  30.         }  
  31.     }  
  32.       
  33.     // 输出数据已经使用完毕,那么可以释放它,这样解码器就可以重复使用它了  
  34.     decoder.releaseOutputBuffer(outIndex, true);  
  35.     break;  
  36. }  
原创粉丝点击