文章目录
前言
上一篇文章(基于 FFmpeg 的自定义 Media Extractor(1):播放器基本原理及 Extractor 框架简析)中提到,在 Android Multimedia 框架中,Media Extractor 为媒体文件扫描服务(MediaProvider)提供重要的支撑。本文继续 基于 Android 11,将从 MediaProvider 创建 Extractor 的过程展开分析,简述 Extractor 的选择与创建流程。
MediaProvider 创建 Extractor 的过程简析
Android 提供了 MediaMetadataRetriever 类[1],用于从输入媒体文件中检索帧和元数据[2]。MediaProvider正是使用该类解析媒体文件中的信息,如:视频封面、媒体文件时长、专辑、歌手等信息:
- MediaProvider 的 onCreate 函数中,会创建一个 ModernMediaScanner 对象;
- ModernMediaScanner 对象中的 scanItemAudio 或 scanItemvideo 用于扫描音频或视频;
- 扫描时会创建 MediaMetadataRetriever 对象;
- MediaMetadataRetriever 最终会通过 MediaExtractorFactory 的 Create 函数来创建 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 接受两个参数:
- DataSource:提供媒体数据源,在上一章 StagefrightMetadataRetriever.cpp 的例子中,存在本地源和网络源两种情况,分别通过
new PlayerServiceFileSource(fd, offset, length)
和PlayerServiceDataSourceFactory::getInstance()->CreateFromURI(httpService, uri, headers)
创建; - MIME 类型:指定媒体数据的格式,从函数声明中可知默认值为
NULL
。
在函数体中,首先通过 property_get_bool
函数检查系统属性 media.stagefright.extractremote
的值。如果该值为 false,则创建一个本地 extractor;否则,创建一个远程 extractor:
- 本地 extractor:直接调用
CreateFromService
函数创建; - 远程 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;
};
该函数中有两个我们在上篇文章中提到过的符号,ExtractorPlugin
和 CreatorFunc
。回顾上一篇文章 Extractor 加载流程 章节内容,extractor 服务获取特定路径下 so 库中的 GetExtractorDef
结构体对象,封装为 ExtractorPlugin
后注册到链表中,该链表其实是 MediaExtractorFactory
类的静态成员变量。
MediaExtractorFactory
与 ExtractorPlugin
、GetExtractorDef
及 CreatorFunc
的关系如上图。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 插件,从中选出支持被检测媒体源,且置信度最高的插件。
Extractor 的创建
再次回顾 MediaExtractorFactory::CreateFromService 函数,其内部主要为两个步骤:
- sniff 检测媒体文件,并返回 CreatorFunc 函数指针;
- 调用 CreatorFunc 创建 Extractor。
参考资料
[1] MediaMetadataRetriever-Android Developers
[2] Android多媒体框架之MediaMetadataRetriever-CSDN博客