Android多媒体开发(七)----Android中OpenMax的实现

       preview篇是android对openmax的接口实现的一些规则,还有一些厂商的方案。本节就顺着上上篇的流程,分析一下从AwesomePlayer到OpenMax的调用流程。

AwesomePlayer中openmax的入口

       android中很多模块都是C/S架构的,这里AwesomePlayer中获取openmax的入口也不例外。如果要获取OMX服务,AwesomePlayer是作为Client端的。
       AwesomePlayer 中有个变量,声明位于framework/av/media/libstagefright/include/AwesomePlayer.h中 :

1
OMXClient mClient;

我们看看让我们看看 OMXClient 这个类,位于framework/av/include/media/stagefright/OMXClient.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class OMXClient {
public:
OMXClient();

status_t connect();
void disconnect();

sp<IOMX> interface() {
return mOMX;
}

private:
sp<IOMX> mOMX;

OMXClient(const OMXClient &);
OMXClient &operator=(const OMXClient &);
};

       OMXClient 有个IOMX 的变量 mOMX ,这个就是和OMX服务进行binder通讯的。
       上一篇 的Android中OpenMax的适配层中讲到,这个IOMX就是OpenMax的适配层接口。IOMX表示OpenMax的一个组件,根据Android的Binder IPC机制,BnOMX继承IOMX,实现者需要继承实现BnOMX

       在 AwesomePlayer 的构造函数中会调用如下代码,位于framework/av/media/libstagefright/AwesomePlayer.cpp中:

1
2
3
4
5
6
7
AwesomePlayer::AwesomePlayer()
: ......
{
......
CHECK_EQ(mClient.connect(), (status_t)OK);
......
}

       构造方法中会调用OMXClient的connect方法,位于framework/av/media/libstagefright/OMXClient.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
status_t OMXClient::connect() {
/*获取MediaPlayerService服务*/
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16("media.player"));
sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);

CHECK(service.get() != NULL);
//然后通过MediaPlayerService来创建OMX的实例
mOMX = service->getOMX();
CHECK(mOMX.get() != NULL);
//什么情况会有不是在本地,貌似OMX是在MediaPlayerService当中new出来的,
// OMXClient是在AwesomePlayer当中new出来的,而AwesomePlayer又是在MediaPlayerService当中new出来
// 所以什么情况下会走到如下的这个using client-side OMX mux当中去(MuxOMX位于OMXClient.cpp当中)?
if (!mOMX->livesLocally(0 /* node */, getpid())) {
ALOGI("Using client-side OMX mux.");
mOMX = new MuxOMX(mOMX);
}

return OK;
}

       OMXClient::connect函数是通过binder机制 获得到MediaPlayerService,然后通过MediaPlayerService来创建OMX的实例。这样OMXClient就获得到了OMX的入口,接下来就可以通过binder机制来获得OMX提供的服务:

1
2
3
4
5
6
7
8
9
sp<IOMX> MediaPlayerService::getOMX() {
Mutex::Autolock autoLock(mLock);

if (mOMX.get() == NULL) {
mOMX = new OMX;
}

return mOMX;
}

       (什么情况会有不是在本地,貌似OMX是在MediaPlayerService当中new出来的,OMXClient是在AwesomePlayer当中new出来的,而AwesomePlayer又是在MediaPlayerService当中new出来, 所以什么情况下会走到如下的这个using client-side OMX mux当中去(MuxOMX位于OMXClient.cpp当中)?)

       OMX的定义位于framework/av/media/libstagefright/include/OMX.h中,我们可以看到OMX继承于BnOMX,所以这里我们就获取了OpenMax的入口了

       在创建音视频解码mVideoSource、mAudioSource的时候会把OMXClient中的sp< IOMX > mOMX的实例 传给mVideoSource、mAudioSource来共享使用这个OMX的入口。
       也就是说一个AwesomePlayer对应着 一个IOMX 变量,AwesomePlayer中的音视频解码器共用这个IOMX变量来获得OMX服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//OMXClient.h中的interface函数
sp<IOMX> interface() {
return mOMX;
}
//AwesomePlayer.cpp中initAudioDecoder函数片段,创建音频解码器
mAudioSource = OMXCodec::Create(
mClient.interface(), mAudioTrack->getFormat(),
false, // createEncoder
mAudioTrack);
//AwesomePlayer.cpp中initVideoDecoder函数片段,创建视频解码器
mVideoSource = OMXCodec::Create(
mClient.interface(), mVideoTrack->getFormat(),
false, // createEncoder
mVideoTrack,
NULL, flags, USE_SURFACE_ALLOC ? mNativeWindow : NULL);

OMX中的成员

       每个AwesomePlayer 只有一个OMX服务的入口,但是AwesomePlayer不一定就只需要1种解码器。有可能音视频都有,或者有很多种。这个时候这些解码器都需要OMX的服务,也就是OMX那头需要建立不同的解码器的组件来对应着AwesomePlayer中不同的code。OMX中非常重要的2个成员就是 OMXMaster 和 OMXNodeInstance。OMX通过这俩个成员来创建和维护不同的openmax 解码器组件,为AwesomePlayer中不同解码提供服务。让我们看看他们是怎么实现这些工作的。
调用OMX流程

  • OMX中 OMXNodeInstance 负责创建并维护不同的实例,这些实例是根据上面需求创建的,以node作为唯一标识。这样播放器中每个OMXCodec在OMX服务端都对应有了自己的OMXNodeInstance实例。
  • OMXMaster 维护底层软硬件解码库,根据OMXNodeInstance中想要的解码器来创建解码实体组件。

       接下来我们来看看解码器创建的流程。

准备工作初始化OMXMaster

       OMX构造函数中会进行初始化OMXMaster,位于framework/av/media/libstagefright/omx/OMX.cpp中:

1
2
3
4
5
6
7
//这个mMaster全部变量声明位于OMX.h中
OMXMaster *mMaster;

OMX::OMX()
: mMaster(new OMXMaster),
mNodeCounter(0) {
}

       接着看OMXMaster的构造方法,位于framework/av/media/libstagefright/omx/OMXMaster.cpp中:

1
2
3
4
5
OMXMaster::OMXMaster()
: mVendorLibHandle(NULL) {
addVendorPlugin();//添加芯片商编解码插件(硬解)
addPlugin(new SoftOMXPlugin);//添加软解插件
}

       OMXMaster 负责OMX中编解码器插件管理,软件解码和硬件解码都是使用OMX标准,挂载plugins的方式来进行管理。接下来我们分析这两个流程:

添加硬解插件

       硬解插件查看addVendorPlugin函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

void OMXMaster::addVendorPlugin() {
addPlugin("libstagefrighthw.so");//添加libstagefrighthw.so
}
void OMXMaster::addPlugin(const char *libname) {
mVendorLibHandle = dlopen(libname, RTLD_NOW);//动态打开libstagefrighthw.so

if (mVendorLibHandle == NULL) {
return;
}
//定义一个OMXPluginBase 函数指针
typedef OMXPluginBase *(*CreateOMXPluginFunc)();
//调用插件类中的createOMXPlugin函数,创建一个编解码plugin
CreateOMXPluginFunc createOMXPlugin =
(CreateOMXPluginFunc)dlsym(
mVendorLibHandle, "createOMXPlugin");
//度过调用上述方法失败,则调用如下函数(一般不会失败)
if (!createOMXPlugin)
createOMXPlugin = (CreateOMXPluginFunc)dlsym(
mVendorLibHandle, "_ZN7android15createOMXPluginEv");
//如果创建编解码插件成功,则调用如下重载函数
if (createOMXPlugin) {
addPlugin((*createOMXPlugin)());
}
}
//保存软解和硬解的插件List
List<OMXPluginBase *> mPlugins;
//保存所有编解码组件的map
KeyedVector<String8, OMXPluginBase *> mPluginByComponentName;

void OMXMaster::addPlugin(OMXPluginBase *plugin) {
Mutex::Autolock autoLock(mLock);
//将硬解插件加入到list中
mPlugins.push_back(plugin);

OMX_U32 index = 0;

char name[128];
OMX_ERRORTYPE err;
//循环遍历所有的编解码组件
while ((err = plugin->enumerateComponents(
name, sizeof(name), index++)) == OMX_ErrorNone) {
String8 name8(name);
//如果这个组件已经加载过了,则忽略,continue
if (mPluginByComponentName.indexOfKey(name8) >= 0) {
ALOGE("A component of name '%s' already exists, ignoring this one.",
name8.string());

continue;
}
//然后根据每一个组件的名字和硬解插件对象指针,保存进map
mPluginByComponentName.add(name8, plugin);
}

if (err != OMX_ErrorNoMore) {
ALOGE("OMX plugin failed w/ error 0x%08x after registering %zu "
"components", err, mPluginByComponentName.size());
}

       这里主要分两步:
       (1)动态打开libstagefrighthw.so库,然后获取硬解插件对象指针。
       这个libstagefrighthw.so是和硬件相关的,每个厂商内部实现都不一样,编译目录都在hardware/[厂商]/{一些目录}/libstagefrighthw/ 下面,以Qualcomm(高通)为例,就是hardware/qcom/media/libstagefrighthw/,这个目录下的文件都会被编译进入libstagefrighthw.so。

       (2)先保存上一步获取的硬解码插件。
       循环遍历所有的编解码组件,然后根据每一个组件的名字和硬解插件对象指针,保存进mPluginByComponentName这个类似map的变量。

       循环遍历硬解插件的流程我们就得进入第一步提到的编译硬解插件的libstagefrighthw.so的源文件看看了。依然以高通为例,步入hardware/qcom/media/libstagefrighthw/QComOMXPlugin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
OMX_ERRORTYPE QComOMXPlugin::enumerateComponents(
OMX_STRING name,
size_t size,
OMX_U32 index) {
if (mLibHandle == NULL) {
return OMX_ErrorUndefined;
}
//会调用mComponentNameEnum函数指针,赋值位于构造函数
return (*mComponentNameEnum)(name, size, index);
}

/*我们第一步创建硬解插件的对象,然后将指针传递给上面保存*/
OMXPluginBase *createOMXPlugin() {
return new QComOMXPlugin;
}
//QComOMXPlugin的构造函数
QComOMXPlugin::QComOMXPlugin()
: mLibHandle(dlopen("libOmxCore.so", RTLD_NOW)),
mInit(NULL),
mDeinit(NULL),
mComponentNameEnum(NULL),
mGetHandle(NULL),
mFreeHandle(NULL),
mGetRolesOfComponentHandle(NULL) {
if (mLibHandle != NULL) {
mInit = (InitFunc)dlsym(mLibHandle, "OMX_Init");
mDeinit = (DeinitFunc)dlsym(mLibHandle, "OMX_Deinit");
//这个是我们需要的,在libOmxCore.so里面,调用OMX_ComponentNameEnum函数
mComponentNameEnum =
(ComponentNameEnumFunc)dlsym(mLibHandle, "OMX_ComponentNameEnum");

mGetHandle = (GetHandleFunc)dlsym(mLibHandle, "OMX_GetHandle");
mFreeHandle = (FreeHandleFunc)dlsym(mLibHandle, "OMX_FreeHandle");

mGetRolesOfComponentHandle =
(GetRolesOfComponentFunc)dlsym(
mLibHandle, "OMX_GetRolesOfComponent");

(*mInit)();
}

       然后我们需要的,在libOmxCore.so里面,调用OMX_ComponentNameEnum函数。这时候我们就得查看上一篇文章,Android多媒体开发(六)—-Android中OpenMax的实现(preview) ,看看高通对于这一部分的而实现。
       找到后进入hardware/qcom/media/mm-core/omxcore/src/common/qc_omx_core.cpp中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
OMX_API OMX_ERRORTYPE OMX_APIENTRY
OMX_ComponentNameEnum(OMX_OUT OMX_STRING componentName,
OMX_IN OMX_U32 nameLen,
OMX_IN OMX_U32 index)

{

OMX_ERRORTYPE eRet = OMX_ErrorNone;
DEBUG_PRINT("OMXCORE API - OMX_ComponentNameEnum %x %d %d\n",(unsigned) componentName
,(unsigned)nameLen
,(unsigned)index);
if(index < SIZE_OF_CORE)
{
#ifdef _ANDROID_
//其实就是这个core数组里面所有的组件名,这个core我们上一节也分析过omx_core_cb_type core[]
//是不同型号中注册的编解码组件,在hardware/qcom/media/mm-core/src下面
strlcpy(componentName, core[index].name,nameLen);
#else
strncpy(componentName, core[index].name,nameLen);
#endif
}
else
{
eRet = OMX_ErrorNoMore;
}
return eRet;
}

       因为要找到所有的硬件编解码组件,我们找到了上一盘文章分析到的数组core。其实就是这个core数组里面所有的组件名,这个core我们上一节也分析过omx_core_cb_type core[],是不同型号中注册的编解码组件,在hardware/qcom/media/mm-core/src下面。会看到有许多型号,7627A、7630、8084、8226、8610、8660等等。比如这个8974的,位于hardware/qcom/media/mm-core/src/8974/qc_registry_table_android.c。里面有非常多的硬件编解码组件。

       然后根据每一个组件的名字和硬解插件对象指针,保存进mPluginByComponentName这个类似map的变量。
       硬解码插件添加就到这儿了。

添加软解插件

       软解插件添加就简单了,addPlugin(new SoftOMXPlugin)。所以我们看看SoftOMXPlugin的enumerateComponents函数,位于framework/av/media/libstagefright/omx/SoftOMXPlugin.cpp中:

1
2
3
4
5
6
7
8
9
10
11
12
OMX_ERRORTYPE SoftOMXPlugin::enumerateComponents(
OMX_STRING name,
size_t /* size */,
OMX_U32 index) {
if (index >= kNumComponents) {
return OMX_ErrorNoMore;
}
//软解查找所有的组件就是在kComponents这个数组里
strcpy(name, kComponents[index].mName);

return OMX_ErrorNone;
}

       软解查找所有的组件就是在kComponents这个数组里,我们看看这个数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static const struct {
const char *mName;
const char *mLibNameSuffix;
const char *mRole;

} kComponents[] = {
{ "OMX.google.aac.decoder", "aacdec", "audio_decoder.aac" },
{ "OMX.google.aac.encoder", "aacenc", "audio_encoder.aac" },
{ "OMX.google.amrnb.decoder", "amrdec", "audio_decoder.amrnb" },
{ "OMX.google.amrnb.encoder", "amrnbenc", "audio_encoder.amrnb" },
{ "OMX.google.amrwb.decoder", "amrdec", "audio_decoder.amrwb" },
{ "OMX.google.amrwb.encoder", "amrwbenc", "audio_encoder.amrwb" },
{ "OMX.google.h264.decoder", "h264dec", "video_decoder.avc" },
{ "OMX.google.h264.encoder", "h264enc", "video_encoder.avc" },
{ "OMX.google.hevc.decoder", "hevcdec", "video_decoder.hevc" },
{ "OMX.google.g711.alaw.decoder", "g711dec", "audio_decoder.g711alaw" },
{ "OMX.google.g711.mlaw.decoder", "g711dec", "audio_decoder.g711mlaw" },
{ "OMX.google.h263.decoder", "mpeg4dec", "video_decoder.h263" },
{ "OMX.google.h263.encoder", "mpeg4enc", "video_encoder.h263" },
{ "OMX.google.mpeg4.decoder", "mpeg4dec", "video_decoder.mpeg4" },
{ "OMX.google.mpeg4.encoder", "mpeg4enc", "video_encoder.mpeg4" },
{ "OMX.google.mp3.decoder", "mp3dec", "audio_decoder.mp3" },
{ "OMX.google.vorbis.decoder", "vorbisdec", "audio_decoder.vorbis" },
{ "OMX.google.opus.decoder", "opusdec", "audio_decoder.opus" },
{ "OMX.google.vp8.decoder", "vpxdec", "video_decoder.vp8" },
{ "OMX.google.vp9.decoder", "vpxdec", "video_decoder.vp9" },
{ "OMX.google.vp8.encoder", "vpxenc", "video_encoder.vp8" },
{ "OMX.google.raw.decoder", "rawdec", "audio_decoder.raw" },
{ "OMX.google.flac.encoder", "flacenc", "audio_encoder.flac" },
{ "OMX.google.gsm.decoder", "gsmdec", "audio_decoder.gsm" },
};

       android 默认会提供一系列的软件解码器,我们会发现这些软解组件名都是以OMX.google开头的

       然后根据每一个组件的名字和软解插件对象指针,同样保存进mPluginByComponentName这个类似map的变量。

创建mVideoSource/mAudioSource

       有了上面的OMX,接下来会在AwesomePlayer::initVideoDecoder中创建mVideoSource,AwesomePlayer::initAudioDecoder中创建mAudioSource。

创建mVideoSource

       创建视频解码器,依然位于AwesomePlayer中,省略部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {  
ATRACE_CALL();
......
mVideoSource = OMXCodec::Create(
mClient.interface(), mVideoTrack->getFormat(),
false, // createEncoder
mVideoTrack,
NULL, flags, USE_SURFACE_ALLOC ? mNativeWindow : NULL);
status_t err = mVideoSource->start();
......
return mVideoSource != NULL ? OK : UNKNOWN_ERROR;
}

       保留主要部分,然后查看OMXCodec的Create方法,位于framework/av/media/libstagefright/OMXCode.cpp中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// static
sp<MediaSource> OMXCodec::Create(
const sp<IOMX> &omx, //OMX
const sp<MetaData> &meta, //视频源的元数据
bool createEncoder, //false
const sp<MediaSource> &source, //mVideoTrack视频源
const char *matchComponentName,//NULl
uint32_t flags,//缺省,0
const sp<ANativeWindow> &nativeWindow) {

int32_t requiresSecureBuffers;
//如果是安全的数据源,需要DRM处理
if (source->getFormat()->findInt32(
kKeyRequiresSecureBuffers,
&requiresSecureBuffers)
&& requiresSecureBuffers) {
flags |= kIgnoreCodecSpecificData;
flags |= kUseSecureInputBuffers;
}

const char *mime;
//获取视频源的mime
bool success = meta->findCString(kKeyMIMEType, &mime);
CHECK(success);
//匹配的解码器组件
Vector<CodecNameAndQuirks> matchingCodecs;
//查找和视频源mime匹配的所有解码器组件
findMatchingCodecs(
mime, createEncoder, matchComponentName, flags, &matchingCodecs);
//如果没找到解码器返回NULL
if (matchingCodecs.isEmpty()) {
ALOGV("No matching codecs! (mime: %s, createEncoder: %s, "
"matchComponentName: %s, flags: 0x%x)",
mime, createEncoder ? "true" : "false", matchComponentName, flags);
return NULL;
}
//创建一个OMXCodecObserver消息观察器,用于处理消息回调
sp<OMXCodecObserver> observer = new OMXCodecObserver;
IOMX::node_id node = 0;
//遍历和视频源mime匹配的所有解码器组件
for (size_t i = 0; i < matchingCodecs.size(); ++i) {
//解码组件名
const char *componentNameBase = matchingCodecs[i].mName.string();
//解码组件的Quirk
uint32_t quirks = matchingCodecs[i].mQuirks;
const char *componentName = componentNameBase;

......
//如果是编码器,false
if (createEncoder) {
sp<MediaSource> softwareCodec =
InstantiateSoftwareEncoder(componentName, source, meta);

if (softwareCodec != NULL) {
ALOGV("Successfully allocated software codec '%s'", componentName);

return softwareCodec;
}
}

ALOGV("Attempting to allocate OMX node '%s'", componentName);
//如果是安全的数据源,则需要OMX.SEC开头的解码器
if (!createEncoder
&& (quirks & kOutputBuffersAreUnreadable)
&& (flags & kClientNeedsFramebuffer)) {
if (strncmp(componentName, "OMX.SEC.", 8)) {
// For OMX.SEC.* decoders we can enable a special mode that
// gives the client access to the framebuffer contents.

ALOGW("Component '%s' does not give the client access to "
"the framebuffer contents. Skipping.",
componentName);

continue;
}
}
//这个allocateNode 就是文章最开始讲的,在OMX那头创建一个和mVideoSource相匹配的解码实例。用node值作为唯一标识。
status_t err = omx->allocateNode(componentName, observer, &node);
if (err == OK) {
ALOGV("Successfully allocated OMX node '%s'", componentName);
//创建一个OMXCodec实例
sp<OMXCodec> codec = new OMXCodec(
omx, node, quirks, flags,
createEncoder, mime, componentName,
source, nativeWindow);
//然后将这个OMXCodec实例设置给OMXCodecObserver,用于回调消息
observer->setCodec(codec);
//根据元数据设置相关内容
err = codec->configureCodec(meta);
if (err == OK) {
return codec;
}

ALOGV("Failed to configure codec '%s'", componentName);
}
}

return NULL;
}

       主要流程我们分步查看:

查找所有匹配解码组件

       根据mVideoTrack传进来的视频信息mime,查找相匹配的解码器组件。查看findMatchingCodecs方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// static
void OMXCodec::findMatchingCodecs(
const char *mime, //视频源mime
bool createEncoder, //false
const char *matchComponentName,//NULL
uint32_t flags,//0
Vector<CodecNameAndQuirks> *matchingCodecs) {//外部刚创建的空的Vector
matchingCodecs->clear();
//获取本地配置的编解码器组件列表
const sp<IMediaCodecList> list = MediaCodecList::getInstance();
if (list == NULL) {
return;
}

size_t index = 0;
for (;;) {
//查找匹配的解码器在list中的index
ssize_t matchIndex =
list->findCodecByType(mime, createEncoder, index);

if (matchIndex < 0) {
break;
}

index = matchIndex + 1;
//获取匹配解码器的MediaCodecInfo
const sp<MediaCodecInfo> info = list->getCodecInfo(matchIndex);
CHECK(info != NULL);
const char *componentName = info->getCodecName();

// If a specific codec is requested, skip the non-matching ones.
//如果已经确定了解码器组件名(即外部传入的那个matchComponentName,为NULL),
//并且本地配置组件列表中的匹配解码器名字和外部传入的解码器组件名不行等,则跳过这个列表中的组件,继续往下
if (matchComponentName && strcmp(componentName, matchComponentName)) {
continue;
}

// When requesting software-only codecs, only push software codecs 如果仅仅需要软解
// When requesting hardware-only codecs, only push hardware codecs 如果仅仅需要硬解
// When there is request neither for software-only nor for 如果不是上面两种情况
// hardware-only codecs, push all codecs
if (((flags & kSoftwareCodecsOnly) && IsSoftwareCodec(componentName)) ||
((flags & kHardwareCodecsOnly) && !IsSoftwareCodec(componentName)) ||
(!(flags & (kSoftwareCodecsOnly | kHardwareCodecsOnly)))) {
//将匹配到的的解码器加入到外部传入的Vector当中
ssize_t index = matchingCodecs->add();
CodecNameAndQuirks *entry = &matchingCodecs->editItemAt(index);
entry->mName = String8(componentName);
entry->mQuirks = getComponentQuirks(info);

ALOGV("matching '%s' quirks 0x%08x",
entry->mName.string(), entry->mQuirks);
}
}

if (flags & kPreferSoftwareCodecs) {//如果是优先软解,则需要将软解组件排序到前面
matchingCodecs->sort(CompareSoftwareCodecsFirst);
}
}

       这里主要也是做了两件事:

  • 取出获取本地配置的编解码器组件列表
  • 根据视频的mime类型在本地配置的编解码器组件列表中选取所有匹配的解码器

       我们依然查看:

       (1) 获取本地配置编解码器列表

       const sp< IMediaCodecList > list = MediaCodecList::getInstance();我们进入MediaCodecList的getInstance()函数看看,位于framework/av/media/libstagefright/MediaCodecList.cpp中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// static
sp<IMediaCodecList> MediaCodecList::getInstance() {
Mutex::Autolock _l(sRemoteInitMutex);
if (sRemoteList == NULL) {
//获取MediaPlayerService服务
sp<IBinder> binder =
defaultServiceManager()->getService(String16("media.player"));
sp<IMediaPlayerService> service =
interface_cast<IMediaPlayerService>(binder);
if (service.get() != NULL) {//MediaPlayerService的getCodecList函数
sRemoteList = service->getCodecList();
}

if (sRemoteList == NULL) {
// if failed to get remote list, create local list
sRemoteList = getLocalInstance();
}
}
return sRemoteList;
}

       然后进入MediaPlayerService查看getCodecList函数:

1
2
3
sp<IMediaCodecList> MediaPlayerService::getCodecList() const {
return MediaCodecList::getLocalInstance();//这特么又回去了
}

       这特么又回去了,继续查看MediaCodecList::getLocalInstance():

1
2
3
4
MediaCodecList::MediaCodecList()
: mInitCheck(NO_INIT) {
parseTopLevelXMLFile("/etc/media_codecs.xml");
}

       后续其实就是解析/etc/media_codecs.xml,然后将里面的编解码器相关信息保存起来,每一个编解码器就是一个MediaCodecInfo。
       解析xml的函数都在MediaCodecList.cpp中,只不过用C++的函数处理的,看了下先关函数,和java层的SAX解析xml很像。有兴趣的童鞋可以看看,这里限于篇幅就不贴代码了。

       实际上系统中存在的解码器可以很多,但能够被应用使用的解码器是根据配置来的,即/etc/media_codecc.xml。这个文件一般由硬件或者系统的生产厂家在build整个系统的时候提供,一般是保存在代码的device/[company]/[codename]目录下的,例如device/samsung/manta/media_codecs.xml。这个文件配置了系统中有哪些可用的codec以及,这些codec对应的媒体文件类型。在这个文件里面,系统里面提供的软硬codec都需要被列出来。

       我借了同事的nexus 5,刷的是android的原生系统,也是高通的芯片。然后取出media_codecc.xml文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<MediaCodecs>
<Include href="media_codecs_google_audio.xml" /> <!-- audio codec相关,在另一个xml -->
<Include href="media_codecs_google_telephony.xml" /><!-- telephony codec相关 -->
<Settings>
<Setting name="max-video-encoder-input-buffers" value="9" />
</Settings>
<Encoders><!-- 一些视频的encoder -->
<MediaCodec name="OMX.qcom.video.encoder.mpeg4" type="video/mp4v-es" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports"/>
<Quirk name="requires-loaded-to-idle-after-allocation"/>
<Limit name="size" min="96x64" max="1920x1088" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="blocks-per-second" min="1" max="489600" />
<Limit name="bitrate" range="1-60000000" />
<Limit name="concurrent-instances" max="13" />
</MediaCodec>
<MediaCodec name="OMX.qcom.video.encoder.h263" type="video/3gpp" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports"/>
<Quirk name="requires-loaded-to-idle-after-allocation"/>
<Limit name="size" min="96x64" max="720x576" />
<Limit name="alignment" value="2x2" />
<Limit name="concurrent-instances" max="13" />
</MediaCodec>
<MediaCodec name="OMX.qcom.video.encoder.avc" type="video/avc" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports"/>
<Quirk name="requires-loaded-to-idle-after-allocation"/>
<Limit name="size" min="96x64" max="3840x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="blocks-per-second" min="1" max="972000" />
<Limit name="bitrate" range="1-100000000" />
<Limit name="concurrent-instances" max="13" />
</MediaCodec>
<MediaCodec name="OMX.qcom.video.encoder.vp8" type="video/x-vnd.on2.vp8" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports"/>
<Quirk name="requires-loaded-to-idle-after-allocation"/>
<Limit name="size" min="96x64" max="3840x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="blocks-per-second" min="1" max="777600" />
<Limit name="bitrate" range="1-20000000" />
<Limit name="concurrent-instances" max="13" />
</MediaCodec>
</Encoders>
<Decoders><!-- 一些视频的decoder -->
<MediaCodec name="OMX.qcom.video.decoder.avc" type="video/avc" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports"/>
<Quirk name="defers-output-buffer-allocation"/>
<Limit name="size" min="64x64" max="3840x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="blocks-per-second" min="1" max="972000" />
<Limit name="bitrate" range="1-100000000" />
<Feature name="adaptive-playback" />
<Limit name="concurrent-instances" max="13" />
</MediaCodec>
<MediaCodec name="OMX.qcom.video.decoder.avc.secure" type="video/avc" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports"/>
<Quirk name="defers-output-buffer-allocation"/>
<Limit name="size" min="64x64" max="3840x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="blocks-per-second" min="1" max="972000" />
<Limit name="bitrate" range="1-100000000" />
<Feature name="adaptive-playback" />
<Feature name="secure-playback" required="true" />
<Limit name="concurrent-instances" max="6" />
</MediaCodec>
<MediaCodec name="OMX.qcom.video.decoder.mpeg4" type="video/mp4v-es" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports"/>
<Quirk name="defers-output-buffer-allocation"/>
<Limit name="size" min="64x64" max="1920x1088" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="blocks-per-second" min="1" max="489600" />
<Limit name="bitrate" range="1-60000000" />
<Feature name="adaptive-playback" />
<Limit name="concurrent-instances" max="13" />
</MediaCodec>
<MediaCodec name="OMX.qcom.video.decoder.h263" type="video/3gpp" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports"/>
<Quirk name="defers-output-buffer-allocation"/>
<Limit name="size" min="64x64" max="720x576" />
<Limit name="alignment" value="2x2" />
<Feature name="adaptive-playback" />
<Limit name="concurrent-instances" max="13" />
</MediaCodec>
<MediaCodec name="OMX.qcom.video.decoder.vp8" type="video/x-vnd.on2.vp8" >
<Quirk name="requires-allocate-on-input-ports" />
<Quirk name="requires-allocate-on-output-ports" />
<Quirk name="defers-output-buffer-allocation" />
<Limit name="size" min="64x64" max="3840x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="blocks-per-second" min="1" max="777600" />
<Limit name="bitrate" range="1-20000000" />
<Feature name="adaptive-playback" />
<Limit name="concurrent-instances" max="13" />
</MediaCodec>
</Decoders>
<Include href="media_codecs_google_video.xml" />
</MediaCodecs>

       也就是说,如果系统里面实际上包含了某个codec,但是并没有被配置在这个文件里,那么应用程序也无法使用到!
       上面获取配置xml中的解码器,因为解析xml和SAX解析很像,是逐行扫描,所以保存的list也是按照xml中的顺序保存的

       (2)取出所有匹配的解码器列表
       获取本地配置的编解码器组件列表后,查找匹配的解码器在list中的index。我们查看ssize_t matchIndex = list->findCodecByType(mime, createEncoder, index) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// legacy method for non-advanced codecs
ssize_t MediaCodecList::findCodecByType(
const char *type, //视频源mime
bool encoder, //false
size_t startIndex //从编解码器开始遍历的位置
) const {
static const char *advancedFeatures[] = {//高级播放模式
"feature-secure-playback", //DRM加密
"feature-tunneled-playback", //“隧道”播放模式,为了低功耗
};
//mCodecInfos就是我们刚刚解析media_codecs.xml中配置的编解码器信息
size_t numCodecs = mCodecInfos.size();
//选混遍历所有配置的解码器
for (; startIndex < numCodecs; ++startIndex) {

const MediaCodecInfo &info = *mCodecInfos.itemAt(startIndex).get();

if (info.isEncoder() != encoder) {
continue;
}
//如果这个视频源mime类型,这个遍历的解码器有能力播放,则ok
sp<MediaCodecInfo::Capabilities> capabilities = info.getCapabilitiesFor(type);
if (capabilities == NULL) {
continue;
}
const sp<AMessage> &details = capabilities->getDetails();

int32_t required;
bool isAdvanced = false;
//如果是高级播放模式,则不能播
for (size_t ix = 0; ix < ARRAY_SIZE(advancedFeatures); ix++) {
if (details->findInt32(advancedFeatures[ix], &required) &&
required != 0) {
isAdvanced = true;
break;
}
}

if (!isAdvanced) {//然后返回有能力播放这个类型视频的解码器index
return startIndex;
}
}

return -ENOENT;
}

       上面内容就是从配置的本地编解码列表中选取支持视频源mime类型的解码器,然后返回它位于列表中的index。
       然后回到findMatchingCodecs函数中,就是根据所有匹配的index去一一获取编解码器的MediaCodecInfo,然后再做先关设置,最后加入到matchingCodecs这个Vector当中。

选取并初始化第一个解码器

       接着会创建OMXCodecObserver 实例,OMXCodecObserver功能后续会详细介绍。创建一个node 并初始化为0。

1
2
sp<OMXCodecObserver> observer = new OMXCodecObserver;  
IOMX::node_id node = 0;

       然后通过omx入口 依靠binder 机制调用OMX服务中的allocateNode(),这一步把匹配得到的解码器组件名、OMXCodecObserver实例和初始化为0的node一并传入。

1
status_t err = omx->allocateNode(componentName, observer, &node);

       这个allocateNode 就是文章最开始讲的,在OMX那头创建一个和mVideoSource相匹配的解码实例。用node值作为唯一标识。
       让我们来看看真正的omx中allocateNode做了啥,位于framework/av/media/libstagefright/omx/OMX.cpp中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
status_t OMX::allocateNode(
const char *name, const sp<IOMXObserver> &observer, node_id *node) {
Mutex::Autolock autoLock(mLock);

*node = 0;
//创建一个OMXNodeInstance实例
OMXNodeInstance *instance = new OMXNodeInstance(this, observer);

OMX_COMPONENTTYPE *handle;
//通过mMaster->makeComponentInstance创建真正解码器的组件,并通过handle与OMXNodeInstance关联。
OMX_ERRORTYPE err = mMaster->makeComponentInstance(
name, &OMXNodeInstance::kCallbacks,
instance, &handle);

if (err != OMX_ErrorNone) {
ALOGE("FAILED to allocate omx component '%s'", name);

instance->onGetHandleFailed();

return UNKNOWN_ERROR;
}

*node = makeNodeID(instance);
mDispatchers.add(*node, new CallbackDispatcher(instance));

instance->setHandle(*node, handle);

mLiveNodes.add(observer->asBinder(), instance);
observer->asBinder()->linkToDeath(this);

return OK;
}

       创建一个OMXNodeInstance实例。
       通过mMaster->makeComponentInstance创建真正解码器的组件,并通过handle与OMXNodeInstance关联。
       所以说mMaster->makeComponentInstance这里是建立解码器组件的核心。会把mVideoSource需要的解码器name一直传递下去。
       我们查看OMXMaster的makeComponentInstance函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//mPluginByInstance位于OMXMaster.h中
KeyedVector<OMX_COMPONENTTYPE *, OMXPluginBase *> mPluginByInstance;

OMX_ERRORTYPE OMXMaster::makeComponentInstance(
const char *name,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component) {
Mutex::Autolock autoLock(mLock);

*component = NULL;
//mPluginByComponentName就是我们第一步准备工作中保存所有编解码组件的map,然后找到匹配解码器组件名在其中的位置
ssize_t index = mPluginByComponentName.indexOfKey(String8(name));

if (index < 0) {
return OMX_ErrorInvalidComponentName;
}
//然后根据解码器在其中的位置找到队形的插件,即软解或者硬解插件之一,二选一
OMXPluginBase *plugin = mPluginByComponentName.valueAt(index);
//然后调用注册插件的makeComponentInstance函数
OMX_ERRORTYPE err =
plugin->makeComponentInstance(name, callbacks, appData, component);

if (err != OMX_ErrorNone) {
return err;
}
//将组件和对应插件加入到这个map中
mPluginByInstance.add(*component, plugin);
//
return err;
}

       mPluginByComponentName就是我们第一步准备工作中保存所有编解码组件的map,然后找到匹配解码器组件名在其中的位置,然后根据解码器在其中的位置找到队形的插件,即软解或者硬解插件之一,二选一,最后调用注册插件的makeComponentInstance函数。

       我们依然以高通平台为例,查看步入hardware/qcom/media/libstagefrighthw/QComOMXPlugin.cpp,查看makeComponentInstance函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
OMX_ERRORTYPE QComOMXPlugin::makeComponentInstance(
const char *name,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component) {
if (mLibHandle == NULL) {
return OMX_ErrorUndefined;
}
//会调用mGetHandle函数函数指针,在构造函数中又定义
return (*mGetHandle)(
reinterpret_cast<OMX_HANDLETYPE *>(component),
const_cast<char *>(name),
appData, const_cast<OMX_CALLBACKTYPE *>(callbacks));
}

QComOMXPlugin::QComOMXPlugin()
: mLibHandle(dlopen("libOmxCore.so", RTLD_NOW)),
mInit(NULL),
mDeinit(NULL),
mComponentNameEnum(NULL),
mGetHandle(NULL),
mFreeHandle(NULL),
mGetRolesOfComponentHandle(NULL) {
if (mLibHandle != NULL) {
mInit = (InitFunc)dlsym(mLibHandle, "OMX_Init");
mDeinit = (DeinitFunc)dlsym(mLibHandle, "OMX_Deinit");

mComponentNameEnum =
(ComponentNameEnumFunc)dlsym(mLibHandle, "OMX_ComponentNameEnum");
//重点在这里,会调用hardware/qcom/media/mm-core/omxcore/src/common/qc_omx_core.c的OMX_GetHandle方法
mGetHandle = (GetHandleFunc)dlsym(mLibHandle, "OMX_GetHandle");
mFreeHandle = (FreeHandleFunc)dlsym(mLibHandle, "OMX_FreeHandle");

mGetRolesOfComponentHandle =
(GetRolesOfComponentFunc)dlsym(
mLibHandle, "OMX_GetRolesOfComponent");

(*mInit)();
}
}

       最后会调用qcom OpenMax IL的核心和公共内容。 其中qc_omx_core为主要文件,位于hardware/qcom/media/mm-core/omxcore/src/common/qc_omx_core.c,调用OMX_GetHandle()函数,用户获取各个组件的句柄。这一部分上一篇已经讲过了,可以查看Android多媒体开发(六)—-Android中OpenMax的实现(preview) ,看看高通对于这一部分的而实现。
       假设是hevc/h265类型编码的视频,OMX解码组件会加载对应的libOmxVdec.so或libOmxVdecHevc.so库。高通芯片确实犀利呀,我在同事的nexus手机上查找视频解码库就一个libOmxVdec.so,而别的一些平台放了一大堆so,比如amlogic或者mstar的,不仅夹了很多私货,其实性能也很差。。。。。。

       经过这一路下来,终于完成了解码器的创建工作。简单总结一下:

  • AwesomePlayer中通过initVideoDecoder 来创建video解码器mVideoSource。
  • mVideoSource 中通过上部分demux后的视频流 mVideoTrack来获得解码器的类型,通过类型调用omx->allocateNode 创建omx node实例与自己对应。以后都是通过node实例来操作解码器。
  • 在 omx->allocateNode中 通过mMaster->makeComponentInstance 来创建真正对应的解码器组件。这个解码器组件是完成之后解码实际工作的。
  • 在创建mMaster->makeComponentInstance过程中,也是通过上面mVideoTrack
    过来的解码器类型名,找到相对应的解码器的库,然后实例化。

消息处理

       OMXCodec,OMXCodecObserver和OMXNodeInstance是一一对应的,
简单的可以理解它们3个构成了OpenMAX IL的一个Component,每一个node就是一个codec在OMX服务端的标识。
       当然还有CallbackDispatcher,用于处理codec过来的消息,通过它的post/loop/dispatch来发起接收,最终透过IOMX::onMessage -> OMXNodeInstance::onMessage -> OMXCodecObserver::onMessage -> OMXCodec::on_message一路往上,当然消息的来源是因为我们有向codec注册OMXNodeInstance::kCallbacks,请看:

1
2
3
4
5
6
7
8
9
status_t OMX::allocateNode(
const char *name, const sp<IOMXObserver> &observer, node_id *node) {
...
OMX_ERRORTYPE err = mMaster->makeComponentInstance(
name, &OMXNodeInstance::kCallbacks,
instance, &handle);
...
return OK;
}

       kCallbacks包含3种事件:

1
2
3
OMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = {
&OnEvent, &OnEmptyBufferDone, &OnFillBufferDone
};

       它们分别都会调用到自己owner的OnEvent/OnEmptyBufferDone/OnFillBufferDone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// static
OMX_ERRORTYPE OMXNodeInstance::OnEvent(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_EVENTTYPE eEvent,
OMX_IN OMX_U32 nData1,
OMX_IN OMX_U32 nData2,
OMX_IN OMX_PTR pEventData) {
OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
if (instance->mDying) {
return OMX_ErrorNone;
}
return instance->owner()->OnEvent(
instance->nodeID(), eEvent, nData1, nData2, pEventData);
}

       而owner相应的又会调用自己dispatcher的post方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
OMX_ERRORTYPE OMX::OnEvent(
node_id node,
OMX_IN OMX_EVENTTYPE eEvent,
OMX_IN OMX_U32 nData1,
OMX_IN OMX_U32 nData2,
OMX_IN OMX_PTR pEventData) {
ALOGV("OnEvent(%d, %ld, %ld)", eEvent, nData1, nData2);

omx_message msg;
msg.type = omx_message::EVENT;
msg.node = node;
msg.u.event_data.event = eEvent;
msg.u.event_data.data1 = nData1;
msg.u.event_data.data2 = nData2;

findDispatcher(node)->post(msg);

return OMX_ErrorNone;
}

       这样所有的事件都串起来了,消息有来源,有最终的去处!
       结合这些信息所以我们就可以认为在这里是创建出了一个Component出来。下面贴个图:
时序图

硬解还是软解

       上面加载解码组件的时候有一段很重要的话,为了加深记忆我要再贴一遍:

       实际上系统中存在的解码器可以很多,但能够被应用使用的解码器是根据配置来的,即/etc/media_codecc.xml。这个文件一般由硬件或者系统的生产厂家在build整个系统的时候提供,一般是保存在代码的device/[company]/[codename]目录下的,例如device/samsung/manta/media_codecs.xml。这个文件配置了系统中有哪些可用的codec以及,这些codec对应的媒体文件类型。在这个文件里面,系统里面提供的软硬codec都需要被列出来。
       也就是说,如果系统里面实际上包含了某个codec,但是并没有被配置在这个文件里,那么应用程序也无法使用到!

       在这里配置文件里面,如果出现多个codec对应同样类型的媒体格式的时候,这些codec都会被保留起来。当系统使用的时候,将后选择第一个匹配的codec。除非是指明了要软解码还是硬解码,但是Android的framework层为上层提供服务的AwesomePlayer中在处理音频和视频的时候,对到底是选择软解还是硬解的参数没有设置。所以虽然底层是支持选择的,但是对于上层使用MediaPlayer的Java程序来说,还是只能接受默认的codec选取规则。

       一般来说,如果系统里面有对应媒体的硬件解码器的话,系统开发人员应该是会配置在media_codecs.xml中,所以大多数情况下,如果有硬件解码器,那么我们总是会使用到硬件解码器。

       所以对于这种情况,经常听到APP开发人员骂google的MediaPlayer支持格式太少,兼容性太差,然后将锅强行甩给google。。。。。。这事也不能怨google,要怪就怪芯片商或者方案商吧!

       google估计是听到的骂声太多了,所以推出了ExoPlayer,可以避免这种芯片商这种匹配第一个codec规则的弊端。

结语

       本篇算是对Android中OpenMax的实现做了一个差不多详细的分析。我水平有限,这几天看源码看的也很头疼,如有错误欢迎指正,我会在第一时间修改的。
妹子

坚持技术分享,您的支持将鼓励我继续创作!