Android 新API 之 MediaCodec使用笔记 <一>

http://blog.csdn.net/shawnkong/article/details/16337381


Android的视频编解码一直让人有点纠结,SDK竟然不提供硬件编解码的API,如果你想自己做,只能通过JNI借助第三方编解码器,其都是使用的软解码,效率很难保证,这对想做视频通话的是一个不小的打击。

好了,说到google 新提供的SDK中出现的类MediaCodec,这个api限制在API 16后,也就是Android 4.1.2后才可以使用,如果你的系统低于这个版本,是不可以使用这个类的。MediaCodec这家伙能提供给你硬件编解码功能,当然得厂商支持在下层已经,如果厂商没做好,系统会提供给你软件编解码器,反正不用你操心,可以直接就拿来用的。

使用网上有人提供的Demo,可以解码mp4文件,具体情况还没研究,先放出Github链接,直接自己抓出来就可以用,

如果懒得去抓,下面贴出代码,就一个简单的Activity,一切搞定,

“记得修改文件名,SAMPLE变量为你自己的文件名称”,

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  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.     private static final String SAMPLE = Environment.getExternalStorageDirectory() + "/video.mp4";  
  19.     private PlayerThread mPlayer = null;  
  20.   
  21.     @Override  
  22.     protected void onCreate(Bundle savedInstanceState) {  
  23.         super.onCreate(savedInstanceState);  
  24.         SurfaceView sv = new SurfaceView(this);  
  25.         sv.getHolder().addCallback(this);  
  26.         setContentView(sv);  
  27.     }  
  28.   
  29.     protected void onDestroy() {  
  30.         super.onDestroy();  
  31.     }  
  32.   
  33.     @Override  
  34.     public void surfaceCreated(SurfaceHolder holder) {  
  35.     }  
  36.   
  37.     @Override  
  38.     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {  
  39.         if (mPlayer == null) {  
  40.             mPlayer = new PlayerThread(holder.getSurface());  
  41.             mPlayer.start();  
  42.         }  
  43.     }  
  44.   
  45.     @Override  
  46.     public void surfaceDestroyed(SurfaceHolder holder) {  
  47.         if (mPlayer != null) {  
  48.             mPlayer.interrupt();  
  49.         }  
  50.     }  
  51.   
  52.     private class PlayerThread extends Thread {  
  53.         private MediaExtractor extractor;  
  54.         private MediaCodec decoder;  
  55.         private Surface surface;  
  56.   
  57.         public PlayerThread(Surface surface) {  
  58.             this.surface = surface;  
  59.         }  
  60.   
  61.         @Override  
  62.         public void run() {  
  63.             extractor = new MediaExtractor();  
  64.             extractor.setDataSource(SAMPLE);  
  65.   
  66.             for (int i = 0; i < extractor.getTrackCount(); i++) {  
  67.                 MediaFormat format = extractor.getTrackFormat(i);  
  68.                 String mime = format.getString(MediaFormat.KEY_MIME);  
  69.                 if (mime.startsWith("video/")) {  
  70.                     extractor.selectTrack(i);  
  71.                     decoder = MediaCodec.createDecoderByType(mime);  
  72.                     decoder.configure(format, surface, null0);  
  73.                     break;  
  74.                 }  
  75.             }  
  76.   
  77.             if (decoder == null) {  
  78.                 Log.e("DecodeActivity""Can't find video info!");  
  79.                 return;  
  80.             }  
  81.   
  82.             decoder.start();  
  83.   
  84.             ByteBuffer[] inputBuffers = decoder.getInputBuffers();  
  85.             ByteBuffer[] outputBuffers = decoder.getOutputBuffers();  
  86.             BufferInfo info = new BufferInfo();  
  87.             boolean isEOS = false;  
  88.             long startMs = System.currentTimeMillis();  
  89.   
  90.             while (!Thread.interrupted()) {  
  91.                 if (!isEOS) {  
  92.                     int inIndex = decoder.dequeueInputBuffer(10000);  
  93.                     if (inIndex >= 0) {  
  94.                         ByteBuffer buffer = inputBuffers[inIndex];  
  95.                         int sampleSize = extractor.readSampleData(buffer, 0);  
  96.                         if (sampleSize < 0) {  
  97.                             // We shouldn't stop the playback at this point, just pass the EOS  
  98.                             // flag to decoder, we will get it again from the  
  99.                             // dequeueOutputBuffer  
  100.                             Log.d("DecodeActivity""InputBuffer BUFFER_FLAG_END_OF_STREAM");  
  101.                             decoder.queueInputBuffer(inIndex, 000, MediaCodec.BUFFER_FLAG_END_OF_STREAM);  
  102.                             isEOS = true;  
  103.                         } else {  
  104.                             decoder.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);  
  105.                             extractor.advance();  
  106.                         }  
  107.                     }  
  108.                 }  
  109.   
  110.                 int outIndex = decoder.dequeueOutputBuffer(info, 10000);  
  111.                 switch (outIndex) {  
  112.                 case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:  
  113.                     Log.d("DecodeActivity""INFO_OUTPUT_BUFFERS_CHANGED");  
  114.                     outputBuffers = decoder.getOutputBuffers();  
  115.                     break;  
  116.                 case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:  
  117.                     Log.d("DecodeActivity""New format " + decoder.getOutputFormat());  
  118.                     break;  
  119.                 case MediaCodec.INFO_TRY_AGAIN_LATER:  
  120.                     Log.d("DecodeActivity""dequeueOutputBuffer timed out!");  
  121.                     break;  
  122.                 default:  
  123.                     ByteBuffer buffer = outputBuffers[outIndex];  
  124.                     Log.v("DecodeActivity""We can't use this buffer but render it due to the API limit, " + buffer);  
  125.   
  126.                     // We use a very simple clock to keep the video FPS, or the video  
  127.                     // playback will be too fast  
  128.                     while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {  
  129.                         try {  
  130.                             sleep(10);  
  131.                         } catch (InterruptedException e) {  
  132.                             e.printStackTrace();  
  133.                             break;  
  134.                         }  
  135.                     }  
  136.                     decoder.releaseOutputBuffer(outIndex, true);  
  137.                     break;  
  138.                 }  
  139.   
  140.                 // All decoded frames have been rendered, we can stop playing now  
  141.                 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {  
  142.                     Log.d("DecodeActivity""OutputBuffer BUFFER_FLAG_END_OF_STREAM");  
  143.                     break;  
  144.                 }  
  145.             }  
  146.   
  147.             decoder.stop();  
  148.             decoder.release();  
  149.             extractor.release();  
  150.         }  
  151.     }  
  152. }  

最後再奉上我修改過的代碼,增加了選擇文件的功能.

http://download.csdn.net/detail/shawnkong/6555857



最近从事手机iOS/Android直播、视频实时美颜方面的工作。

欢迎加群交流:496010189

4
0
 
 
我的同类文章

参考知识库

img
iOS知识库

img
Android知识库

img
.NET知识库

img
Java SE知识库

img
Java EE知识库

img
Java 知识库

img
Swift知识库

猜你在找
FFmpeg音视频高级开发实战 iOS&Android;
Android 5.x顶级视频课程
Android SQLite 性能优化——显示使用事务
Android底层技术:HAL驱动开发
Android基础视频教程
查看评论
15楼  IT_Transformers 2016-10-19 18:14发表 [回复]
你好,我解码以后从哪个地方可以得到每一帧的数据呢?
14楼  邪恶的鱼蛋 2016-06-27 09:14发表 [回复]
您好,我想请问一下MediaCodec 如何暂停解码,就是我点暂停,他就停留在这里,点击继续他就继续开始解码
Re:  Shawn4com 2016-07-25 18:15发表 [回复]
回复邪恶的鱼蛋:把解码线程挂起,这样解码就不会继续了
13楼  ai454121 2015-11-10 20:26发表 [回复]
LZ,您好,我发现decoder.configure(format, surface, null, 0); 中如果surface设置为null,那解码速度会比现在慢很多,这边测试差不多慢2倍,这个可能是什么原因引起的,如果不配置surface,有什么办法改进吗?
Re:  Shawn4com 2016-07-25 18:21发表 [回复]
回复ai454121:不设置surface会解码变慢么?你是从哪里看出来解码变慢了么?检查一下sleep那个地方,看是不是调用次数增多了
12楼  he_wen_jian 2015-03-05 13:13发表 [回复]
请问如何播放网络视频?
Re:  Shawn4com 2016-07-25 18:16发表 [回复]
回复he_wen_jian:这个只是简单演示解码video,如果需要播放网络视频,你还需要有接收、音频解码播放功能
11楼  amsh10421 2014-09-15 17:46发表 [回复]
extractor = new MediaExtractor(); 
extractor.setDataSource(SAMPLE); 
for (int i = 0; i < extractor.getTrackCount(); i++) 


extractor.getTrackCount(); 返回值是0 请问这个怎么修改?是我的文件没读进去么?
Re:  Shawn4com 2014-09-16 11:06发表 [回复]
回复amsh10421: 加文件读写权限了么?
10楼  sanbo_xyz 2014-06-19 18:51发表 [回复]
上传的不对。。。
Re:  Shawn4com 2014-06-20 14:54发表 [回复]
回复sanbo_xyz:什么不对?
Re:  sanbo_xyz 2014-06-23 11:21发表 [回复]
回复Shawn4com:不能正常使用。
Re:  Shawn4com 2014-06-23 16:09发表 [回复]
回复sanbo_xyz:有什么问题,能说的具体点么?我这里跑的好好的。。
9楼  newplumage 2014-04-03 15:54发表 [回复]
感谢这个例子,我能正常运行了。有两个问题
1.音频需另外解码,怎么处理?
2.视频和音频怎么同步?
请指教!!
Re:  Shawn4com 2014-04-15 10:18发表 [回复]
回复newplumage:sorry,我暂时只用来解码视频,没有测试音频,因为现在我主要不是用来做播放器。同步的话使用时间戳。
8楼  yqymmzby 2014-04-01 13:45发表 [回复]
请问音频使用MediaPlayer同步解吗?还是什么?
7楼  yubestming_job 2014-03-20 10:51发表 [回复]
你好,我想问下,我做的实时视频通话,c++那边给我传递过来的是一个Byte[]的字节数组,我怎么显示出来,在android端,求指教。QQ512573717
Re:  Shawn4com 2014-03-21 11:01发表 [回复]
回复yubestming_job: 实时流好像不支持的,我试过,没弄出来,要不你试试,如果成功了,回头跟出来?
6楼  xhcw1011 2014-02-19 09:17发表 [回复]
你好,请问我想解实时的h264的流该怎么解,我现在按照网上一些例子做出来了,但是效果很差,很模糊。基本看不到东西。
Re:  chenqingfei 2014-10-11 09:05发表 [回复]
回复xhcw1011:同感,我也是在网上搜集了一些信息,写出来的,效果比较差,
如果是非高清的,流畅度还能保证,如果是高清的,网络根本就支撑不了,而且我还做了分包,如果不分包,高清的一帧数据UDP都发不过去。这个问题真的很棘手啊。不知道微信是怎么做的。
Re:  紫枫wrongs 2014-02-28 14:36发表 [回复]
回复xhcw1011:你好,对于实时的视频,在接收端,如何解析解包呢,是不是需要将包组起来,然后解码, 请问下你解码是使用的啥方式,求助!!!
Re:  紫枫wrongs 2014-02-19 14:16发表 [回复]
回复xhcw1011:你好,请问你对h264如何进行解码的呢,我使用的是.264文件解码,可在extractor.setDataSource(fileString); 一直报异常呢
Re:  xhcw1011 2014-02-19 14:33发表 [回复]
回复紫枫wrongs:你先看下是报什么错。
Re:  紫枫wrongs 2014-02-20 10:35发表 [回复]
回复xhcw1011:播放H264错误是我的文件有问题,现在换了文件解决了,对于实时流的接收端,你是使用RTP 接收的吗 可以给个Demo参考下吗
5楼  紫枫wrongs 2014-02-17 16:48发表 [回复]
你好,下载的demo,怎么在extractor.setDataSource(SAMPLE);
一直报错呢,是不是不能支持.h264吗 ?
Re:  Shawn4com 2014-02-17 22:21发表 [回复]
回复紫枫wrongs:SAMPLE 要制定一个文件。。
Re:  紫枫wrongs 2014-02-18 10:03发表 [回复]
回复Shawn4com:sample 这个路径下的文件,是我自己编码的,使用h264的播放器都可以播放的,
Re:  Shawn4com 2014-04-14 14:43发表 [回复]
回复紫枫wrongs:sdk里面的extractor只支持mp4,avi和mkv格式的,h264的裸码解析不了的
4楼  紫枫wrongs 2014-02-13 16:48发表 [回复]
下载csdn的实例,extractor = new MediaExtractor();
extractor.setDataSource(SAMPLE);
执行到这的时候一直报错,求指点啊,
3楼  zuchgi 2014-02-11 00:46发表 [回复]
String SAMPLE = Environment.getExternalStorageDirectory() + "/encoder.h264";
extractor.setDataSource(SAMPLE);

运行到这里的时候显示找不到extractor,请问楼主要如何处理?
Re:  Shawn4com 2014-02-25 12:14发表 [回复]
回复zuchgi:贴错误信息看下。
2楼  chen7yang 2014-01-08 15:03发表 [回复] [引用] [举报]
求助:
我的代码 执行到这句时
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);

就会抛 IllegalStateException ,
Re:  Shawn4com 2014-01-09 16:17发表 [回复] [引用] [举报]
回复chen7yang:据说这个api还不稳定,实际使用可能有问题。。
1楼  temofil2009 2013-12-24 11:17发表 [回复]
这样的话送到surface播出来的视频是没有audio的吧?
Re:  Shawn4com 2013-12-25 10:40发表 [回复]
回复temofil2009:是的,这个只是单解视频,音频需另外解码。。























另外一篇文章的代码,也可以看看:

代码片段(1)[全屏查看所有代码]

1. [代码][Java]代码     

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
private final String TAG = "MediaCodeSample" ;
     /** 用来解码 */
     private MediaCodec mMediaCodec;
     /** 用来读取音频文件 */
     private MediaExtractor extractor;
     private MediaFormat format;
     private String mime = null ;
     private int sampleRate = 0 , channels = 0 , bitrate = 0 ;
     private long presentationTimeUs = 0 , duration = 0 ;
     public void decode(String url)
     {
 
         extractor = new MediaExtractor();
         // 根据路径获取源文件
         try
         {
             extractor.setDataSource(url);
         } catch (Exception e)
         {
             Log.e(TAG, " 设置文件路径错误" + e.getMessage());
         }
         try
         {
             // 音频文件信息
             format = extractor.getTrackFormat( 0 );
             mime = format.getString(MediaFormat.KEY_MIME);
             sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
             // 声道个数:单声道或双声道
             channels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
             // if duration is 0, we are probably playing a live stream
             duration = format.getLong(MediaFormat.KEY_DURATION);
             // System.out.println("歌曲总时间秒:"+duration/1000000);
             bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
         } catch (Exception e)
         {
             Log.e(TAG, "音频文件信息读取出错:" + e.getMessage());
             // 不要退出,下面进行判断
         }
         Log.d(TAG, "Track info: mime:" + mime + " 采样率sampleRate:" + sampleRate + " channels:" + channels + " bitrate:"
                 + bitrate + " duration:" + duration);
         // 检查是否为音频文件
         if (format == null || !mime.startsWith( "audio/" ))
         {
             Log.e(TAG, "不是音频文件 end !" );
             return ;
         }
         // 实例化一个指定类型的解码器,提供数据输出
         // Instantiate an encoder supporting output data of the given mime type
         mMediaCodec = MediaCodec.createDecoderByType(mime);
 
         if (mMediaCodec == null )
         {
             Log.e(TAG, "创建解码器失败!" );
             return ;
         }
         mMediaCodec.configure(format, null , null , 0 );
 
         mMediaCodec.start();
         // 用来存放目标文件的数据
         ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
         // 解码后的数据
         ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
         // 设置声道类型:AudioFormat.CHANNEL_OUT_MONO单声道,AudioFormat.CHANNEL_OUT_STEREO双声道
         int channelConfiguration = channels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO;
         Log.i(TAG, "channelConfiguration=" + channelConfiguration);
         extractor.selectTrack( 0 );
         // ==========开始解码=============
         boolean sawInputEOS = false ;
         boolean sawOutputEOS = false ;
         final long kTimeOutUs = 10 ;
         MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
         while (!sawOutputEOS)
         {
             try
             {
                 if (!sawInputEOS)
                 {
                     int inputBufIndex = mMediaCodec.dequeueInputBuffer(kTimeOutUs);
                     if (inputBufIndex >= 0 )
                     {
                         ByteBuffer dstBuf = inputBuffers[inputBufIndex];
 
                         int sampleSize = extractor.readSampleData(dstBuf, 0 );
                         if (sampleSize < 0 )
                         {
                             Log.d(TAG, "saw input EOS. Stopping playback" );
                             sawInputEOS = true ;
                             sampleSize = 0 ;
                         } else
                         {
                             presentationTimeUs = extractor.getSampleTime();
                         }
 
                         mMediaCodec.queueInputBuffer(inputBufIndex, 0 , sampleSize, presentationTimeUs,
                                 sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0 );
 
                         if (!sawInputEOS)
                         {
                             extractor.advance();
                         }
 
                     } else
                     {
                         Log.e(TAG, "inputBufIndex " + inputBufIndex);
                     }
                 } // !sawInputEOS
 
                 // decode to PCM and push it to the AudioTrack player
                 int res = mMediaCodec.dequeueOutputBuffer(info, kTimeOutUs);
 
                 if (res >= 0 )
                 {
                     int outputBufIndex = res;
                     ByteBuffer buf = outputBuffers[outputBufIndex];
                     final byte [] chunk = new byte [info.size];
                     buf.get(chunk);
                     buf.clear();
                     if (chunk.length > 0 )
                     {
 
                         // chunk解码后的音频流
                         // TODO:处理...
                     }
                     mMediaCodec.releaseOutputBuffer(outputBufIndex, false );
                     if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0 )
                     {
                         Log.d(TAG, "saw output EOS." );
                         sawOutputEOS = true ;
                     }
 
                 } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)
                 {
                     outputBuffers = mMediaCodec.getOutputBuffers();
                     Log.w(TAG, "[AudioDecoder]output buffers have changed." );
                 } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)
                 {
                     MediaFormat oformat = mMediaCodec.getOutputFormat();
                     Log.w(TAG, "[AudioDecoder]output format has changed to " + oformat);
                 } else
                 {
                     Log.w(TAG, "[AudioDecoder] dequeueOutputBuffer returned " + res);
                 }
 
             } catch (RuntimeException e)
             {
                 Log.e(TAG, "[decodeMP3] error:" + e.getMessage());
             }
         }
         // =================================================================================
         if (mMediaCodec != null )
         {
             mMediaCodec.stop();
             mMediaCodec.release();
             mMediaCodec = null ;
         }
         if (extractor != null )
         {
             extractor.release();
             extractor = null ;
         }
         // clear source and the other globals
         duration = 0 ;
         mime = null ;
         sampleRate = 0 ;
         channels = 0 ;
         bitrate = 0 ;
         presentationTimeUs = 0 ;
         duration = 0 ;
     }




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值