基于 FFmpeg 的自定义 Media Extractor(2):Extractor 的选择与创建

前言

上一篇文章(基于 FFmpeg 的自定义 Media Extractor(1):播放器基本原理及 Extractor 框架简析)中提到,在 Android Multimedia 框架中,Media Extractor 为媒体文件扫描服务(MediaProvider)提供重要的支撑。本文继续 基于 Android 11,将从 MediaProvider 创建 Extractor 的过程展开分析,简述 Extractor 的选择与创建流程。

MediaProvider 创建 Extractor 的过程简析

Android 提供了 MediaMetadataRetriever 类[1],用于从输入媒体文件中检索帧和元数据[2]MediaProvider正是使用该类解析媒体文件中的信息,如:视频封面、媒体文件时长、专辑、歌手等信息:

  1. MediaProvider 的 onCreate 函数中,会创建一个 ModernMediaScanner 对象;
  2. ModernMediaScanner 对象中的 scanItemAudio 或 scanItemvideo 用于扫描音频或视频;
  3. 扫描时会创建 MediaMetadataRetriever 对象;
  4. MediaMetadataRetriever 最终会通过 MediaExtractorFactory 的 Create 函数来创建 Extractor。

大致流程如下图:
MediaProvider 创建 Extractor 流程
更多细节可点击下列链接,阅读 AOSP 源码,本文不过多阐述:
MediaProvider.java
ModernMediaScanner.java
MediaMetadataRetriever.java
android_media_MediaMetadataRetriever.cpp
mediametadataretriever.cpp
MediaPlayerService.cpp
MetadataRetrieverClient.cpp
StagefrightMetadataRetriever.cpp
MediaExtractorFactory.cpp

MediaExtractorFactory 简析

通过前一章节对 MediaProvider 调用 Extractor 过程的简单梳理,我们已经知道 MediaExtractorFactory::Create 函数为创建 Extractor 的入口,该函数代码如下:

sp<IMediaExtractor> MediaExtractorFactory::Create(
        const sp<DataSource> &source, const char *mime) {
    ALOGV("MediaExtractorFactory::Create %s", mime);

    if (!property_get_bool("media.stagefright.extractremote", true)) {
        // 1. 在调用进程中创建 extractor
        ALOGW("creating media extractor in calling process");
        return CreateFromService(source, mime);
    } else {
        // 2. 创建远程 extractor
        ALOGV("get service manager");
        sp<IBinder> binder = defaultServiceManager()->getService(String16("media.extractor"));

        if (binder != 0) {
            sp<IMediaExtractorService> mediaExService(
                    interface_cast<IMediaExtractorService>(binder));
            sp<IMediaExtractor> ex;
            mediaExService->makeExtractor(
                    CreateIDataSourceFromDataSource(source),
                    mime ? std::make_unique<std::string>(mime) : nullptr,
                    &ex);
            return ex;
        } else {
            ALOGE("extractor service not running");
            return NULL;
        }
    }
    return NULL;
}

MediaExtractorFactory::Create 接受两个参数:

  1. DataSource:提供媒体数据源,在上一章 StagefrightMetadataRetriever.cpp 的例子中,存在本地源和网络源两种情况,分别通过 new PlayerServiceFileSource(fd, offset, length)PlayerServiceDataSourceFactory::getInstance()->CreateFromURI(httpService, uri, headers) 创建;
  2. MIME 类型:指定媒体数据的格式,从函数声明中可知默认值为 NULL

在函数体中,首先通过 property_get_bool 函数检查系统属性 media.stagefright.extractremote 的值。如果该值为 false,则创建一个本地 extractor;否则,创建一个远程 extractor:

  1. 本地 extractor:直接调用 CreateFromService 函数创建;
  2. 远程 extractor:通过 extractor 服务的 makeExtractor 函数创建。

系统默认为创建远程 extractor,参考官方文档:媒体框架强化

MediaExtractorFactory::Create 接受的 sp<DataSource> 参数是一个本地对象,无法传递到 extractor 服务端,需要将 sp<DataSource> 对象封装为 IBinder 对象,这个过程通过 CreateIDataSourceFromDataSource 函数实现:

sp<IDataSource> CreateIDataSourceFromDataSource(const sp<DataSource> &source) {
    if (source == nullptr) {
        return nullptr;
    }
    return RemoteDataSource::wrap(source);
}

class RemoteDataSource : public BnDataSource {
    ......
}

继续分析 extractor 服务端代码 MediaExtractorService.cpp 中 makeExtractor 函数:

::android::binder::Status MediaExtractorService::makeExtractor(
        const ::android::sp<::android::IDataSource>& remoteSource,
        const ::std::unique_ptr< ::std::string> &mime,
        ::android::sp<::android::IMediaExtractor>* _aidl_return) {
    ALOGV("@@@ MediaExtractorService::makeExtractor for %s", mime.get()->c_str());

    // 1. 通过 CreateDataSourceFromIDataSource 函数将 IDataSource 对象封装为 DataSource 本地对象
    sp<DataSource> localSource = CreateDataSourceFromIDataSource(remoteSource);

    MediaBuffer::useSharedMemory();
    // 2. 调用 CreateFromService 函数创建 extractor
    sp<IMediaExtractor> extractor = MediaExtractorFactory::CreateFromService(
            localSource,
            mime.get() ? mime.get()->c_str() : nullptr);

    ALOGV("extractor service created %p (%s)",
            extractor.get(),
            extractor == nullptr ? "" : extractor->name());

    if (extractor != nullptr) {
        registerMediaExtractor(extractor, localSource, mime.get() ? mime.get()->c_str() : nullptr);
    }
    
    // 3. 将创建的 extractor 赋值给 _aidl_return 返回给 client 端
    *_aidl_return = extractor;
    return binder::Status::ok();
}

通过上述分析,我们发现不论是创建本地还是远程 extractor,最终调用的都是 MediaExtractorFactory::CreateFromService 函数,回到 MediaExtractorFactory.cpp 中,继续分析 CreateFromService 函数:

sp<IMediaExtractor> MediaExtractorFactory::CreateFromService(
        const sp<DataSource> &source, const char *mime) {
    void *meta = nullptr;
    void *creator = NULL;
    FreeMetaFunc freeMeta = nullptr;
    float confidence;
    sp<ExtractorPlugin> plugin;
    uint32_t creatorVersion = 0;
    // 1. 通过 sniff 函数检测媒体数据源 source,返回 CreatorFunc 函数指针
    creator = sniff(source, &confidence, &meta, &freeMeta, plugin, &creatorVersion);
    
    ......

    MediaExtractor *ex = nullptr;
    if (creatorVersion == EXTRACTORDEF_VERSION_NDK_V1 ||
            creatorVersion == EXTRACTORDEF_VERSION_NDK_V2) {
        // 2. 调用 CreatorFunc 函数 extractor
        CMediaExtractor *ret = ((CreatorFunc)creator)(source->wrap(), meta);
        ......
        
        // 3. 将 CMediaExtractor 封装为 MediaExtractor(C++ 对象)
        ex = ret != nullptr ? new MediaExtractorCUnwrapper(ret) : nullptr;
    }

    ALOGV("Created an extractor '%s' with confidence %.2f",
         ex != nullptr ? ex->name() : "<null>", confidence);

    // 4. 将 MediaExtractor 封装为 IBinder 对象,在前面分析的 makeExtractor 函数中通过 _aidl_return 返回给 client 端
    return CreateIMediaExtractorFromMediaExtractor(ex, source, plugin);
}

class MediaExtractorCUnwrapper : public MediaExtractor {
    ......
private:
    CMediaExtractor *plugin;
};

该函数中有两个我们在上篇文章中提到过的符号,ExtractorPluginCreatorFunc。回顾上一篇文章 Extractor 加载流程 章节内容,extractor 服务获取特定路径下 so 库中的 GetExtractorDef 结构体对象,封装为 ExtractorPlugin 后注册到链表中,该链表其实是 MediaExtractorFactory 类的静态成员变量。
MediaExtractor 类图
MediaExtractorFactoryExtractorPluginGetExtractorDefCreatorFunc 的关系如上图。sniff 的过程,即遍历 ExtractorPlugin 链表,找到最合适的 ExtractorPlugin 对象,从而匹配到用于创建 extractor 的最佳 CreatorFunc

Sniff 过程和 Extractor 的选择

MediaExtractorFactory::sniff 检测媒体源是否被支持,并寻找最适合该媒体源的 extractor 插件。sniff 函数接受 6 个参数:

const sp &source: 需检测的媒体数据源
float *confidence:存储 extractor 插件返回的置信度
void **meta:存储 extractor 插件的元数据
FreeMetaFunc *freeMeta:存储释放 extractor 元数据的函数
sp &plugin:存储插件对象
uint32_t *creatorVersion: 存储版本信息

void *MediaExtractorFactory::sniff(
        const sp<DataSource> &source,
        float *confidence,
        void **meta,
        FreeMetaFunc *freeMeta,
        sp<ExtractorPlugin> &plugin,
        uint32_t *creatorVersion) {

    // 初始化置信度为 0.0f,元数据为 nullptr
    *confidence = 0.0f;
    *meta = nullptr;

    // 获取插件列表
    std::shared_ptr<std::list<sp<ExtractorPlugin>>> plugins;
    {
        Mutex::Autolock autoLock(gPluginMutex);
        if (!gPluginsRegistered) {
            return NULL;
        }
        plugins = gPlugins;
    }

    void *bestCreator = NULL;

    // 遍历所有的插件
    for (auto it = plugins->begin(); it != plugins->end(); ++it) {
        ALOGV("sniffing %s", (*it)->def.extractor_name);
        float newConfidence;
        void *newMeta = nullptr;
        FreeMetaFunc newFreeMeta = nullptr;

        void *curCreator = NULL;

        // 根据插件的版本调用相应的 sniff 方法,检测媒体源是否被当前插件支持
        if ((*it)->def.def_version == EXTRACTORDEF_VERSION_NDK_V1) {
            curCreator = (void*) (*it)->def.u.v2.sniff(
                    source->wrap(), &newConfidence, &newMeta, &newFreeMeta);
        } else if ((*it)->def.def_version == EXTRACTORDEF_VERSION_NDK_V2) {
            curCreator = (void*) (*it)->def.u.v3.sniff(
                    source->wrap(), &newConfidence, &newMeta, &newFreeMeta);
        }

        // 如果检测成功,更新置信度、元数据、释放元数据的函数、插件、最佳的 
        // CreatorFunc 及其版本
        if (curCreator) {
            if (newConfidence > *confidence) {
                *confidence = newConfidence;
                if (*meta != nullptr && *freeMeta != nullptr) {
                    (*freeMeta)(*meta);
                }
                *meta = newMeta;
                *freeMeta = newFreeMeta;
                plugin = *it;
                bestCreator = curCreator;
                *creatorVersion = (*it)->def.def_version;
            } else {
                if (newMeta != nullptr && newFreeMeta != nullptr) {
                    newFreeMeta(newMeta);
                }
            }
        }
    }

    // 返回最佳的 CreatorFunc
    return bestCreator;
}

上述代码 (*it)->def.u.v2[3].sniff(source->wrap(), &newConfidence, &newMeta, &newFreeMeta) 最终会调用到每个 extractor 插件库中定义的 sniff 函数,以 Android 原生的 MP3Extractor 为例,会调用到 MP3Extractor.cpp 文件中的 Sniff 方法:

static CreatorFunc Sniff(
        CDataSource *source, float *confidence, void **meta,
        FreeMetaFunc *freeMeta) {
    ......

    Mp3Meta *mp3Meta = new Mp3Meta;
    mp3Meta->pos = pos;
    mp3Meta->header = header;
    mp3Meta->post_id3_pos = post_id3_pos;
    *meta = mp3Meta;
    *freeMeta = ::free;

    *confidence = 0.2f;

    return CreateExtractor;
}

extern "C" {
// This is the only symbol that needs to be exported
__attribute__ ((visibility ("default")))
ExtractorDef GETEXTRACTORDEF() {
    return {
        EXTRACTORDEF_VERSION,
        UUID("812a3f6c-c8cf-46de-b529-3774b14103d4"),
        1, // version
        "MP3 Extractor",
        { .v3 = {Sniff, extensions} }
    };
}

综上所述,其实 sniff 过程即为 extractor 选择的过程。通过遍历每个 extractor 插件,从中选出支持被检测媒体源,且置信度最高的插件。
Sniff 流程

Extractor 的创建

再次回顾 MediaExtractorFactory::CreateFromService 函数,其内部主要为两个步骤:

  1. sniff 检测媒体文件,并返回 CreatorFunc 函数指针;
  2. 调用 CreatorFunc 创建 Extractor。
    Extractor 创建

参考资料

[1] MediaMetadataRetriever-Android Developers
[2] Android多媒体框架之MediaMetadataRetriever-CSDN博客

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值