1. 程式人生 > >[gitbook] Android框架分析系列之Android stagefright框架

[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的處理