Android Media Framework(4): 支持格式的擴展

Android Media Framework 框架的層次:

  1. Java層:frameworks/base/media/java/android/media/MediaPlayer.java
  2. JNI本地調(diào)用:frameworks/base/media/jni/android_media_MediaPlayer.cpp
  3. libmedia多媒體底層庫:frameworks/base/media/libmedia/mediaplayer.cpp
  4. libmediaplayer多媒體服務(wù)部分:frameworks/base/media/libmediaplayerservice/MediaPlayerService.cpp, StagefrightPlayer.cpp
  5. Stagefright框架:frameworks/base/media/libstagefright/AwesomePlayer.cpp, OMX

整個Media框架的核心是AwesomePlayer,Awesomeplayer中對應(yīng)的數(shù)據(jù)結(jié)構(gòu)主要有DataSource, MediaExtractor, MediaSource。其中:

  • DataSource主要負(fù)責(zé)提供原始數(shù)據(jù)
  • MediaSource負(fù)責(zé)提供demux后的數(shù)據(jù)(即實際的audio 或者 video數(shù)據(jù)包)
  • MediaExtractor則負(fù)責(zé)中間的過程,即將從DataSource得到的原始數(shù)據(jù)解析成解碼器需要的es數(shù)據(jù),并通過MediaSource的接口輸出。

Android本身支持的文件格式有限,硬件廠商有時出于運營策略也會限制部分格式支持,被限制的部分格式需要收費等,于是公司就有自己擴展支持格式的需求。

我們先看AwesomePlayer工作的3個大的步驟:

  1. 探測文件類型,根據(jù)文件類型創(chuàng)建對應(yīng)的提取器(MediaExtractor);
  2. 進(jìn)入/libstagefright/omx/SoftOMXPlugin.cpp讀取kComponents數(shù)組,此為現(xiàn)有系統(tǒng)支持的解碼器列表;
  3. 讀取/etc/media_codec.xml文件,根據(jù)已探測的文件類型獲取需要的解碼器名稱,再對比kComponents得到實際的解碼器。

我們具體的來細(xì)看每一個步驟:

1) 探測文件類型,根據(jù)文件類型創(chuàng)建對應(yīng)的提取器(MediaExtractor)

在AwesomePlayer的構(gòu)造函數(shù)中由DataSource注冊sniff探測文件類型:

AwesomePlayer::AwesomePlayer(){  
    ...
    DataSource::RegisterDefaultSniffers();  
    ...
}  

RegisterDefaultSniffers的實現(xiàn):

void DataSource::RegisterDefaultSniffers() {
    RegisterSniffer(SniffMPEG4);  
    RegisterSniffer(SniffFragmentedMP4);  
    RegisterSniffer(SniffMatroska);  
    RegisterSniffer(SniffOgg);  
    RegisterSniffer(SniffWAV);  
    RegisterSniffer(SniffFLAC);  
    RegisterSniffer(SniffAMR);  
    RegisterSniffer(SniffMPEG2TS);  
    RegisterSniffer(SniffMP3);  
    RegisterSniffer(SniffAAC);  
    RegisterSniffer(SniffMPEG2PS);  
    RegisterSniffer(SniffWVM);  
  
    char value[PROPERTY_VALUE_MAX];  
    if (property_get("drm.service.enabled", value, NULL)  
            && (!strcmp(value, "1") || !strcasecmp(value, "true"))) {  
        RegisterSniffer(SniffDRM);  
    }  
}
// static
void DataSource::RegisterSniffer(SnifferFunc func) {
    Mutex::Autolock autoLock(gSnifferMutex);
 
    for (List<SnifferFunc>::iterator it = gSniffers.begin();
         it != gSniffers.end(); ++it) {
        if (*it == func) {
            return;
        }
    }
 
    gSniffers.push_back(func);
}

從代碼可以看出RegisterDefaultSniffers的主要作用既是注冊Sniffer函數(shù)將所有的sniffer函數(shù)都掛在全局鏈表gSniffers中。sniffer函數(shù)的主要作用就是用于探測文件的類型,每種類型的媒體文件都對應(yīng)一個sniffer函數(shù)。這里從代碼可以看出原生的android播放器支持的格式還比較少。

AwesomePlayer中extractor 創(chuàng)建流程:
在setDataSource的最后,會調(diào)用setDataSource_l(dataSource),將datasource和對應(yīng)的extractor對應(yīng)起來。

status_t AwesomePlayer::setDataSource_l(  
        const sp<DataSource> &dataSource) {  
    sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);  
   
    if (extractor == NULL) {  
        return UNKNOWN_ERROR;  
    }  
   
    if (extractor->getDrmFlag()) {  
        checkDrmStatus(dataSource);  
    }  
   
    return setDataSource_l(extractor);  
}  

MediaExtractor::Create(), 這里通過MediaExtractor::Create創(chuàng)建extractor:

sp<MediaExtractor> MediaExtractor::Create(  
        const sp<DataSource> &source, const char *mime) {  
    sp<AMessage> meta;  
   
    String8 tmp;  
    if (mime == NULL) {  
        float confidence;  
        if (!source->sniff(&tmp, &confidence, &meta)) {  
            ALOGV("FAILED to autodetect media content.");  
   
            return NULL;  
        }  
   
        mime = tmp.string();  
        ALOGV("Autodetected media content as '%s' with confidence %.2f",  
             mime, confidence);  
    } 

主要是將gSniffers鏈表中的每種格式的函數(shù)調(diào)用一遍,選取最高的confidence作為選中的文件格式。

MediaExtractor *ret = NULL;  
    if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)  
            || !strcasecmp(mime, "audio/mp4")) {  
        int fragmented = 0;  
        if (meta != NULL && meta->findInt32("fragmented", &fragmented) && fragmented) {  
            ret = new FragmentedMP4Extractor(source);  
        } else {  
            ret = new MPEG4Extractor(source);  
        }  
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {  
        ret = new MP3Extractor(source, meta);  
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)  
            || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {  
        ret = new AMRExtractor(source);  
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {  
        ret = new FLACExtractor(source);  
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {  
        ret = new WAVExtractor(source);  
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {  
        ret = new OggExtractor(source);  
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {  
        ret = new MatroskaExtractor(source);  
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {  
        ret = new MPEG2TSExtractor(source);  
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WVM)) {  
        // Return now.  WVExtractor should not have the DrmFlag set in the block below.  
        return new WVMExtractor(source);  
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {  
        ret = new AACExtractor(source, meta);  
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {  
        ret = new MPEG2PSExtractor(source);  
    }  
   
    if (ret != NULL) {  
       if (isDrm) {  
           ret->setDrmFlag(true);  
       } else {  
           ret->setDrmFlag(false);  
       }  
    }  
   
    return ret;  
} 

成功的通過sniff函數(shù)確定了文件的格式之后,就可以構(gòu)造對應(yīng)的extractor對象。


2)進(jìn)入/libstagefright/omx/SoftOMXPlugin.cpp讀取kComponents數(shù)組,此為現(xiàn)有系統(tǒng)支持的解碼器列表

AwesomePlayer通過OMXClient::connect()得到OMX的實例,在構(gòu)造OMX對象的過程中又調(diào)用了OMXMaster的構(gòu)造函數(shù)創(chuàng)建OMXMaster的對象。

status_t OMXClient::connect() {  
    sp<</span>IServiceManager> sm = defaultServiceManager();  
    sp<</span>IBinder> binder = sm->getService(String16("media.player"));  
    sp<</span>IMediaPlayerService> service = interface_cast<</span>IMediaPlayerService>(binder);  
    
    CHECK(service.get() != NULL);  
    
    mOMX = service->getOMX();  
    CHECK(mOMX.get() != NULL);  
    
    if (!mOMX->livesLocally(NULL , getpid())) {  
        ALOGI("Using client-side OMX mux.");  
        mOMX = new MuxOMX(mOMX);  
    }  
    
    return OK;  
}  
OMX::OMX()  
    : mMaster(new OMXMaster),  
      mNodeCounter(0) {  
}  

在OMXMaster構(gòu)造函數(shù)中有addPlugin()函數(shù)創(chuàng)建SoftOMXPlugin對象

OMXMaster::OMXMaster()  
    : mVendorLibHandle(NULL) {  
    addVendorPlugin();  
    addPlugin(new SoftOMXPlugin);  
}  

addPlugin()函數(shù)的實現(xiàn)在之前的文章中已經(jīng)給出,主要是將enumerateComponents枚舉出來的各種解碼器名字存放在成員變量mPluginByComponentName中,類型為 KeyedVector。這樣就讀取了kComponents數(shù)組。


3)讀取/etc/media_codec.xml文件,根據(jù)已探測的文件類型獲取需要的解碼器名稱,再對比kComponents得到實際的解碼器

AwesomePlayer構(gòu)造函數(shù)結(jié)束后,在setDataSource之后會調(diào)用prepare方法,其實現(xiàn)中會調(diào)用initAudioDecoder和initVideoDecoder來構(gòu)造解碼器實例。

status_t AwesomePlayer::initVideoDecoder()
{ 
    mVideoSource = OMXCodec::Create(
            mClient.interface(), 
            mVideoTrack->getFormat(), 
            false, 
            mVideoTrack);
}
sp<</span>MediaSource> OMXCodec::Create(*)  
{  
    
        findMatchingCodecs(  
            mime, createEncoder, matchComponentName, flags, &matchingCodecs);  
    
        sp<</span>OMXCodecObserver> observer = new OMXCodecObserver;  
        IOMX::node_id node = 0;  
    
        status_t err = omx->allocateNode(componentName, observer, &node);  
    
        sp<</span>OMXCodec> codec = new OMXCodec(  
                    omx, node, quirks, flags,  
                    createEncoder, mime, componentName,  
                    source, nativeWindow);  
    
        observer->setCodec(codec);  
    
        err = codec->configureCodec(meta);  
    
}  

findMatchingCodecs()的實現(xiàn):

void OMXCodec::findMatchingCodecs(  
        const char *mime,  
        bool createEncoder, const char *matchComponentName,  
        uint32_t flags,  
        Vector<</span>CodecNameAndQuirks> *matchingCodecs) {  
    matchingCodecs->clear();  
    
    const MediaCodecList *list = MediaCodecList::getInstance();  
    if (list == NULL) {  
        return;  
    }  
    
    size_t index = 0;  
    for (;;) {  
        ssize_t matchIndex =  
            list->findCodecByType(mime, createEncoder, index);  
    
        if (matchIndex <</span> 0) {  
            break;  
        }  
    
        index = matchIndex + 1;  
    
        const char *componentName = list->getCodecName(matchIndex);  
    
        // If a specific codec is requested, skip the non-matching ones.  
        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)))) {  
    
            ssize_t index = matchingCodecs->add();  
            CodecNameAndQuirks *entry = &matchingCodecs->editItemAt(index);  
            entry->mName = String8(componentName);  
            entry->mQuirks = getComponentQuirks(list, matchIndex);  
    
            ALOGV("matching '%s' quirks 0xx",  
                  entry->mName.string(), entry->mQuirks);  
        }  
    }  
    
    if (flags & kPreferSoftwareCodecs) {  
        matchingCodecs->sort(CompareSoftwareCodecsFirst);  
    }  
}  

從代碼可以看到主要就是從MediaCodecList找到與傳入的matchComponentName對應(yīng)的解碼器名稱,MediaCodecList就是從/etc/media_codecs.xml解析出支持的解碼器名稱并匹配出對應(yīng)的解碼器名稱。

我的總結(jié)(猜想,不是很明白):

AwesomePlayer首先探測文件格式類型,由文件格式類型可以得到mime和matchComponentName,由mime創(chuàng)建出對應(yīng)的提取器(MediaExtractor);然后讀取支持的格式列表kComponents數(shù)組作為預(yù)備;最后由matchComponentName在MediaCodecList(media_codec.xml)中找到需要的解碼器名稱,根據(jù)這個解碼器名稱到kComponents中查詢并創(chuàng)建實際的解碼器。


于是,可以得出結(jié)論:
要擴展對文件格式的支持,那么這三個地方都要相應(yīng)擴展:

  • 第一,擴展相應(yīng)的提取器(MediaExtractor);
  • 第二,擴展media_codec.xml,matchComponentName在這里邊找到需要的解碼器名稱;
  • 第三,擴展支持的解碼器列表kComponents。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內(nèi)容