MediaCodec相关知识
在Android中还可以通过MediaCodec播放视频
MediaCodec基本认识
MediaCodec是什么
MediaCodec类可以访问底层媒体编解码框架(StageFright或OMX),即编解码组件。这时Android low-level多媒体支持基础设施的一部分(通常与MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface和AudioTrack一起使用)。它本身不是Codec,它通过调用底层编解码组件获得了Codec的能力
创建MediaCodec的方式
创建MediaCodec(按格式 创建) 1.MediaCodec createDecoderByType (String type):创建解码器 2.MediaCodec createEncoderByType (String type):创建编码器 3.type :数据解析阶段的mimeType,如video/avc
创建MediaCodec(按Codec名字 创建) 1.MediaCodec createByCodecName (String name) 2.OMX.google.h264.decoder:软解码 3.OMX.MTK.VIDEO.DECODER.AVC:硬解码
MediaCodec的工作方式
MediaCodec处理输入数据产生输出数据。当异步处理数据时,使用一组输入和输出Buffer队列。在逻辑上,客户端请求或接受数据后填入预先设定的空输入缓冲区,输入Buffer填满数据后将其传递到MediaCodec并进行编解码处理。之后MediaCodec编解码后的数据被填充到一个输出Buffer中。最后客户端请求或接收输出Buffer,消耗输出Buffer中的内容,用完后释放,给回MediaCodec重新填充输出数据
注意必须要保证输入和输出队列同时非空,即至少持有一个输入Buffer和输出Buffer才能工作
1.MediaCodec读取输入Buffer的数据,进行解码或编码,将处理完的数据填入输出Buffer 2.MediaCodec处理完一个输入Buffer的数据,将此Buffer移入客户端队列,客户端可以继续使用 3.MediaCodec将填充的输出Buffer放入客户端队列,客户端可以取出数据进行播放
MediaCodec状态周期图
在MediaCodec的生命周期内存在3种状态,即Stopped 、Executing 和Released 。Stopped状态实际上还可处于3种子状态,即Uninitialized 、Configured 和Error ,而Executing状态概念上的进展通过3个子状态进行,即Flushed 、Running 、End-of-Stream 。
当使用工厂方法创建一个MediaCodec时,其处于未初始化状态。首先要通过configure(...) 配置它,让其处于配置状态,然后调用start函数把它转变为Executing状态
在调用start函数后,MediaCodec会立刻刷新子状态,它拥有所有的Buffer。一旦第一个输入Buffer被从列中移除,MediaCodec将花费比较长的时间移动到正在运行的子状态上。当队列的输入Buffer带有end of stream 标记,它会转换到end of stream子状态。这种状态下它不再接受进一步输入Buffer,但仍然生成输出Buffer,直到达到end of stream状态输出。在executing状态的任何时候调用flush 函数可以回到flushed状态
调用stop 函数返回的MediaCodec处于未初始化状态,因为它可能被再次配置。当再使用一个编解码器(Codec)时必须调用release函数否则会出现资源未被释放的问题。极少数情况下会遇到一个Error状态,可调用reset函数让编解码器再次可用
特定编解码格式数据
我们可以先理解为一些重要编解码信息的载体。在MediaCodec编解码过程中,当遇到AAC音频格式及MPEG-4、H.264/5视频格式时,需要设置大量的Buffer,或者将特定信息载体作为真实数据的开头。在处理这样的压缩格式,进行编解码操作前,调用start函数后,这些载体是编解码的依据,所以要立刻传给MediaCodec。当调用MediaCodec的queueInputBuffer 函数时,这些数据必须使用flag:BUFFER_FLAG_CODEC_CONFIG ,表明已经配置好,可以进行真正的编解码操作
特定编解码格式数据也可以包含在ByteBuffer 条目的格式中传递给配置钥匙"csd-0/1"等。这些钥匙总是包含从MediaExtractor类中获取视频源的格式。当调用start函数时,特定编解码格式数据格式自动提交给编解码器,如果不包含特定编码的数据格式,你可以按照格式要求选择以正确的顺序提交到指定的缓冲区。对于H.264还可以连接所有信息载体数据并提交作为单个编解码配置缓冲区
Android使用下图所示的特定编解码格式数据缓冲区。这些也需要跟踪格式中设置适当的MediaMuxer Track 配置。每个参数设置和特定编解码格式数据部分标注(*)必须以"\x00\x00\x00\x01" 开始。必须注意在执行start函数之后需要马上刷新编解码器,在任何输出Buffer或输出格式被改变并返回之前,特定编解码格式数据也许会丢失在刷新过程中。在这种刷新中,要确保合适的编解码器做编解码操作不出问题,必须重新提交使用带BUFFER_FLAG_CODEC_CONFIG 标志的Buffer数据
在任意有效的输出Buffer带有codec-config 标志前,如果是编码过程,编码器将创建并返回特定编码的数据,如果是解码过程,解码器将解出对应的YUV 数据
Codec数据处理过程
每个Codec维护的一组输入和输出Buffer都指向API调用的bufferid 。成功调用start函数后,客户端拥有输入和输出Buffer。在同步模式下,调用dequeueInput/OutputBuffer(...) 来获得Codec的输入或输出Buffer。在异步模式下,通过MediaCodec.Callback.onInput/OutputBufferAvailable(...) 回调函数将自动接收回调后的Buffer
Codec在异步模式下通过onOutputBufferAvailable 回调函数将返回一个只读输出Buffer,或在同步模式下响应dequeueOutputBuffer 调用。在输出Buffer被处理后,调用releaseOutputBuffer函数返回Codec的Buffer
1.异步处理使用缓冲区
从5.0开始首选异步模式处理数据,处理方法是在调用configure 函数之前设置一个回调。异步模式改变了状态转换,因为刷新后必须调用start函数,MediaCodec过渡到Running子状态,这时开始接受输入Buffer。同样在初始直接调用start函数后,MediaCodec进入Running状态,通过回调传递可用的输入Buffer
2.同步解码和异步解码
同步方式
错误使用的原因是,开发者会主观上认为,MediaCodec的工作方式是提供一个input数据就输出一个对应的output函数。事实上MediaCodec在读入input数据之后,按照自己的逻辑编解码,可能拆分编解码数据,或者合并输出。MediaCodec的使用者应该认为input和output没有对应关系,使用者的角色是完全被动的,把Buffer的使用权交给MediaCodec
异步方式
用MediaCodec来编解码一个视频到SurfaceView显示
从创建到Start过程
1.MediaCodec从创建到start过程(到JNI部分)
2.补充MediaCodec基本用法
使用MediaCodec的基本模式
(1)创建和配置MediaCodec对象 (2)进行以下循环:如果一个输入缓冲区准备好,读取部分数据,复制到缓冲区,如果一个输出缓冲区准备好,复制到缓冲区 (3)销毁MediaCodec对象
一个MediaCodec对象可以对特定类型的数据(如MP3或H.264)进行编码或解码。因为在原始数据上操作,所以任何文件头(比如ID3 tags)必须被剔除,MediaCodec不与任何更高层次的内容交互,所以无法通过扬声器播放音频或者从网络接收视频流。它只读入缓冲区数据,再输出到缓冲区。MediaCodec可以把大部分的外层数据去掉
必须将MediaCodec的输入处理成特定的格式,H.264视频编码输入的就是一帧数据,H.264解码指的是一个NAL单元 。但你不可能一次只提交单个数据或数据在需要处理的时候才出现,这样看起来输入更像是一个流。实际上编解码器在输出前同时拥有多个Buffer
MediaCodec中的BufferInfo 内部类
MediaCodec和MediaPlayer在很多地方有相似之处,当java层调用MediaCodec。createByCodeName、MediaCodec.recateDecoderByType、MediaCodec.createEncoderByType时,都会执行MediaCodec的构造函数,构造函数中都会调用native_setup
其中对应一段代码,相当于做了一次映射,如native_setup对应android_media_MediaCodec_native_setup 等,接着进入android_media_MediaCodec_native_setup 函数,主要用于构建JNI层中的MediaCodec,也就是JMediaCodec
创建JMediaCodec后,通过setMediaCodec 设置JMediaCodec
上面用到了智能指针中的成员函数incStrong和decStrong来维护引用计数器的值,这两个函数就是提供给智能指针来调用的。要注意在decStrong函数中,如果当前引用计数值为1,那么减一后就会变成0,于是就会删除这个对象。接下来看看JMediaCodec的构造函数
可以看出JMediaCodec最终执行到C++层MediaCodec的两个Create 函数,通过以上几个步骤得到MediaCodec对象后,就开始执行java层调用MediaCodec.configure 函数
获取format中的map,实际上是一个HashMap,然后遍历视频源的格式名并存放到两个数组中,再通过android_media_MediaCodec_native_configure向下传递
通过getMediaCodec获取对应的JMediaCodec,接着传入jsurface 变量,表示JNI层中的Surface数据,这个jsurface变量不一定是真正的Surface(Surface实际上是一块Buffer),也有可能是SurfaceHolder(SurfaceHolder可以通过其中的getSurface获取Surface),最后调用JMediaCodec的configure函数
上面最后的mCodec->configure 最终会调用MediaCodec.cpp中的configure 函数,构建一些编解码器。在MediaCodec.java中调用start函数后,会执行下图代码
在上面的代码获取JMediaCodec后,调用JMediaCodec中的start 函数,JMediaCodec中的start函数代码如下
这个mCodec最后执行C++的MediaCodec的start函数。最后到达ACodec.cpp 的start函数中去执行解码操作
|