Android多媒体框架--18:OpenMAX介绍
"本文转载自:[yanbixing123]的Android MultiMedia框架完全解析 - OpenMAX介绍"
1.概述
OpenMAX IL API通过C语言致力于打造可移植媒体组件的阵列平台。这些组件可以是来源(source)、汇出(sink)、编解码器(codec)、过滤器(filter)、分离器(splitter)、混频器(mixers),或任何其他操作。
OpenMAX IL API允许用户加载,控制,连接和卸载各个组件。Android主要的多媒体引擎StageFright是通过IBinder使用OpenMax,用于编解码(Codec)处理。按照OpenMAX的抽象,Android本身是不关心被构造的Codec到底是硬件解码还是软件解码的。
OpenMAX的官网如下:OpenMAX Overview - The Khronos Group Inc
2.OMX介绍
OpenMAX IL主要内容如下:
-
客户端(Client):OpenMax IL的调用者;
-
组件(Component):OpenMax IL的单元,每一个组件实现一种功能;
-
端口(Port):组件的输入输出接口;
-
隧道化(Tunneled):让两个组件直接连接的方式。
OpenMAX IL的客户端,通过调用四个OpenMAX IL组件,实现了一个功能。四个组件分别是Source组件、Host组件、Accelerator组件和Sink组件。Source组件只有一个输出端口;而Host组件有一个输入端口和一个输出端口;Accelerator组件具有一个输入端口,调用了硬件的编解码器,加速主要体现在这个环节上。Accelerator组件和Sink组件通过私有通讯方式在内部进行连接,没有经过明确的组件端口。
OpenMAX IL在使用的时候,其数据流也有不同的处理方式:既可以经由客户端,也可以不经由客户端。图中Source组件到Host组件的数据流就是经过客户端的;而Host组件到Accelerator组件的数据流就没有经过客户端,使用了隧道化的方式;Accelerator组件和Sink组件甚至可以使用私有的通讯方式。
OpenMAX Core是辅助各个组件运行的部分,它通常需要完成各个组件的初始化等工作,在真正运行过程中,重点是各个OpenMAX IL的组件,OpenMAX Core不是重点,也不是标准。
OpenMAX IL的组件是OpenMax IL实现的核心内容,一个组件以输入、输出端口为接口,端口可以被连接到另一个组件上。外部对组件可以发送命令,还进行设置/获取参数、配置等内容。组件的端口可以包含缓冲区(Buffer)的队列。
组件的处理的核心内容是:通过输入端口消耗Buffer,通过输出端口填充Buffer,由此多组件相联接可以构成流式的处理。OpenMAX IL中一个组件的结构如下图所示:
02.pngOpenMAX的这些组件,可以完全构成一个播放器,但是在Android中使用最多的,还是只用OMX的Accelerator组件来做编解码,因为如果纯靠CPU来做软件的编解码的话,会消耗大量的资源,甚至对于现在的高清4K视频,有的CPU甚至都解不动,所以,一般会将这个任务交给VPU(Video Process Unit)来处理,即所谓的硬解。
3.Android中的OMX
Android中的NuPlayer就是用openmax来做(Codec)编解码,其实在OpenMAX接口设计中,它不光能用来当编解码,它的组件也可以组成一个完整的播放器,包括source、demux、decode、output。但是Android只用它来做code,主要有如下原因:
(1)在整个播放器中,解码器不得不说是最重要的一部分,而且也是最耗资源的一块。如果全靠软解,直接通过cpu来运算,特别是高清视频。别的事你就可以啥都不干了,所以解码器是最需要硬件提供加速的部分。现在的高清解码芯片都是主芯片+DSP结构,解码的工作都是通过DSP来做,不会在过多的占用主芯片。所有将芯片中DSP硬件编解码的能力通过openmax标准接口呈现出来,提供上层播放器来用,这块是openmax最重要的意义。
(2)source主要是和协议打交道,demux分解容器部分,大多数的容器格式的分解是不需要通过硬件来支持。只是ts流这种格式最可能用到硬件的支持。因为ts格式比较特殊,单包的大小太小了,只有188字节。所以也是为什么现在常见的解码芯片都会提供硬件ts demux 的支持。
(3)音视频输出部分video/audio output这块和操作系统关系十分紧密。可以看看著名开源播放器vlc。vlc 在mac、linux、Windows都有,功能上差别也不大。所以说他是跨平台的,他跨平台跨在哪?主要的工作量还是在音视频解码完之后的输出模块。因为各个系统的图像渲染和音频输出实现方法不同,所以vlc需要针对每个平台实现不同的output。这部分内容放在openmax来显然不合适。
Android中使用的主要是OpenMax的编解码功能。虽然OpenMax也可以生成输入、输出、文件解析-构建等组件,但是在各个系统(不仅是Android)中使用的最多的还是编解码组件。媒体的输入、输出环节和系统的关系很大,引入OpenMax标准比较麻烦;文件解析-构建环节一般不需要使用硬件加速。编解码组件也是最能体现硬件加速的环节,因此最常使用。
(不知道这里还对不对,有没有使用到OMXCodec,还是说直接使用ACodec了?下面这个图中的OMXCodec也值得商榷)
(1)android系统中只用openmax来做codec,所以android向上抽象了一层OMXCodec,提供给上层播放器用,也就是上面所讲述的Accelerator组件。播放器中音视频解码器mVideosource、mAudiosource都是OMXCodec的实例。
(2)OMXCodec通过IOMX 依赖binder机制 获得 OMX服务,OMX服务才是openmax在android中实现。
(3)OMX把软编解码和硬件编解码统一看作插件的形式管理起来。
03.png(4)OMX具体实现
-
OMXNodeInstance负责创建并维护不同的实例,这些实例以node_id作为唯一标识。这样播放器中每个ACodec在OMX服务端都对应有了自己的OMXNodeInstance实例。
-
OMXMaster用来维护底层软硬件解码库,根据OMXNodeInstance中想要的解码器来创建解码实体组件。
-
OMXPluginBase模板负责加载组件库,创建组件实例,由OMXMaster管理。Android原生提供的组件都是由SoftOMXPlugin类来管理,这个SoftOMXPlugin类就是继承自OMXPluginBase。(Android源码提供了一些软件解码和编码的组件,它们被抽象为SoftOMXComponent)
-
OMXClient是客户端用来与OMX IL进行通信的。
-
内部结构CallbackDispatcher作用是用来接收回调函数的消息。
-
OMXNodeInstance同CallbackDispatcher一起完成不同实例的消息处理任务。
(5)ACodec同OMXNodeInstance的消息传递
-
ACodec将CodecObserver observer对象通过omx->allocateNode()传递到OMXNodeInstance。
-
OMXNodeInstance将kCallbacks(OnEvent,OnEmptyBufferDone,OnFillBufferDone)传递给OMX Component
-
当OMX Component有消息notify上来时,OMXNodeInstance最先收到,然后调用OMX.cpp。将消息在OMX.cpp里面将OMX Component thread转换到CallbackDispatcher线程中处理。CallbackDispatcher又将消息反调到OMXNodeInstance。最后调用CodecObserver 的onMessage()回到ACodec中。
(6)ACodec与OMX组件的关系
-
ACodec ,CodecObserver和OMXNodeInstance是一一对应的,简单的可以理解它们3个构成了OpenMAX IL的一个Component,每一个node就是一个codec在OMX服务端的标识。当然还有CallbackDispatcher,用于处理codec过来的消息,通过它的post/loop/dispatch来发起接收,从OMX.cpp发送消息,最终通过OMXNodeInstance::onMessage -> CodecObserver::onMessage -> ACodec::onMessage一路往上,当然消息的来源是因为我们有向codec注册OMXNodeInstance::kCallbacks。
-
而在OMXPluginBase创建组件实例的时候,需要传递一个callback给组件,这个callback用于接收组件的消息,它的实现是在OMXNodeInstance.cpp中。而kcallbacks是OMXNodeInstance的静态成员变量,它内部的三个函数指针分别指向了OMXNodeInstance的三个静态方法,也即是这三个方法与组件进行着消息传递。
(7)连接OMX服务器
在前面文档《Android多媒体框架--15:MediaCodec解析》中,MediaCodec创建成功后会执行init()函数,主要是改变内部状态为INITIALIZING,同时调用了mCodec(此时为ACodec)的initiateAllocateComponent()方法:
mCodec->initiateAllocateComponent(format);
----------------------------
void ACodec::initiateAllocateComponent(const sp &msg) {
msg->setWhat(kWhatAllocateComponent);
msg->setTarget(this);
msg->post();
}
按照消息队列机制找到回调:
ACodec::UninitializedState::onMessageReceived(const sp &msg)
case ACodec::kWhatAllocateComponent:
{
onAllocateComponent(msg);
handled = true;
break;
}
MediaCodec::UninitializedState::onAllocateComponent()
bool ACodec::UninitializedState::onAllocateComponent(const sp &msg) {
ALOGV("onAllocateComponent");
CHECK(mCodec->mNode == 0);
OMXClient client;
// 客户端链接OMX服务
if (client.connect() != OK) {
mCodec->signalError(OMX_ErrorUndefined, NO_INIT);
return false;
}
sp omx = client.interface();
sp notify = new AMessage(kWhatOMXDied, mCodec);
Vector matchingCodecs;
AString mime;
AString componentName;
uint32_t quirks = 0;
int32_t encoder = false;
if (msg->findString("componentName", &componentName)) {
sp list = MediaCodecList::getInstance();
if (list != NULL && list->findCodecByName(componentName.c_str()) >= 0) {
matchingCodecs.add(componentName);
}
} else {
CHECK(msg->findString("mime", &mime));
if (!msg->findInt32("encoder", &encoder)) {
encoder = false;
}
// 找出符合要求的解码器
MediaCodecList::findMatchingCodecs(
mime.c_str(),
encoder, // createEncoder
0, // flags
&matchingCodecs);
}
sp observer = new CodecObserver;
IOMX::node_id node = 0;
status_t err = NAME_NOT_FOUND;
for (size_t matchIndex = 0; matchIndex < matchingCodecs.size();
++matchIndex) {
componentName = matchingCodecs[matchIndex];
quirks = MediaCodecList::getQuirksFor(componentName.c_str());
pid_t tid = gettid();
int prevPriority = androidGetThreadPriority(tid);
androidSetThreadPriority(tid, ANDROID_PRIORITY_FOREGROUND);
// 创建真正的解码器实例
err = omx->allocateNode(componentName.c_str(), observer, &mCodec->mNodeBinder, &node);
androidSetThreadPriority(tid, prevPriority);
if (err == OK) {
break;
} else {
ALOGW("Allocating component '%s' failed, try next one.", componentName.c_str());
}
node = 0;
}
if (node == 0) {
if (!mime.empty()) {
ALOGE("Unable to instantiate a %scoder for type '%s' with err %#x.",
encoder ? "en" : "de", mime.c_str(), err);
} else {
ALOGE("Unable to instantiate codec '%s' with err %#x.", componentName.c_str(), err);
}
mCodec->signalError((OMX_ERRORTYPE)err, makeNoSideEffectStatus(err));
return false;
}
mDeathNotifier = new DeathNotifier(notify);
if (mCodec->mNodeBinder == NULL ||
mCodec->mNodeBinder->linkToDeath(mDeathNotifier) != OK) {
// This was a local binder, if it dies so do we, we won't care
// about any notifications in the afterlife.
mDeathNotifier.clear();
}
notify = new AMessage(kWhatOMXMessageList, mCodec);
observer->setNotificationMessage(notify);
mCodec->mComponentName = componentName;
mCodec->mRenderTracker.setComponentName(componentName);
mCodec->mFlags = 0;
if (componentName.endsWith(".secure")) {
mCodec->mFlags |= kFlagIsSecure;
mCodec->mFlags |= kFlagIsGrallocUsageProtected;
mCodec->mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown;
}
mCodec->mQuirks = quirks;
mCodec->mOMX = omx;
mCodec->mNode = node;
{
sp notify = mCodec->mNotify->dup();
notify->setInt32("what", CodecBase::kWhatComponentAllocated);
notify->setString("componentName", mCodec->mComponentName.c_str());
notify->post();
}
// 这里设置的是ACodec的状态
mCodec->changeState(mCodec->mLoadedState);
return true;
}
这里首先是client.connect() 去连接服务器,看下这块的实现:
- OMXClient.cpp
status_t OMXClient::connect() {
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> playerbinder = sm->getService(String16("media.player"));
sp<IMediaPlayerService> mediaservice = interface_cast<IMediaPlayerService>(playerbinder);
if (mediaservice.get() == NULL) {
ALOGE("Cannot obtain IMediaPlayerService");
return NO_INIT;
}
sp<IOMX> mediaServerOMX = mediaservice->getOMX();
if (mediaServerOMX.get() == NULL) {
ALOGE("Cannot obtain mediaserver IOMX");
return NO_INIT;
}
// If we don't want to use the codec process, and the media server OMX
// is local, use it directly instead of going through MuxOMX
if (!sCodecProcessEnabled &&
mediaServerOMX->livesLocally(0 /* node */, getpid())) {
mOMX = mediaServerOMX;
return OK;
}
sp<IBinder> codecbinder = sm->getService(String16("media.codec"));
sp<IMediaCodecService> codecservice = interface_cast<IMediaCodecService>(codecbinder);
if (codecservice.get() == NULL) {
ALOGE("Cannot obtain IMediaCodecService");
return NO_INIT;
}
sp<IOMX> mediaCodecOMX = codecservice->getOMX();
if (mediaCodecOMX.get() == NULL) {
ALOGE("Cannot obtain mediacodec IOMX");
return NO_INIT;
}
mOMX = new MuxOMX(mediaServerOMX, mediaCodecOMX);
return OK;
}
它通过获取mediaplayer和mediacodec这两个service来分别获取OMX服务,而在MediaPlayerService.cpp中,它new出来一个OMX:
sp<IOMX> MediaPlayerService::getOMX() {
Mutex::Autolock autoLock(mLock);
if (mOMX.get() == NULL) {
mOMX = new OMX;
}
return mOMX;
}
同样在MediaCodecService.cpp中,它也是new出来一个OMX:
sp<IOMX> MediaCodecService::getOMX() {
Mutex::Autolock autoLock(mLock);
if (mOMX.get() == NULL) {
mOMX = new OMX;
}
return mOMX;
}
在OMX的构造函数中,创建了一个OMXMaster:
Omx::Omx() :
mMaster(new OMXMaster()),
mParser() {
}
那么OpenMAX就从OMXMaster开始讲起。