音视频开发之IOMX调用端—OMXCodec源码分析
创始人
2024-05-31 14:33:32
0

概述

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 mLooper; //mLooper->setName("MediaCodec_looper");sp 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 mLooper;sp mCodec;//……
}

这里的mLooper就是上面JMediaCodec的mLooper,mCodec是ACodec(ACodec继承CodecBase)

看下初始化过程(有简化):

//CreateByType与CreateByComponentName类似
sp MediaCodec::CreateByComponentName(const sp &looper, const char *name, status_t *err) {sp 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 &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 msg = new AMessage(kWhatInit, id());//第一个消息:kWhatInitmsg->setString("name", name);msg->setInt32("nameIsType", nameIsType);
​if (nameIsType) {msg->setInt32("encoder", encoder);}
​sp 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 sm = defaultServiceManager();sp binder = sm->getService(String16("media.player"));sp service = interface_cast(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.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 msg = new AMessage(kWhatDequeueOutputBuffer, id());msg->setInt64("timeoutUs", timeoutUs);
​sp 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 &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 response = new AMessage;
​//1. dequeuePortBuffer,从mAvailPortBuffers取indexssize_t index = dequeuePortBuffer(kPortIndexOutput);
​if (index < 0) {CHECK_EQ(index, -EAGAIN);return false;}
​//2. 根据index,从mPortBuffers取ABufferconst sp &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;
}

为了简化分析,这里跳过错误处理部分,看常规流程:

  1. dequeuePortBuffer,从mAvailPortBuffers取index
  2. 根据index,从mPortBuffers取ABuffer
  3. 取ABuffer中的值,通过response通知给消息等待者

在以上步骤中,用到两个buffer,分别是mPortBuffers和mAvailPortBuffers。

mPortBuffers中保存了解码器的所有buffer(可用,不可用的都在),mAvailPortBuffers类似一个队列,排队可用buffer的index。

下面我们从dequeOutput的角度看下这两个buffer的填充。

  1. mPortBuffers

mPortBuffers声明为:Vector mPortBuffers[2];

最外层是一个长度为2的数组,通过kPortIndexOutput和kPortIndexInput分别访问输出和输入buffer。

BufferInfo主要字段(本文用到的):

struct BufferInfo {uint32_t mBufferID;sp mData;//……
};
​
struct ABuffer : public RefBase {//……uint8_t *data();sp meta();
private:sp 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
  1. 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 *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内部都是基于状态机和消息队列实现的。消息队列可有效解决多线程问题。

相关内容

热门资讯

安卓系统对比骁龙,性能与生态的... 你有没有想过,为什么你的手机里装的是安卓系统,而不是苹果的iOS呢?又或者,为什么你的安卓手机里搭载...
qt程序安卓系统运行,基于Qt... 你有没有想过,为什么有些手机上的程序运行得那么顺畅,而有些却总是卡得让人抓狂?今天,就让我来给你揭秘...
安卓系统免费应用推荐,助你畅享... 手机里的应用是不是越来越多,有时候都挑花眼了呢?别急,今天我就来给你推荐一些安卓系统上的免费应用,让...
安卓系统视频通话app,打造无... 你有没有发现,现在手机上的视频通话功能越来越强大了?尤其是安卓系统上的那些视频通话app,简直让人爱...
安卓系统发现高危病毒,守护手机... 亲爱的手机用户们,最近可是有个大消息在安卓系统用户群里炸开了锅!没错,就是安卓系统发现了一款高危病毒...
安卓系统疯狂弹广告,揭秘广告软... 你有没有遇到过这种情况?手机里突然弹出一个广告,让你瞬间心情大崩溃?没错,说的就是安卓系统那让人头疼...
ebook 10进入安卓系统 你有没有发现,最近你的安卓手机里多了一个新伙伴——那就是电子书(ebook)10!没错,就是那个我们...
安卓系统如何调听筒,安卓系统调... 手机听筒声音突然变小了?别急,让我来教你如何轻松调教安卓系统的听筒,让它重新恢复活力!一、检查音量设...
安卓系统是怎么手机,解锁智能生... 你有没有想过,我们每天不离手的安卓手机,它背后的安卓系统究竟是怎么一回事呢?今天,就让我带你一探究竟...
安卓系统能代替windows系... 你有没有想过,我们日常使用的安卓系统和Windows系统,哪个才是真正的霸主呢?是不是有时候觉得安卓...
lp108安卓系统,功能特点与... 你有没有听说最近LP108安卓系统火得一塌糊涂?没错,就是那个让无数手机用户都为之疯狂的新系统!今天...
安卓系统挂载u盘,轻松实现数据... 你有没有想过,你的安卓手机或平板电脑突然变成了一个移动的U盘?没错,就是那种可以随意存取文件的神奇设...
i5 安卓系统,引领智能终端新... 你有没有想过,为什么你的手机总是卡得要命,而别人的手机却能流畅如丝?是不是因为你的手机搭载了那个传说...
安卓手机系统没有升级,揭秘潜在... 你有没有发现,你的安卓手机系统好像好久没升级了呢?是不是觉得有点out了?别急,今天就来给你详细聊聊...
安卓14系统定制v,创新功能与... 你知道吗?最近安卓系统又出新花样了!安卓14系统定制版V,这名字听起来就让人兴奋不已。今天,就让我带...
手机安卓系统越高越好,探索最新... 你有没有发现,每次手机更新系统,你的手机就像脱胎换骨了一样?没错,说的就是你,那个安卓手机!今天,咱...
鸿蒙系统怎么用回安卓,轻松实现... 你是不是也和我一样,对鸿蒙系统的新鲜感还没过,却又忍不住想回到熟悉的安卓世界?别急,今天就来手把手教...
苹果7跟安卓系统,性能对决与用... 你有没有想过,为什么苹果7那么受欢迎,而安卓系统却有着庞大的用户群体?今天,我们就来聊聊这个话题,看...
安卓手机刷简化系统,轻松实现流... 你有没有想过,你的安卓手机其实可以变得更加轻快、流畅呢?没错,就是通过刷简化系统!今天,就让我带你一...
社保掌上通安卓系统,轻松掌握在... 你有没有发现,现在的生活越来越离不开手机了?无论是购物、聊天还是办公,手机都能轻松搞定。这不,今天就...