MediaCodec基础入门

来源:互联网 发布:网络安全工程师怎么考 编辑:程序博客网 时间:2024/05/23 00:05

  • 一MediaCodec的应用背景角色
  • 二MediaCodec的状态机代码设计架构交互机制
  • 三 MediaCodec的同步模式及JAVA代码
  • 四参考资料

一.MediaCodec的应用背景、角色

如图1所示,APP开发者可以采用两种多媒体JAVA库开发多媒体播放服务,分别为MediaPlayer,MediaCodec。MediaCodec的C++层是对ACodec的封装;ACodec完成的工作是向下调用OpenMAX的IL接口,实现音视频的编解码模块管理工作,作为媒体中间层的中间力量;OpenMAX IL层统一了音视频编解码的调用接口,所有的芯片厂商都实现一套标准的接口,以供不同的上层调用,包括ACodec,OMXCodec。
MediaCodec可以说是面向APP开发者的,研究MediaCodec的C++层实现,可以发现它是如何将复杂的ACodec层进行封装管理,并简化出平易近人简单易用的接口。讲道理,MediaPlayer的接口比MediaCodec更加亲切、简明。但是,MediaCodec与MediaPlayer/VideoView等high-level APIs相比,MediaCodec是low-level APIs,因此它提供了更加完善、灵活、丰富的接口,开发者可以实现更加灵活的功能。从API 16开始,Android提供了Mediacodec类以便开发者更加灵活的处理音视频的编解码。[1]
这里写图片描述
图1 Android Media框架

二.MediaCodec的状态机、代码设计架构、交互机制

MediaCodec存在三种状态,分别是STOPPED,EXECUTING,以及RELEASED。STOPPED与EXECUTING状态分别包含三个子状态,如图2所示。刚创建MediaCodec的时候,MediaCodec处于UNITIALIZED状态;当你在CONFIGURED状态调用START()方法时,将进入FLUSHED状态,而不是类似MediaPlayer的PREPARED或者STARTED状态。FLUSHED状态是START()方法调用之后的状态,也是FLUSH()方法调用之后的状态,这个状态下,MediaCodec拥有INPUT BUFFER与OUT BUFFER资源,并且不进行任何的内部数据传送与解码处理。当JAVA应用层(以下简称应用层)第一次调用dequeueInputBuffer从MediaCodec解码器中取得第一个INPUT BUFFER时,便实际上进入了RUNNING状态;当应用层调用queueInputBuffer并且带上一个码流结束的标志时,MediaCodec就进入了END OF STREAM状态。调用STOP()方法从EXECUTING状态可以切换到UNITIALIZED状态,STOP()方法类似于MediaPlayer的STOP()方法,在调用该方法后,你需要重新配置解码器。如果你想让播放器暂停播放而不是停止退出的话,MediaCodec并没有提供类似于MediaPlayer一样的PAUSE()方法供你暂停播放,你所需要做的是,让应用层不要再调用dequeueOutputBuffer()方法从解码器中取得视频图像或者音频数据。
这里写图片描述
图2 MediaCodec的状态转换[2]

图3为MediaCodec提供给应用层的主要操作方法。在一个简单的APK DEMO中,一般需要包括这些主要操作方法。创建一个MediaCodec解码器实例可以采用两种方法,一种是CreateByType,一种是CreateByComponentName。注意到,最新的MediaCodec修改了这两种方法名称以及增加了其他方法,详情可参考资料X。dequeueInputBuffer是从MediaCodec解码器取得一个空的Buffer,queueInputBuffer是将一个填充好码流数据的Buffer送给MediaCodec解码器,dequeueOutputBuffer是从MediaCodec解码器取得一个存放一帧图像的Buffer,renderOutputBufferAndRelease是将一个存放有一帧图像的Buffer送给GPU显示并且在显示完毕后还给MediaCodec解码器。当应用层将最后一笔码流准备送给MediaCodec解码器后,必须再送一个空的Input Buffer并带上一个码流结束标志BUFFER_FLAG_END_OF_STREAM,这种通常用于解码。signalEndOfInputStream的作用类似于前述的操作,但通常用于录像(Record)的情况,并且录像的码流输入采用Surface的方式,当录像结束时,调用该方法告诉编码器,码流结束了。
这里写图片描述
图3 MediaCodec提供给应用层的主要操作方法

图4为MediaCodec提供给应用层的其他操作方法。requestActivityNotification的意思是,应用层调用该方法以得知MediaCodec解码器拥有多少个已经解码好的Output Buffer,它的一个意义在于,应用层应当尽快地取走解码好的图像数据,不要慢吞吞的,以致于有可能出现可用于解码的Buffer数量不够而使内部解码引擎解码效率降低。releaseOutputBuffer的作用在于,取到的这个Output Buffer不送去显示而直接还给MediaCodec解码器。其他函数见名知义,不赘述。
这里写图片描述
图4 MediaCodec提供给应用层的其他操作方法

MediaCodec的代码设计架构比ACodec的架构简单许多,但都具备同一个特点:利用AMessage机制进行消息的传递。如图5所示,在MediaCodec中,当一个方法被调用的时候,该方法内部将发出一个消息事件,基于ALooper,onMessageReceived函数将作为一个消息处理枢纽,接收来自各个方法的消息事件,并进行相应的事件处理。譬如当上层进行configure方法调用时,该方法将发送一个kWhatConfigure消息,onMessageReceived收到该case,进行具体的实现,但实现关键在于调用ACodec层的方法,即ACodec->initiateConfigureComponent,而ACodec方法内部又继续调用OpenMAX IL层标准接口。onMessageReceived函数处理多个消息事件,假若事件的处理较为简单,则直接内部实现,假若事件的处理较为复杂或代码量多,则调用内部静态函数进行处理,譬如当收到kWhatDequeueOutputBuffer消息事件,即上层APK索要Output Buffer时,主要调用内部静态函数handleDequeueOutputBuffer完成,以实现函数主体逻辑清晰。
这里写图片描述
图5 MediaCodec的主要代码设计架构

MediaCodec实质是对ACodec的封装与管理,MediaCodec需要与ACodec进行通讯交互,包括MediaCodec直接调用ACodec方法,而ACodec采用AMessage向MediaCodec进行状态反馈与通知。譬如当图5中MediaCodec调用ACodec的initateConfigureComponent方法,ACodec完成OMX的组件配置工作后,将一个kWhatComponentAllocated事件放在一个AMessage消息中,即notify对象,并将该消息post出去,post的“目的地”为CodecBase(具体可见ACodec的onAllocateComponent函数),而CodecBase是继承于AHandler的虚类,它会将消息“转发”给MediaCodec。CodecBase基于AHandler构造,而MediaCodec亦基于AHandler构造,该消息将在MediaCodec的onMessageReceived中接收到并进行处理。这样的消息反馈机制,如图6所示。
这里写图片描述
图6 MediaCodec与ACodec的交互机制

具体的AMessage机制原理可以参考文章[3]。基于该文章,可以明白以下这个过程的工作原理:ACodec是如何地set一个参数,并post出去,然后MediaCodec是如何find到这个参数。

三. MediaCodec的同步模式及JAVA代码

这是github上面的一个MediaCodec的java代码范例,采用同步模式。
https://github.com/cedricfung/MediaCodecDemo/blob/master/src/io/vec/demo/mediacodec/DecodeActivity.java

简单地说,MediaCodec在API18+下的主要调用流程[2]:
1. create and configure the MediaCodec object
2. loop until done:
if an input buffer is ready: read a chunk of input, copy it into the buffer
if an output buffer is ready: get the output from the buffer
3. stop and release MediaCodec object

1,创建与配置MediaCodec实例,
调用MediaCodec.createDecoderByType(mime),MediaCodec.configure(format, surface, null ,0)。

2,启动解码器,调用MediaCodec.start。
在这个过程中,ACodec会切换在IDLE状态,切换过程中会分配input buffers与output buffers,在KODI下,首先分配input buffer,继而分配output buffer,分配input buffers采用的是allocateBufferWithBackup方法,分配output buffers采用的是allocateBuffer- FromNativeWindow方法。两种不同的分配方法可以在ACodec的allocateBuffersOnPort函数中可见。input buffer最终调用OMX的allocateBuffer方法,output buffer最终调用OMX的useBuffer方法,前者由OMX层分配BUFFER内存,后者为OMX直接利用从native window申请而来的BUFFER内存。

3,APP获取input buffers与output buffers的操作地址,调用MediaCodec.getInputBuffers,MediaCodec.getOutputBuffers。

4,APP取得一个空的input buffer,并往其中添加sample data,最后送到解码器。调用MediaCodec.dequeueInputBuffer, MediaExtractor.readSampleData, MediaCodec.queueInputBuffer。
APP取的input buffer从OMX层而来,传递的是索引,加上之前获取的input buffers的操作地址便可取得目标buffer地址。送input buffer到解码器也是传递地址索引与buffer大小参数。

5,APP取得一个output buffer,在有可能进行显示渲染之后,将output buffer归还解码器。调用MediaCodec.dequeueOutputBuffer,MediaCodec.releaseOutput-BufferAndRelease。
同样的,APP取得output buffer的地址索引,归还解码器时也是通过地址索引。

6,停止并释放MediaCodec实例,否则会造成内存泄露,调用MediaCodec.stop,MediaCodec.release。

四.参考资料

  1. http://www.cnblogs.com/roger-yu/p/5635494.html
  2. https://developer.android.com/reference/android/media/MediaCodec.html
  3. http://www.jianshu.com/p/408521fbcb64