Codec2是Android中多媒体相关的软件框架,是MediaCodec的中间件,往上对接MediaCodec Native层,往下提供新的API标准供芯片底层的编解码去实现,也就是说适配了Codec2,就可以通过MediaCodec来调用芯片的硬件编解码的能力,来完成一些多媒体相关的功能。这篇文章先从下到上讲解适配Codec2需要实现的接口,然后再从上到下分析MediaCodec的流程来分析这些接口是如何调用的。主要抓住以下两条主线
开始之前需要如下前置知识
下面以Android中的软件Hevc编码器的实现为例,分析如何适配Codec2接口,首先看codec2的基本架构,分为4层,第一层是sfplugin,负责和上层的stage fright对接,下面是HIDL,是各个组件的硬件抽象层,再往下是Core,封装了一个组件需要实现的接口,最后是具体的Component实现,这里以Hevc软编码器为例,再往下就是具体的编解码库了,软编码器调用的是ihevce相关的接口
Android中的codec2目录在frameworks/av/media/codec2
core层组织了components的运行方式,这里先分析core层,其中主要的文件是:core/include/C2Component.h
,其中包含了C2Component
和C2ComponentInterface
两个类
C2Component
中定义了一个组件需要实现的接口,定义如下,这里需要关注的两个重要接口
queue_nb
:可以看作送帧/送流的接口,在编解码之前,将需要处理的原始数据送入,该接口必现设计为非阻塞的onWorkDone_nb
:当一帧数据处理完了之后会回调该接口TODO
SimpleC2Component
提供了一种组件的实现,后面不同的实现只需要继承该实现即可,其中重要的接口设计如下。可以看到SimpleC2Component
继承了C2Component
,并将实现了一个AMessage和AHandle的异步消息机制,下面分别对相应的函数进行分析
该接口将上面的传递的listener设置到状态机中,后续时机合适时再回调相应接口
该函数实现了C2Component.queue_nb
,实际只是将work放到队列中,并且发起一个异步消息然后返回,满足非阻塞的要求
该函数是异步消息处理接口,当发出对应的消息时最终会调用到该函数中,这里processQueue()
函数是处理一帧数据,然后返回当前队列是否还有未处理的数据
该函数完成编解码的实际操作,代码如下,其中process
和onWorkDone_nb
都由子类实现,work先从之前的mWorkQueue
队列中拿出,再调用process
进行处理,process
是一个虚函数,由子类实现,处理完成之后再调用listener->onWorkDone_nb
通知处理完成事件onWorkDone_nb
也是一个虚函数,由子类实现
下面分析process
的实现,以Android Hevc软编码器为例,类继承自SimpleC2Component
,实现如下,主要流程是从work中取出输入buffer,然后进行一帧编码,然后再把输出设置到work中
下面分析finishWork
,可见软编码输出的buffer最终是拷贝到了一个C2Buffer中,最终再放入work->worklets.front()->output.buffers
队列
C2Component
的上层是HIDL层,可以理解为Android的HAL层,这一层的头文件所继承的接口由一种叫做HIDL(Hardware Interface Definition Language)的语言动态生成,输出到out目录下,例如其中的IComponent
头文件位于:
out/soong/.intermediates/hardware/interfaces/media/c2/1.0/android.hardware.media.c2@1.0_genc++_headers/gen/android/hardware/media/c2/1.0/IComponent.h
其定义如下
对应的HIDL文件为hardware/interfaces/media/c2/1.0/IComponent.hal
Component
继承自IComponent.h
,也就会实现其中的接口,通过Component
就可以调用上面讲到的C2Component
,其定义如下,我们重点看下其中的queue
和Listener
。
queue
的实现如下,这里的mComponent
实际上就是C2Component
,这里如何实现的暂且不表,后面再分析,以软编码为例,因此这里最终调用的queue_nb
实际上调用的是SimpleC2Component.queue_nb
再看Listener
的定义,这里实际继承的是C2Component::Listener
,并且对onWorkDone_nb
进行了实现,因为onWorkDone_nb
是回调函数,因此由调用者实现也是符合预期的。这里其实就是调用了另一个回调listener->onWorkDone
以上的Listener只是一个定义,还要看该Listener是在哪里声明的,以及是什么时候注册的。首先第一个问题,Listener声明是在SimpleC2Component::ExecState.mListener
,只要继承了SimpleC2Component
内部就有该成员,第二个问题,注册是在Component::initListener
函数中,定义如下,同样以软编码为例,这里mComponent->setListener_vb
调用实际是SimpleC2Component.setListener_vb
关于HIDL还有很长的一个调用流程,这里暂且分析到这里,后续再从MediaCodec从上往下分析,看如何调用到HIDL的
MediaCodec是Android app层来进行多媒体编解码的模块,分为java层和cpp层,这里只从cpp层切入
首先来看MediaCodec是如何使用的,由于MediaCodec也基于AMessage机制,因此先要创建一个ALooper,然后传递到MediaCodec中,下面以创建Hevc编码器为例,伪代码如下
首先分析queueInputBuffer
,看YUV是如何送到具体的编码器组件的,可以看到实际这里只是发了一个异步消息,将index
送进去,然后调用PostAndAwaitResponse
阻塞等待消息响应
再查看MediaCodec的消息处理函数,由于queueInputBuffer
是阻塞等待的,因此这里要调用PostReplyWithError
之后,queueInputBuffer
才返回,这里往后是调用了onQueueInputBuffer
函数
下面分析onQueueInputBuffer
,可以看到这里实际是通过index获取到MediaCodecBuffer
,并且送到了mBufferChannel->queueInputBuffer
中,注意执行到此处最外层的queueInputBuffer
仍然在等待消息响应,因此到这里为止都是阻塞的
下面看dequeueInputBuffer
函数,该函数是获取一个空闲的输入buffer,这里是否空闲仍然需要底层的组件来通知,因此需要分析这里的向上通知的流程。可以看到该函数仍然是发起一个异步消息,然后阻塞等待响应
再回到onMessageReceived
函数,最终会调用到dequeuePortBuffer
函数,可以看到MediaCodec中有一个mAvailPortBuffers
链表,存储着当前可用的buffer的index,当dequeue的时候只需要从这个链表中拿出第一个index就行了
继续分析availBuffers
是什么时候更新的,查看updateBuffers
函数,发现其中的index是通过异步消息上报的,updateBuffers
可以更新输入队列和输出队列,我们只看输入队列,发现是在收到kWhatFillThisBuffer
消息时更新的
至此可以发现,MediaCodec中的输入和输出buffer是否可用时由异步消息通知的,而异步消息又是底层的组件通过回调MediaCodec的接口发送的,这部分代码在BufferCallback
中,实际上BufferCallback
是继承了CodecBase::BufferCallback
类,并实现了其中的接口,可以看到输入和输出buffer都是在这里回调的
CodecBase
实际上又是codec2中的接口了,属于sfplugin模块,篇幅原因这里暂时不往下分析了
获取输出buffer和获取输入buffer是一个流程,都是发出一个消息然后等待响应,这里输出buffer是底层组件处理好的数据,因此也需要底层组件来通知
获取输出buffer和获取输入buffer相同,都是调用的dequeuePortBuffer
函数,只不过传入参数不同,也就是这里仍然是从mAvailPortBuffers
队列中获取以及处理完成的buffer index
获取输入buffer仍然是调用updateBuffers进行mAvailPortBuffers
的更新,只不过这里是从kWhatDrainThisBuffer
更新的,而发出该消息的地方也是另一个onOutputBufferAvailable
回调
释放输出buffer流程比较简单,也是发一个异步消息然后阻塞等待,在消息处理中将mPortBuffers
队列对应的buffer清除,然后最后调用mBufferChannel->discardBuffer
通知底层组件
总结以上流程,除开HIDL和sfplugin,这里关于使用MediaCodec调用codec2进行编解码的流程进行了大体的分析,首先看整体的运作流程和实现关系如下
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。