[gitbook] Android框架分析系列之Android stagefright框架
請支援作者原創:
https://mr-cao.gitbooks.io/Android/content 點選開啟連結
本文以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
檢測一個檔案是否是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<MediaPlayerBase> 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<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre(
player_type playerType)
{
ALOGV("player type = %d", playerType);
// create the right type of player
sp<MediaPlayerBase> 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<MediaPlayerInterface*>(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<String8> *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中。
// static
OMX_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<MediaSource> 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<IOMXObserver> &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物件的指標
// static
OMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = {
&OnEvent, &OnEmptyBufferDone, &OnFillBufferDone
};
kCallbacks用來接收元件內部的訊息。
OMXMaster的makeComponentInstance方法邏輯如下:
根據元件的名字,找到其對應的OMXPluginBase(類似於元件管理者的角色)物件。然後呼叫OMXPluginBase的makeComponentInstance方法獲得OMX_COMPONENTTYPE物件的指標。
OMXMaster內部維護著一個map表:KeyedVector<String8, OMXPluginBase *> mPluginByComponentName;
。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 */
// static
OMX_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的處理