[gitbook] Android框架分析系列之Android stagefright框架
请支持作者原创:
https://mr-cao.gitbooks.io/Android/content 点击打开链接
Table of Contents
- 1. MP3文件播放过程
- 1.1. MP3文件简介
- 1.2. stagefright框架
- 1.3. parser步骤
- 1.4. 解码器组件的加载
- 1.5. 解码过程
本文以Android6.0系统源码为基础,分析Android stagefright的框架。
1. MP3文件播放过程
我觉得弄清楚一个播放框架是如何运转的,应该以播放某个具体的文件为例子,沿着函数调用的流程一步步的深入下去,如果能够大致弄明白在播放文件过程中诸如文件是如何解析,数据buffer的分配与传递,以及消息机制的处理这些知识点,可以算是入门了。因为一个音视频播放框架代码其实还是蛮庞大的,如果陷入每一个细节的深究,可能不能把握整体的脉络。这篇文章主要以MP3文件和MP4文件为例子讲解stagefright的框架。因为音频文件的格式和播放流程相对简单,所以首先以MP3文件的处理来展开分析。
1.1. MP3文件简介
MP3文件是一种很常见的数字音频格式,属于有损编码。这里不介绍MP3文件的具体编码是如何实现的,而是关注MP3的文件规范,也就是MP3文件的数据是如何组织的。
一个MP3文件由一个个独立的frame组成。每一个frame又包括frame header和真正的音频编码数据。在MP3文件的开头或者结尾可能存在ID3标准的多媒体信息,这些信息包括歌曲的Title,Artist,Album等。
下面图简单的描述了MP3文件的格式:
其中符号A的长度是11,代表着FrameSync,A全部置为1.符号B的长度为2位,代表着文件类型:
-
00: MPEG Version2.5
-
01: 保留
-
10: MPEG Version2
-
11: MPEG Version 1
符号C长度为2,描述着了MPEG文件层次:
-
00: 保留
-
01: Layer III
-
10: Layer II
-
11: Layrr I
其它位的描述可以参考:MPEG Audio Layer I/II/III frame header。
检测一个文件是否是MP3文件,就是通过检测文件中是否有满足MP3规范的Frame存在。播放一个MP3文件,基本存在以下几个步骤:
-
分辨文件是否是MP3文件
-
从MP3文件的Frame中提取出Audio数据,交给解码器进行解码
-
解码器所得的pcm数据进行播放
1.2. stagefright框架
这里以MediaPlayer的实现为入口探究stagefright的框架。在java层,Android提供了MediaPlayer类用于播放音视频。这个java类通过调用jni的方法,访问native的MediaPlayer类的功能。可以说java层的MediaPlayer类只是native层的MediaPlayer的一个包装。
那么native层的MediaPlayer与stagefrgiht又有什么关系呢?可以参考下图:
从上图中可以看出,MediaPlayer使用binder通信协议访问MediaPlayerService,每个MediaPlayer在meida_server进程中都对应一个Client。MediaPlayer持有Client的代理端,通过IMediaPlayer接口类跨进程调用Client的服务。同时MediaPlayer也实现了binder接口,Client类内部也持有其代理类:IMediaPlayerClient。Client内部的状态以及各种消息都是通过IMediaPlayerClient的virtual void notify(int msg,int ext1,int ext2,const Parcel *obj) =0
接口通知给MediaPlayer。MediaPlayer进而通过jni将消息传递到java层的MediaPlayer对象。
每个Client内部都持有一个MediaPlayerBase的智能指针。MediaPlayer的start,stop等函数的实现,实际上是转交给MediaPlayerBase去实现。用户可以扩展多种MediaPlayerBase,根据需求不同使用不同的MediaPlayerBase来播放文件。MediaPlayerFactory就是用来管理多种player,它的实现使用了抽象工厂模式。IFactory是一个抽象的工厂类,用户负责实现。它的主要功能是探测文件,以及生产对应的MediaPlayerBase产品。
Android内部注册的IFactory有:
void MediaPlayerFactory::registerBuiltinFactories() { Mutex::Autolock lock_(&sLock); if (sInitComplete) return; registerFactory_l(new StagefrightPlayerFactory(), STAGEFRIGHT_PLAYER); registerFactory_l(new NuPlayerFactory(), NU_PLAYER); registerFactory_l(new TestPlayerFactory(), TEST_PLAYER); sInitComplete = true;}
每个MeidaPlayerBase都有其对应的player_type,源码中的定义在MediaPlayerInterface.h中:
enum player_type { STAGEFRIGHT_PLAYER = 3, NU_PLAYER = 4, // Test players are available only in the 'test' and 'eng' builds. // The shared library with the test player is passed passed as an // argument to the 'test:' url in the setDataSource call. TEST_PLAYER = 5,};
在播放一个文件的时候,首先要为这个文件找到匹配的MediaPlayerBase。在MediaPlayer调用setDataSource过程中,Client会创建一个合适的MediaPlayerBase,并调用其setDataSource:
status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length){ struct stat sb; int ret = fstat(fd, &sb); if (ret != 0) { ALOGE("fstat(%d) failed: %d, %s", fd, ret, strerror(errno)); return UNKNOWN_ERROR; } if (offset >= sb.st_size) { ALOGE("offset error"); ::close(fd); return UNKNOWN_ERROR; } if (offset + length > sb.st_size) { length = sb.st_size - offset; ALOGV("calculated length = %lld", length); } player_type playerType = MediaPlayerFactory::getPlayerType(this, fd, offset, length); sp p = setDataSource_pre(playerType); if (p == NULL) { return NO_INIT; } // now set data source setDataSource_post(p, p->setDataSource(fd, offset, length)); return mStatus;}sp MediaPlayerService::Client::setDataSource_pre( player_type playerType){ ALOGV("player type = %d", playerType); // create the right type of player sp p = createPlayer(playerType); if (p == NULL) { return p; } if (!p->hardwareOutput()) { Mutex::Autolock l(mLock); mAudioOutput = new AudioOutput(mAudioSessionId, IPCThreadState::self()->getCallingUid(), mPid, mAudioAttributes); static_cast(p.get())->setAudioSink(mAudioOutput); } return p;}
上图中首先是获得文件对应的player_type,然后创建player_type对应的MediaPlayerBase,最后调用MediaPlayerBase的setDataSource。
这里最关键的是,如何根据文件来获得其player_type。MediaPlayerFactory的getPlayerType方法会调用注册的IFactory的scoreFactory方法,这个方法会返回一个得分,得分高的IFactory的player_type被返回。如果系统中有多种MeidaPlayerBase,那么具体的IFactory在实现scoreFactory的时候,需要读取文件的内容,判断文件的格式,看这个格式自己是否支持播放,如果支持那么就可以返回1(最高得分),或者是返回0(最低得分)。
在源码的实现中,StagefrightFactory的scoreFactory直接返回了1,被当做默认的IFactory。理所当然的,系统默认的player就是StagefrightPlayer了。
那StagefrightPlayer的实现机制又是如何的呢?下面会通过几个图来简单的讲解下
通过上图可以发现,StagefrightPlayer内部有指针指向一个AwesomePlayer对象,StagefrightPlayer内部的操作基本转手又交给AwesomePlayer去完成了;而AwesomePlaer也有指针指向StageFrightPlayer,这个指针的作用是用于播放过程中的消息传递,通过callback机制,将AwesomePlayer内部的状态传递给MediaPlayerBase(StagefrightPlayer)。播放MP3文件中,很重要的一环就是解码压缩的音频数据,这个是由解码组件完成。在stagefright框架中,组件的实现使用了OpenMax标准。Android源码提供了一些软件解码和编码的组件,它们被抽象为SoftOMXComponent。OMXPluginBase扮演者组件的管理者。它负责加载组件库,创建组件实例。而OMXMaster则管理着OMXPluginBase,Android原生提供的组件都是由SoftOMXPlugin类来管理,这个类就是继承自OMXPluginBase。对于厂商来说,如果要实现自己的组件管理模块,需要实现OMXPluginBase,并将之编译为libstagefrighthw.so。在OMXMaster中会加载这个库文件,然后调用其createOMXPlugin方法获得一个OMXPluginBase指针,后续都会通过OMXPluginBase来管理组件。
OMXPluginBase的定义如下:
struct OMXPluginBase { OMXPluginBase() {} virtual ~OMXPluginBase() {} virtual OMX_ERRORTYPE makeComponentInstance( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) = 0; virtual OMX_ERRORTYPE destroyComponentInstance( OMX_COMPONENTTYPE *component) = 0; virtual OMX_ERRORTYPE enumerateComponents( OMX_STRING name, size_t size, OMX_U32 index) = 0; virtual OMX_ERRORTYPE getRolesOfComponent( const char *name, Vector *roles) = 0;private: OMXPluginBase(const OMXPluginBase &); OMXPluginBase &operator=(const OMXPluginBase &);};
OMXPluginBase负责:
-
创建组件实例OMX_COMPONENTTYPE
-
销毁组件实例
-
枚举组件名
-
获得组件对应的role(这个该怎么翻译?角色?表示组件的作用)
所以对组件的管理可以这么总结: 通过OMXMaster加载libstagefrighthw.so库文件,创建OMXPluginBase,通过这个类来管理组件。
对于AwesomePlayer来说,它并不直接接触解码组件,而是通过创建OMXCodec来和组件交互。OMXCode内部有一个id,这个id对应于一个OMXNodeInstance。OMX对象中会对产生的每一个OMXNodeInstance分配一个唯一的node_id。每一个OMXNodeInstance内部又保存着组件实例的指针,通过这个指针就可以和组件进行交互了。交互的流程为:OMXCodec → OMX → OMXNodeInstance → OMX_COMPONENTTYPE。而组件的消息则通过OMX_CALLBACKTYPE传递到OMX类,然后也OMX分发给对应OMXNodeInstance。OMXNodeInstance在把消息转发给OMXCodec。
总结下,AwesomePlayer通过OMXCodec建立起和OMX组件的联系。OMXNodeInstance中保存着组件的实例指针,通过OM_Core.h中的宏来操作组件。比如OMX_SendCommand宏的实现如下:
#define OMX_SendCommand( \ hComponent, \ Cmd, \ nParam, \ pCmdData) \ ((OMX_COMPONENTTYPE*)hComponent)->SendCommand( \ hComponent, \ Cmd, \ nParam, \ pCmdData)
这第一个参数就是OMXInstance中的mHandle变量。
在OMXPluginBase创建组件实例的时候,需要传递一个callback给组件:
typedef struct OMX_CALLBACKTYPE{ OMX_ERRORTYPE (*EventHandler)( 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); OMX_ERRORTYPE (*EmptyBufferDone)( OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_PTR pAppData, OMX_IN OMX_BUFFERHEADERTYPE* pBuffer); OMX_ERRORTYPE (*FillBufferDone)( OMX_OUT OMX_HANDLETYPE hComponent, OMX_OUT OMX_PTR pAppData, OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer);} OMX_CALLBACKTYPE;
这个callback用于接收组件的消息,它的实现是在OMXNodeInstance.cpp中。
// staticOMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = { &OnEvent, &OnEmptyBufferDone, &OnFillBufferDone};
kcallbacks是OMXNodeInstance的静态成员变量,它内部的三个函数指针分别指向了OMXNodeInstance的三个静态方法。
下面是一张组件图,大致描述了AwesomePlayer和libstagefrighthw的关系:
AwesomePlayer会为每一个播放的文件,常见一个MediaExtractor,这个类的作用就是做解析用,概念上等同于demuxer或者是Parser。MediaExtractor负责从文件中分离音视频数据,并抽象为MediaSource。MediaSource生产数据,送往OMXCodec。OMXCodec又将数据通过OpenMax的接口送往组件。组件解码之后的数据会返回给OMXCodec,之后由OMXCodec送往播放模块。所以OMXCodec起到一个桥梁作用。
下面就是的类图展示了MP3播放过程中几个关键类的关系:
在上图中,MP3Source负责为OMXCodec从原始文件中读取数据,OMXCodec负责解码,将解码之后的数据松给AudioPlayer,对于不支持hardware output属性的平台,AudioPlayer实际上是调用AudioTrack去播放这些数据。
1.3. parser步骤
对一个MP3文件而言,parser的作用就是
-
提取metadata数据,比如artist,album等;
-
提供文件中的音频压缩数据给解码器
以上两点在Android中是如何实现的呢?
在AwesomePlayer的setDataSource方法中,首先是根据文件句柄创建了一个FileSource对象,这个对象负责读取文件,并提取文件的mime type(文件类型)。FileSource的基类是DataSource,在DataSource中,注册了一些探测器函数,这些函数用来探测文件的类型。
void DataSource::RegisterDefaultSniffers() { Mutex::Autolock autoLock(gSnifferMutex); if (gSniffersRegistered) { return; } RegisterSniffer_l(SniffMPEG4); RegisterSniffer_l(SniffMatroska); RegisterSniffer_l(SniffOgg); RegisterSniffer_l(SniffWAV); RegisterSniffer_l(SniffFLAC); RegisterSniffer_l(SniffAMR); RegisterSniffer_l(SniffMPEG2TS); RegisterSniffer_l(SniffMP3); RegisterSniffer_l(SniffAAC); RegisterSniffer_l(SniffMPEG2PS); RegisterSniffer_l(SniffWVM); RegisterSniffer_l(SniffMidi); char value[PROPERTY_VALUE_MAX]; if (property_get("drm.service.enabled", value, NULL) && (!strcmp(value, "1") || !strcasecmp(value, "true"))) { RegisterSniffer_l(SniffDRM); } gSniffersRegistered = true;}
在AwesomePlayer的构造函数中就会调用DataSource::RegisterDefaultSniffers();
来注册这些探测函数。
在Android中,FileSource只是负责对文件类型进行探测,以及负责读取文件,真正对文件数据进行分析的是MediaExtractor类。MediaExtractor也是个基类,不同的子类对应不同的文件类型。MediaExtractor创建的过程是,首先调用DataSource的探测函数获得文件类型,对于MP3文件来说,其文件类型为“audio/mpeg”。然后根据这个文件类型创建MP3Extractor对象。
MP3Extractor就充当着parser的作用,它负责从FileSource中读取数据,解析出文件的metadata,并且为音视频流创建一个抽象类:MediaSource。对于MP3而言就是其子类MP3Source。
sp MP3Extractor::getTrack(size_t index) { if (mInitCheck != OK || index != 0) { return NULL; } return new MP3Source( mMeta, mDataSource, mFirstFramePos, mFixedHeader, mSeeker);}
MP3Source负责从文件中定位帧数据的位置,并读取帧数据。
1.4. 解码器组件的加载
通过parser部分,已经从mp3文件中提取了metadata数据,以及定位了帧数据。下一步就是创建解码器来负责解码帧数据。
解码器的创建是在prepare步骤。之前已经讲过,对于AwesomePlayer来说,创建解码器就是创建一个OMXCodec对象。
OMXCodec的静态方法create,负责创建对应的OMXCodec,值得注意的是OMXCodec的基类也是MediaSource。
创建OMXCodec的步骤如下:
-
根据文件的mime type,找到匹配的组件;
-
调用omx的allocateNode方法,创建一个组件实例,并返回组件实例的id号;
-
以组件名,mine type字符串和组件实例的id号码为参数创建OMXCodec
1.4.1. 根据mime type 匹配组件
在Android系统中,文件/etc/media_codecs.xml描述了系统支持的codec的情况。 以moto shamu设备的此文件为例子,其内容如下:
<MediaCodecs> <Include href="media_codecs_google_audio.xml" /> <Include href="media_codecs_google_telephony.xml" /> <Settings> <Setting name="max-video-encoder-input-buffers" value="9" /> Settings> <Encoders> <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> ………… Encoders> <Decoders> <MediaCodec name="OMX.qcom.video.decoder.avc" type="video/avc" > <Quirk name="requires-allocate-on-input-ports" /> <Quirk name="requires-allocate-on-output-ports"/> <Limit name="size" min="64x64" max="4096x2160" /> <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"/> <Limit name="size" min="64x64" max="4096x2160" /> <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="5" /> MediaCodec> ………… Decoders> <Include href="media_codecs_google_video.xml" />MediaCodecs>
一份media_codecs.xml文件中可以包括以下几部分内容:
-
include其他codec的配置文件
-
encoder的配置
-
decoder的配置
MediaCodecList类负责加载解析codec配置文件。每一个MediaCodec标签对应于一个codec。以decoders为例,每一个codec都有name和type。这个type就是对应着代码中的mime type。也就是如果文件的mime type和组件的type字符串一样,那么此codec就可以解码这个文件。通过配置文件,可以很方便的获得所有支持文件mime type的组件名。
1.4.2. 分配组件实例
经过上面的步骤,找到了匹配mimetype的组件名。接着调用OMX的接口来分配一个组件实例。
status_t OMX::allocateNode( const char *name, const sp &observer, node_id *node) { Mutex::Autolock autoLock(mLock); *node = 0; OMXNodeInstance *instance = new OMXNodeInstance(this, observer, name); OMX_COMPONENTTYPE *handle; OMX_ERRORTYPE err = mMaster->makeComponentInstance( name, &OMXNodeInstance::kCallbacks, instance, &handle); if (err != OMX_ErrorNone) { ALOGE("FAILED to allocate omx component '%s' err=%s(%#x)", name, asString(err), err); instance->onGetHandleFailed(); return StatusFromOMXError(err); } *node = makeNodeID(instance); mDispatchers.add(*node, new CallbackDispatcher(instance)); instance->setHandle(*node, handle); mLiveNodes.add(IInterface::asBinder(observer), instance); IInterface::asBinder(observer)->linkToDeath(this); return OK;}
-
在OMX中以OMXNodeInstance对象代表着一个组件实例。每一个OMXNodeInstance都有一个唯一的编号
-
OMXMaster的makeComponentInstance方法负责生成OMX_COMPONENTTYPE对象,并将OMX_COMPONENTTYPE对象的指针保存在形参中返回
-
OMXNodeInstance的setHandle方法,保存了创建的OMX_COMPONENTTYPE对象的指针
// staticOMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = { &OnEvent, &OnEmptyBufferDone, &OnFillBufferDone};
kCallbacks用来接收组件内部的消息。
OMXMaster的makeComponentInstance方法逻辑如下:
根据组件的名字,找到其对应的OMXPluginBase(类似于组件管理者的角色)对象。然后调用OMXPluginBase的makeComponentInstance方法获得OMX_COMPONENTTYPE对象的指针。
OMXMaster内部维护着一个map表:KeyedVector
。key为组件的名字,value为OMXPluginBase的指针。一个OMXPluginBase管理着很多的组件,所以有多个组件名对应着同一个OMXPluginBase。
那么OMXPluginBase又是从何而来呢?
OMXMaster::OMXMaster() : mVendorLibHandle(NULL) { addVendorPlugin(); addPlugin(new SoftOMXPlugin);}void OMXMaster::addVendorPlugin() { addPlugin("libstagefrighthw.so");}
-
加载厂商实现的libstagefrighthw.so,然后中动态库中找到函数符号createOMXPlugin的地址,调用此函数返回一个OMXPluginBase指针
-
Android Stagefright框架提供的SoftOMXPlugin(负责管理Stagefright框架提供的软codec)
以下的代码片段,显示了OMXMaster调用OMXPluginBase的enumerateComnonets方法获得其内部管理的组件的名字。
void OMXMaster::addPlugin(OMXPluginBase *plugin) { Mutex::Autolock autoLock(mLock); 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); if (mPluginByComponentName.indexOfKey(name8) >= 0) { ALOGE("A component of name '%s' already exists, ignoring this one.", name8.string()); continue; } mPluginByComponentName.add(name8, plugin); } if (err != OMX_ErrorNoMore) { ALOGE("OMX plugin failed w/ error 0x%08x after registering %zu " "components", err, mPluginByComponentName.size()); }}
看清了OMXMaster如何管理组件之后,我们在回到组件实例的创建过程中来。上面已经说道,OMXMaster根据组件名找到其管理者OMXPluginBase。接着会调用OMXPluginBase的makeComponentInstance方法,获得OMX_COMPONENTTYPE指针。
OMXPluginBase只是Android提供的抽象类。具体的实现依赖各个平台。但是我们可以以SoftOMXPlugin来看看一个OMXPluginBase应该如何实现。因为SoftOMXPlugin也是继承于OMXPluginBase。
首先,SoftOMXPlugin内部维持着一张表,描述着它支持的组件信息,包括组件名,组件所对应的动态库的前缀名,以及组件的role字符串。
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", "avcdec", "video_decoder.avc" }, { "OMX.google.h264.encoder", "avcenc", "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.mpeg2.decoder", "mpeg2dec", "video_decoder.mpeg2" }, { "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" },};
SoftOMXPlugin::makeComponentInstance的逻辑如下:
-
首先根据组件名找到对应的库名前缀
-
然后和字符串“libstagefright_soft_”拼接出完整的组件动态库名
-
然后加载组件库文件,并找到符号createSoftComonent的地址
-
调用函数createSoftComponent获得OMX_COMPONENTTYPE指针
以MP3为例子,它的mime type为audio/mpeg,在xml配置文件中它所对应的组件:
<MediaCodec name="OMX.google.mp3.decoder" type="audio/mpeg"> <Limit name="channel-count" max="2" /> <Limit name="sample-rate" ranges="8000,11025,12000,16000,22050,24000,32000,44100,48000" /> <Limit name="bitrate" range="8000-320000" />MediaCodec>
以组件名OMX.google.mp3.decoder找到其对应的OMXPluginBase,即SoftOMXPlugin。然后根据OMX.google.mp3.decoder组件对应的库前缀名mp3dec,拼接出完整的库文件名:libstagefright_soft_mp3dec.so。加载这个库,获得createSoftOMXComponent的地址之后,调用这个方法:
android::SoftOMXComponent *createSoftOMXComponent( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) { return new android::SoftMP3(name, callbacks, appData, component);
SoftMP3的基类为SoftOMXComponent。其构造函数如下:
SoftOMXComponent::SoftOMXComponent( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) : mName(name), mCallbacks(callbacks), mComponent(new OMX_COMPONENTTYPE), mLibHandle(NULL) { mComponent->nSize = sizeof(*mComponent); mComponent->nVersion.s.nVersionMajor = 1; mComponent->nVersion.s.nVersionMinor = 0; mComponent->nVersion.s.nRevision = 0; mComponent->nVersion.s.nStep = 0; mComponent->pComponentPrivate = this; mComponent->pApplicationPrivate = appData; mComponent->GetComponentVersion = NULL; mComponent->SendCommand = SendCommandWrapper; mComponent->GetParameter = GetParameterWrapper; mComponent->SetParameter = SetParameterWrapper; mComponent->GetConfig = GetConfigWrapper; mComponent->SetConfig = SetConfigWrapper; mComponent->GetExtensionIndex = GetExtensionIndexWrapper; mComponent->GetState = GetStateWrapper; mComponent->ComponentTunnelRequest = NULL; mComponent->UseBuffer = UseBufferWrapper; mComponent->AllocateBuffer = AllocateBufferWrapper; mComponent->FreeBuffer = FreeBufferWrapper; mComponent->EmptyThisBuffer = EmptyThisBufferWrapper; mComponent->FillThisBuffer = FillThisBufferWrapper; mComponent->SetCallbacks = NULL; mComponent->ComponentDeInit = NULL; mComponent->UseEGLImage = NULL; mComponent->ComponentRoleEnum = NULL; *component = mComponent;}
由以上代码可知,OMX_COMPONENTTYPE内部的函数指针全部都指向SoftOMXComponent.cpp中的静态函数。以SendCommand为例子:
#define OMX_SendCommand( \ hComponent, \ Cmd, \ nParam, \ pCmdData) \ ((OMX_COMPONENTTYPE*)hComponent)->SendCommand( \ hComponent, \ Cmd, \ nParam, \ pCmdData) /* Macro End */// staticOMX_ERRORTYPE SoftOMXComponent::SendCommandWrapper( OMX_HANDLETYPE component, OMX_COMMANDTYPE cmd, OMX_U32 param, OMX_PTR data) { SoftOMXComponent *me = (SoftOMXComponent *) ((OMX_COMPONENTTYPE *)component)->pComponentPrivate; return me->sendCommand(cmd, param, data);}
当OMXNodeInstance调用OMX_SendCommand的时候,这个宏展开后,实际上调用的是组件实例OMX_COMPONENTTYPE的SendCommand的方法。而这个方法却是由SoftOMXComponent的静态方法SendCommandWrapper负责具体的实现。最终是调用到了SoftOMXCompoent的虚函数` virtual OMX_ERRORTYPE sendCommand( OMX_COMMANDTYPE cmd, OMX_U32 param, OMX_PTR data); `。
至此,一个组件实例OMX_COMPONENTTYPE的创建过程算是结束了。
1.5. 解码过程
经过上面的步骤之后,负责提供MP3音频压缩数据的MP3Source创建成功了,并且解码器对象OMXCodec也创建成功了,下面要做的就是触发解码器获取压缩数据,然后进行解码。将解码之后的数据送到下一个处理单元进行播放。
1.5.1. OMXCodec初始化
OMX的初始化是在其方法OMXCodec::init()中进行,大致做了如下事情:
-
给组件发送命令,命令组件进入idle状态。
mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle)
-
组件在设置状态进入idle之后,会通过EventHandler给OMXCodec发送状态设置完毕的消息。
OMXCodec::on_message
方法中会对这个OMX_EventCmdComplete进行处理。OMXCodec在init的过程中,状态会经过,LOADED_TO_IDLE→IDLE_TO_EXECUTING→EXECUTING的变化过程. -
分配buffer。分别为input port和output port端口分配buffer,同时使用OMX的标准函数OMX_UseBuffer通知组件为buffer分配header.
1.5.2. 数据buffer的处理
更多相关文章
- Afinal 是一个android的sqlite orm 和 ioc 框架。同时封装了andr
- android不是内部或外部命令,也不是可运行的程序或批处理文件
- 整理 酷炫 Android、Flutter 开源UI框架
- 【源码分享下载】每日更新之Android快速开发框架LoonAndroid
- android sdk 文件目录含义介绍
- 如何查看android数据文件?
- android 核心组件( 2 )
- Android 四大核心组件之Activity[生命周期篇]