概述
OMX Codec是stagefrightplayer中负责解码的模块。由于遵循openmax接口规范,因此结构稍微有点负责,这里就依照awesomeplayer中的调用顺序来介绍。
主要分如下几步:
- 1 mClient->connect
- 2 InitAudioDecoder & InitVideoDecoder
- 3 消息通信机制模型的介绍
- 4 解码过程介绍
先看下类图
这里OMX Codec是以service的方式提供服务的。Awesomeplayer中通过mOmx(IOMX) 作为客户端通过binder方式与OMX 通信完成解码的工作。
源码分析主要结构
与Android其他API类似,MediaCodec主要分为API、JNI、Native、Server四个部分。
本文主要分析其API、JNI、Native三部分的结构,也就是在客户进程中运行的代码。
MediaCodec源码的主要结构如下:
应用代码编写时使用的是java层MediaCodec的接口。这里主要是通过JNI调用Native代码。
进入JNI代码后,主要与JMediaCodec打交道,JMediaCodec负责调用MediaCodec(c++)的方法。
在MediaCodec(c++)和ACodec中包含了解码器(客户端)的主要逻辑。最后ACodec作为MediaCodec与OMX的桥梁,负责调用OMX服务端的功能。
接下来分别看下JMediaCodec,MediaCodec,ACodec和OMXClient的主要结构。
JMediaCodec
JMediaCodec的定义中主要的两个字段:
struct JMediaCodec : public AHandler {
//……
private:
sp<ALooper> mLooper; //mLooper->setName("MediaCodec_looper");
sp<MediaCodec> mCodec;//MediaCodec::CreateByType/CreateByComponentName
//……
};
sp是Android源码中的智能指针的概念,通过引用计数自动回收内存
mCodec是MediaCodec(c++)的实例。mLooper用于MediaCodec(c++,下文如无特别说明MediaCodec均指c++中的MediaCodec类)的事件循环。这两个字段在构造函数中初始化.
MediaCodec
MediaCodec定义中,主要看3个字段:
struct MediaCodec : public AHandler {//AHandler::onMessageReceived
//……
private:
State mState;
sp<ALooper> mLooper;
sp<CodecBase> mCodec;
//……
}
这里的mLooper就是上面JMediaCodec的mLooper,mCodec是ACodec(ACodec继承CodecBase)
看下初始化过程(有简化):
//CreateByType与CreateByComponentName类似
sp<MediaCodec> MediaCodec::CreateByComponentName(
const sp<ALooper> &looper, const char *name, status_t *err) {
sp<MediaCodec> codec = new MediaCodec(looper);
const status_t ret = codec->init(name, false /* nameIsType */, false /* encoder */);
if (err != NULL) {
*err = ret;
}
return ret == OK ? codec : NULL; // NULL deallocates codec.
}
//构造函数,“复制”了mLooper
MediaCodec::MediaCodec(const sp<ALooper> &looper)
: mState(UNINITIALIZED),
mLooper(looper),
mCodec(NULL){
}
//init是主要的初始化过程
status_t MediaCodec::init(const AString &name, bool nameIsType, bool encoder) {
mInitName = name;
mInitNameIsType = nameIsType;
mInitIsEncoder = encoder;
mCodec = new ACodec;//创建ACodec
//registerHandler效果上是增加一个Handler, Looper内部会根据Handler id分发消息
mLooper->registerHandler(mCodec);
mLooper->registerHandler(this);
mCodec->setNotificationMessage(new AMessage(kWhatCodecNotify, id()));
sp<AMessage> msg = new AMessage(kWhatInit, id());//第一个消息:kWhatInit
msg->setString("name", name);
msg->setInt32("nameIsType", nameIsType);
if (nameIsType) {
msg->setInt32("encoder", encoder);
}
sp<AMessage> response;
return PostAndAwaitResponse(msg, &response);
}
MediaCodec的初始化函数牵涉的几个关键的点:
- MediaCodec的主要功能由ACodec实现
- MediaCodec和ACodec内部都是通过事件模型驱动的
ACodec
struct ACodec : public AHierarchicalStateMachine, public CodecBase {
ACodec();
//……
};
ACodec的声明(多继承)可以看出,它内部是状态机,并且提供CodecBase功能。
构造函数:
ACodec::ACodec(){
mUninitializedState = new UninitializedState(this);
mLoadedState = new LoadedState(this);
mLoadedToIdleState = new LoadedToIdleState(this);
mIdleToExecutingState = new IdleToExecutingState(this);
mExecutingState = new ExecutingState(this);
changeState(mUninitializedState);
}
构造函数初始化了内部的几个状态类,并将初始状态设置为UninitializedState。
ACodec的消息处理是委托给AHierarchicalStateMachine::handleMessage处理的。AHierarchicalStateMachine会传递给当前状态类去处理。
OMXClient
OMXClient代码比较简单,用于维持binder连接和访问binder方法。
status_t OMXClient::connect() {
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16("media.player"));
sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
CHECK(service.get() != NULL);
mOMX = service->getOMX();
CHECK(mOMX.get() != NULL);
if (!mOMX->livesLocally(0 /* node */, getpid())) {
ALOGI("Using client-side OMX mux.");
mOMX = new MuxOMX(mOMX);
}
return OK;
}
OMXClient::connect时会从ServiceManager处查询取得media.player服务。
dequeueOutputBuffer
接下来,通过分析dequeueOutputBuffer的调用流程,进一步理解以上结构。
MediaCodec.java会调用native_dequeueOutputBuffer,在android_media_MediaCodec.cpp中映射到android_media_MediaCodec_dequeueOutputBuffer函数.
android_media_MediaCodec_dequeueOutputBuffer调用JMediaCodec.dequeueOutputBuffer.
JMediaCodec.dequeueOutputBuffer`再调用`MediaCodec::dequeueOutputBuffer
这里的JNI逻辑比较简单,不展开分析。
- 主要流程分析
1.1 消息队列
接下来看MediaCodec的实现:
status_t MediaCodec::dequeueOutputBuffer(
size_t *index,
size_t *offset,
size_t *size,
int64_t *presentationTimeUs,
uint32_t *flags,
int64_t timeoutUs) {
sp<AMessage> msg = new AMessage(kWhatDequeueOutputBuffer, id());
msg->setInt64("timeoutUs", timeoutUs);
sp<AMessage> response;
status_t err;
if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
return err;
}
CHECK(response->findSize("index", index));
CHECK(response->findSize("offset", offset));
CHECK(response->findSize("size", size));
CHECK(response->findInt64("timeUs", presentationTimeUs));
CHECK(response->findInt32("flags", (int32_t *)flags));
return OK;
}
dequeueOutputBuffer构造了一个kWhatDequeueOutputBuffer消息,调用PostAndAwaitResponse发送并等待回包。
最后将回包中的字段取出,通过输出参数返回给JMediaCodec去包装。
kWhatDequeueOutputBuffer消息通过ALooper传递给自己处理(MediaCodec继承了AHandler),在onMessageReceived中处理:
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {//有简化
switch (msg->what()) {
case kWhatDequeueOutputBuffer:
{
if (handleDequeueOutputBuffer(replyID, true /* new request */)) {
break;
}
}
}
}
handleDequeueOutputBuffer取得buffer后,会通知等待中的dequeueOutputBuffer函数(所在的线程)
1.2 handleDequeueOutputBuffer
bool MediaCodec::handleDequeueOutputBuffer(uint32_t replyID, bool newRequest) {
sp<AMessage> response = new AMessage;
//1. dequeuePortBuffer,从mAvailPortBuffers取index
ssize_t index = dequeuePortBuffer(kPortIndexOutput);
if (index < 0) {
CHECK_EQ(index, -EAGAIN);
return false;
}
//2. 根据index,从mPortBuffers取ABuffer
const sp<ABuffer> &buffer =
mPortBuffers[kPortIndexOutput].itemAt(index).mData;
//3. 取ABuffer中的值,通过response通知给消息等待者
response->setSize("index", index);
response->setSize("offset", buffer->offset());
response->setSize("size", buffer->size());
int64_t timeUs;
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
response->setInt64("timeUs", timeUs);
int32_t omxFlags;
CHECK(buffer->meta()->findInt32("omxFlags", &omxFlags));
uint32_t flags = 0;
if (omxFlags & OMX_BUFFERFLAG_SYNCFRAME) {
flags |= BUFFER_FLAG_SYNCFRAME;
}
if (omxFlags & OMX_BUFFERFLAG_CODECCONFIG) {
flags |= BUFFER_FLAG_CODECCONFIG;
}
if (omxFlags & OMX_BUFFERFLAG_EOS) {
flags |= BUFFER_FLAG_EOS;
}
response->setInt32("flags", flags);
response->postReply(replyID);
return true;
}
为了简化分析,这里跳过错误处理部分,看常规流程:
- dequeuePortBuffer,从mAvailPortBuffers取index
- 根据index,从mPortBuffers取ABuffer
- 取ABuffer中的值,通过response通知给消息等待者
在以上步骤中,用到两个buffer,分别是mPortBuffers和mAvailPortBuffers。
mPortBuffers中保存了解码器的所有buffer(可用,不可用的都在),mAvailPortBuffers类似一个队列,排队可用buffer的index。
下面我们从dequeOutput的角度看下这两个buffer的填充。
- mPortBuffers
mPortBuffers声明为:Vector mPortBuffers[2];
最外层是一个长度为2的数组,通过kPortIndexOutput和kPortIndexInput分别访问输出和输入buffer。
BufferInfo主要字段(本文用到的):
struct BufferInfo {
uint32_t mBufferID;
sp<ABuffer> mData;
//……
};
struct ABuffer : public RefBase {
//……
uint8_t *data();
sp<AMessage> meta();
private:
sp<AMessage> mMeta;
//……
};
mPortBuffers初始化后的内容是空的,需要到服务端解码器创建后才被填充,填充过程如下:
1. ACodec在LoadedState下收到kWhatStart时,会进入mLoadedToIdleState
2. mLoadedToIdleState进入时(stateEntered)会调用allocateBuffers
3. allocateBuffers调用allocateBuffersOnPort(kPortIndexOutput)
4. allocateBuffersOnPort中根据mOMX->getParameter返回的参数,创建portBuffers,并作为参数构建并发送了CodecBase::kWhatBuffersAllocated消息
5. MediaCodec收到kWhatBuffersAllocated消息,从消息中解析得到portBuffers,并保存到mPortBuffers
- mAvailPortBuffers
mAvailPortBuffers的声明为:List mAvailPortBuffers[2];
它保存的是可用buffer在mPortBuffers中的index。可以理解为生产消费模式中的物料缓冲区。
对于mAvailPortBuffers主要由3个操作:
- 消费:dequeuePortBuffer
- 生产:updateBuffers
- 清理:returnBuffersToCodecOnPort
3.1 dequeuePortBuffer(消费)
分析MediaCodec的handleDequeueOutputBuffer时,提到从dequeuePortBuffer可以取到当前可用的(最先放入的)buffer的index。dequeuePortBuffer实现如下:
ssize_t MediaCodec::dequeuePortBuffer(int32_t portIndex) {
CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
List<size_t> *availBuffers = &mAvailPortBuffers[portIndex];
if (availBuffers->empty()) {
return -EAGAIN;
}
size_t index = *availBuffers->begin();
availBuffers->erase(availBuffers->begin());
//……
return index;
}
如果没有可用buffer,就返回-EAGAIN要求重试。
如果有可用buffer,就返回队头(也就是最先放入的buffer)。
3.2 updateBuffers(生产)
生产过程如下:
1. ACodec收到omx_message::FILL_BUFFER_DONE消息,调用onOMXFillBufferDone
2. 函数onOMXFillBufferDone中构建CodecBase::kWhatDrainThisBuffer消息,发送给MediaCodec
3. kWhatDrainThisBuffer调用updateBuffers
4. updateBuffers遍历mPortBuffers找到bufferId相同的buffer所在index,放入mAvailPortBuffers队尾
3.3 returnBuffersToCodecOnPort(清理)
清理主要发生在以下时机:
- flush
- stop
- release
- state进入UNINITIALIZED
returnBuffersToCodecOnPort主要是clear了mAvailPortBuffers。
总结
梳理整个分析过程,可以总结MediaCodec的源码如下:
- 客户端进程内的代码主要是封装了与服务端omx通信的内容,包括远程buffer到本地的映射(mPortBuffers)
- MediaCodec代码组织分层清晰,分析其他接口一样可以按JMediaCodec -> MediaCodec -> ACodec -> omx的顺序去看
- MediaCodec(c++)和ACodec内部都是基于状态机和消息队列实现的。消息队列可有效解决多线程问题。