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多媒體服務部分:frameworks/base/media/libmediaplayerservice/MediaPlayerService.cpp, StagefrightPlayer.cpp
  5. Stagefright框架:frameworks/base/media/libstagefright/AwesomePlayer.cpp, OMX

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

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

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

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

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

我們具體的來細看每一個步驟:

1) 探測文件類型蚂蕴,根據(jù)文件類型創(chuà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ù)的主要作用就是用于探測文件的類型俯邓,每種類型的媒體文件都對應一個sniffer函數(shù)。這里從代碼可以看出原生的android播放器支持的格式還比較少熔号。

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

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)造對應的extractor對象吩抓。


2)進入/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對應的解碼器名稱,MediaCodecList就是從/etc/media_codecs.xml解析出支持的解碼器名稱并匹配出對應的解碼器名稱倚舀。

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

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


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

  • 第一室琢,擴展相應的提取器(MediaExtractor);
  • 第二落追,擴展media_codec.xml盈滴,matchComponentName在這里邊找到需要的解碼器名稱;
  • 第三,擴展支持的解碼器列表kComponents巢钓。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末病苗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子症汹,更是在濱河造成了極大的恐慌硫朦,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件背镇,死亡現(xiàn)場離奇詭異咬展,居然都是意外死亡,警方通過查閱死者的電腦和手機瞒斩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門破婆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人济瓢,你說我怎么就攤上這事荠割。” “怎么了旺矾?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵蔑鹦,是天一觀的道長。 經(jīng)常有香客問我箕宙,道長嚎朽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任柬帕,我火速辦了婚禮哟忍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陷寝。我一直安慰自己锅很,他們只是感情好,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布凤跑。 她就那樣靜靜地躺著爆安,像睡著了一般。 火紅的嫁衣襯著肌膚如雪仔引。 梳的紋絲不亂的頭發(fā)上扔仓,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機與錄音咖耘,去河邊找鬼翘簇。 笑死,一個胖子當著我的面吹牛儿倒,可吹牛的內(nèi)容都是我干的版保。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼找筝!你這毒婦竟也來了蹈垢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤袖裕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后溉瓶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體急鳄,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年堰酿,在試婚紗的時候發(fā)現(xiàn)自己被綠了疾宏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡触创,死狀恐怖坎藐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哼绑,我是刑警寧澤岩馍,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站抖韩,受9級特大地震影響蛀恩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茂浮,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一双谆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧席揽,春花似錦顽馋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至新翎,卻和暖如春程帕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背地啰。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工愁拭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亏吝。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓岭埠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惜论,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

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