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

Android Media Framework 框架的层次:

  1. Java层:frameworks/base/media/java/android/media/MediaPlayer.java
  2. JNI本地调用: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中对应的数据结构主要有DataSource, MediaExtractor, MediaSource。其中:

  • DataSource主要负责提供原始数据
  • MediaSource负责提供demux后的数据(即实际的audio 或者 video数据包)
  • MediaExtractor则负责中间的过程,即将从DataSource得到的原始数据解析成解码器需要的es数据,并通过MediaSource的接口输出。

Android本身支持的文件格式有限,硬件厂商有时出于运营策略也会限制部分格式支持,被限制的部分格式需要收费等,于是公司就有自己扩展支持格式的需求。

我们先看AwesomePlayer工作的3个大的步骤:

  1. 探测文件类型,根据文件类型创建对应的提取器(MediaExtractor);
  2. 进入/libstagefright/omx/SoftOMXPlugin.cpp读取kComponents数组,此为现有系统支持的解码器列表;
  3. 读取/etc/media_codec.xml文件,根据已探测的文件类型获取需要的解码器名称,再对比kComponents得到实际的解码器。

我们具体的来细看每一个步骤:

1) 探测文件类型,根据文件类型创建对应的提取器(MediaExtractor)

在AwesomePlayer的构造函数中由DataSource注册sniff探测文件类型:

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

RegisterDefaultSniffers的实现:

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函数将所有的sniffer函数都挂在全局链表gSniffers中。sniffer函数的主要作用就是用于探测文件的类型,每种类型的媒体文件都对应一个sniffer函数。这里从代码可以看出原生的android播放器支持的格式还比较少。

AwesomePlayer中extractor 创建流程:
在setDataSource的最后,会调用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创建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链表中的每种格式的函数调用一遍,选取最高的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函数确定了文件的格式之后,就可以构造对应的extractor对象。


2)进入/libstagefright/omx/SoftOMXPlugin.cpp读取kComponents数组,此为现有系统支持的解码器列表

AwesomePlayer通过OMXClient::connect()得到OMX的实例,在构造OMX对象的过程中又调用了OMXMaster的构造函数创建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构造函数中有addPlugin()函数创建SoftOMXPlugin对象

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

addPlugin()函数的实现在之前的文章中已经给出,主要是将enumerateComponents枚举出来的各种解码器名字存放在成员变量mPluginByComponentName中,类型为 KeyedVector。这样就读取了kComponents数组。


3)读取/etc/media_codec.xml文件,根据已探测的文件类型获取需要的解码器名称,再对比kComponents得到实际的解码器

AwesomePlayer构造函数结束后,在setDataSource之后会调用prepare方法,其实现中会调用initAudioDecoder和initVideoDecoder来构造解码器实例。

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()的实现:

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解析出支持的解码器名称并匹配出对应的解码器名称。

我的总结(猜想,不是很明白):

AwesomePlayer首先探测文件格式类型,由文件格式类型可以得到mime和matchComponentName,由mime创建出对应的提取器(MediaExtractor);然后读取支持的格式列表kComponents数组作为预备;最后由matchComponentName在MediaCodecList(media_codec.xml)中找到需要的解码器名称,根据这个解码器名称到kComponents中查询并创建实际的解码器。


于是,可以得出结论:
要扩展对文件格式的支持,那么这三个地方都要相应扩展:

  • 第一,扩展相应的提取器(MediaExtractor);
  • 第二,扩展media_codec.xml,matchComponentName在这里边找到需要的解码器名称;
  • 第三,扩展支持的解码器列表kComponents。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,820评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,648评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,324评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,714评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,724评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,328评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,897评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,804评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,345评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,431评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,561评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,238评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,928评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,417评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,528评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,983评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,573评论 2 359

推荐阅读更多精彩内容