承接第五章中内容的延续:
【五】Android MediaPlayer整体架构源码分析 -【prepareAsync/prepare数据准备处理流程】
本系列文章分析的安卓源码版本:【Android 10.0 版本】
在第五章节第3.1小节分析mediaExService->makeIDataSource(mFd, mOffset, mLength)过程中可知,该方法通过Binder机制调用Bn实现端BnMediaExtractorService子类实现者MediaExtractorService的该方法:
// [frameworks/av/services/mediaextractor/MediaExtractorFactory.cpp]
sp<IDataSource> MediaExtractorService::makeIDataSource(int fd, int64_t offset, int64_t length)
{
sp<DataSource> source = DataSourceFactory::CreateFromFd(fd, offset, length);
return CreateIDataSourceFromDataSource(source);
}
因此需要本章节就分析 媒体提取器【MediaExtractor】的注册实现过程。
首先查看MediaExtractorService该服务的类声明:【省略其他代码】
其实现的是一个Binder服务端即Bn实现端,BinderService将向ServiceManager服务管理中心添加MediaExtractorService自身服务,然后用于Binder客户端去使用即Bp代理端,此处不展开分析,关于Binder机制实现原理请查看:
Android C++底层Binder通信机制原理分析总结【通俗易懂】
// [frameworks/av/services/mediaextractor/MediaExtractorFactory.h]
class MediaExtractorService : public BinderService<MediaExtractorService>, public BnMediaExtractorService {}
MediaExtractorService构造函数初始化:
// [frameworks/av/services/mediaextractor/MediaExtractorFactory.cpp]
MediaExtractorService::MediaExtractorService()
: BnMediaExtractorService() {
// 此处即为加载(注册)媒体提取器处理
MediaExtractorFactory::LoadExtractors();
}
MediaExtractorFactory::LoadExtractors()实现分析:
是一个static方法,加载(注册)平台支持的媒体提取器插件so库。
// [frameworks/av/media/libstagefright/MediaExtractorFactory.cpp]
// static
void MediaExtractorFactory::LoadExtractors() {
// 全局变量锁加锁
Mutex::Autolock autoLock(gPluginMutex);
// 只加载一次
if (gPluginsRegistered) {
return;
}
// 是否忽略版本检查
gIgnoreVersion = property_get_bool("debug.extractor.ignore_version", false);
// 媒体提取器插件列表,初始化为空列表
std::shared_ptr<std::list<sp<ExtractorPlugin>>> newList(new std::list<sp<ExtractorPlugin>>());
// android_get_exported_namespace()在命名空间存在时始终返回此句柄【其他变量不考虑】
android_namespace_t *mediaNs = android_get_exported_namespace("media");
if (mediaNs != NULL) {
// 若media安卓命名空间存在
const android_dlextinfo dlextinfo = {
.flags = ANDROID_DLEXT_USE_NAMESPACE,
.library_namespace = mediaNs,
};
// 加载该命名空间下生成so库(有多个不同文件格式提取器so库插件),so库文件夹地址为:
// /apex/com.android.media/lib/extractors/
// 或 /apex/com.android.media/lib64/extractors/
// 现在的机器默认都加载64位库
// 见第1小节分析
RegisterExtractors("/apex/com.android.media/lib"
#ifdef __LP64__
"64"
#endif
"/extractors", &dlextinfo, *newList);
} else {
ALOGE("couldn't find media namespace.");
}
// 加载该命名空间下生成so库(有多个不同文件格式提取器so库插件),so库文件夹地址为:
// /system/lib/extractors/
// 或 /system/lib64/extractors/
// 现在的机器默认都加载64位库
// 再次加载system目录下的so库
RegisterExtractors("/system/lib"
#ifdef __LP64__
"64"
#endif
"/extractors", NULL, *newList);
// 然后进行排序【按照提取器名称(ASCII码)从小到大排序】
// compareFunc方法实现,见下面的分析
newList->sort(compareFunc);
// 然后缓存在全局变量中
gPlugins = newList;
for (auto it = gPlugins->begin(); it != gPlugins->end(); ++it) {
// For循环来检查每个媒体提取器版本,若是NDK第2版本则如下处理
if ((*it)->def.def_version == EXTRACTORDEF_VERSION_NDK_V2) {
// 再次for循环检查NDK第3版本支持情况即当前媒体提取器支持的文件类型【可以支持很多种类型包括文件扩展类型】
for (size_t i = 0;; i++) {
const char* ext = (*it)->def.u.v3.supported_types[i];
if (ext == nullptr) {
break;
}
// 然后将支持的文件扩展类型全局缓存
gSupportedExtensions.insert(std::string(ext));
}
}
}
// 标记已注册完媒体提取器
gPluginsRegistered = true;
}
compareFunc() 排序比较方法实现:按照提取器名称(ASCII码)从小到大排序
// [frameworks/av/media/libstagefright/MediaExtractorFactory.cpp]
static bool compareFunc(const sp<ExtractorPlugin>& first, const sp<ExtractorPlugin>& second) {
return strcmp(first->def.extractor_name, second->def.extractor_name) < 0;
}
此处附上实际系统中查找这两个so库文件夹目录下的情况,如下:
因此可知,在apex目录下有很多安卓原生的媒体提取器实现so库,而system下面只有一个,并且此处的该库其实不是特定格式的提取器实现模块,而是高通平台自己自定义实现的媒体提取器解复用模块【mm-parser】,属于高通私有库实现。
1、RegisterExtractors() 实现分析:
搜索加载指定目录中的so库(有多个不同文件格式提取器so库插件),现在的机器默认都加载64位库。
// [frameworks/av/media/libstagefright/MediaExtractorFactory.cpp]
//static
void MediaExtractorFactory::RegisterExtractors(
const char *libDirPath, const android_dlextinfo* dlextinfo,
std::list<sp<ExtractorPlugin>> &pluginList) {
ALOGV("search for plugins at %s", libDirPath);
// 尝试打开库文件夹
DIR *libDir = opendir(libDirPath);
if (libDir) {
struct dirent* libEntry;
// 然后循环读取库文件实例
while ((libEntry = readdir(libDir))) {
// 此处跳过.和..开头的目录实例【通过 ls -all 就能看到】
if (libEntry->d_name[0] == '.') {
continue;
}
// 拼接成某个so库文件的全路径名
String8 libPath = String8(libDirPath) + "/" + libEntry->d_name;
// 加载该文件路径是否包含"extractor.so"字符串,false为不包含则处理下个so库文件
if (!libPath.contains("extractor.so")) {
continue;
}
// 打开该so库,返回该so库访问句柄无类型指针,若返回空指针则表示打开失败
void *libHandle = android_dlopen_ext(
libPath.string(),
RTLD_NOW | RTLD_LOCAL, dlextinfo);
CHECK(libHandle != nullptr)
<< "couldn't dlopen(" << libPath.string() << ") " << strerror(errno);
// 获取名为"GETEXTRACTORDEF"的搜索媒体提取器的方法实现,若返回空指针则表示该so库中没有该方法实现
// [dlsym]功能根据动态链接库操作句柄与符号,返回符号对应的地址,不但可以获取函数地址,也可以获取变量地址。
// 即每个媒体提取器so库都应该要实现该方法,然后才能被加载使用。
// 该方法指针声明,见下面的分析
GetExtractorDef getDef =
(GetExtractorDef) dlsym(libHandle, "GETEXTRACTORDEF");
CHECK(getDef != nullptr)
<< libPath.string() << " does not contain sniffer";
ALOGV("registering sniffer for %s", libPath.string());
// 处理单个so库提取器信息
// 见1.1小节分析
RegisterExtractor(
new ExtractorPlugin(getDef(), libHandle, libPath), pluginList);
}
// 关闭打开的库文件夹
closedir(libDir);
} else {
ALOGE("couldn't opendir(%s)", libDirPath);
}
}
GetExtractorDef方法指针声明:
注意:该方法是以C编译器方式编译的。返回一个so库提取器支持情况的ExtractorDef结构(类)。
类和结构的区别其实主要是,结构的成员访问修饰符默认都是public的。
// [frameworks/av/include/media/MediaExtractorPluginApi.h]
// each plugin library exports one function of this type
typedef ExtractorDef (*GetExtractorDef)();
每个媒体提取器so库都应该要实现该方法,然后才能被加载使用。可以在每个提取器so库插件实现代码中看到有如下实现:
刚好对应了上面dlsym获取名称为"GETEXTRACTORDEF"的方法实现。
此处是不是没发现高通模块的定义实现,其实别急,高通私有库模块实现是在另外高通私有路径中定义的,如下
ExtractorDef结构声明:
so库提取器定义信息
// [frameworks/av/include/media/MediaExtractorPluginApi.h]
struct ExtractorDef {
// 当前结构声明版本号
// version number of this structure
const uint32_t def_version;
// 当前名称提取器唯一标识符
// A unique identifier for this extractor.
// See below for a convenience macro to create this from a string.
media_uuid_t extractor_uuid;
// 当前名称提取器版本号,即有可能有两个so库提取器拥有相同唯一标识符(通常是名称相同但路径不同),
// 此时版本号大的将被使用。
// Version number of this extractor. When two extractors with the same
// uuid are encountered, the one with the largest version number will
// be used.
const uint32_t extractor_version;
// 可读的提取器名称
// a human readable name
const char *extractor_name;
union {
struct {
// 一个方法指针
SnifferFunc sniff;
} v2;
struct {
SnifferFunc sniff;
// 提取器支持的文件扩展类型(可以多种类型)
// a NULL terminated list of container mime types and/or file extensions
// that this extractor supports
const char **supported_types;
} v3;
} u;
};
SnifferFunc方法指针声明:
// [frameworks/av/include/media/MediaExtractorPluginApi.h]
// The sniffer can optionally fill in an opaque object, "meta", that helps
// the corresponding extractor initialize its state without duplicating
// effort already exerted by the sniffer. If "freeMeta" is given, it will be
// called against the opaque object when it is no longer used.
typedef CreatorFunc (*SnifferFunc)(
CDataSource *source, float *confidence,
void **meta, FreeMetaFunc *freeMeta);
CDataSource类声明:
主要就是定义了数据源读取操作功能
// [frameworks/av/include/media/MediaExtractorPluginApi.h]
struct CDataSource {
ssize_t (*readAt)(void *handle, off64_t offset, void *data, size_t size);
status_t (*getSize)(void *handle, off64_t *size);
uint32_t (*flags)(void *handle );
bool (*getUri)(void *handle, char *uriString, size_t bufferSize);
void *handle;
};
FreeMetaFunc方法指针声明:
释放内存
// [frameworks/av/include/media/MediaExtractorPluginApi.h]
typedef void (*FreeMetaFunc)(void *meta);
CreatorFunc方法指针声明:
创建返回一个CMediaExtractor类对象指针的方法指针
// [frameworks/av/include/media/MediaExtractorPluginApi.h]
typedef CMediaExtractor* (*CreatorFunc)(CDataSource *source, void *meta);
CMediaExtractor类声明:
该类其实就是每个媒体提取器so库实现功能的操作访问(即都是定义的方法指针)代理类声明【代理设计模式实现,其也相当于是一个统一实现接口,每个实现端必须按此接口功能实现】,调用端可以通过该类来获取数据源的track信息(CMediaTrack),然后通过该track对象来完成控制数据源的读取、解复用处理流程。
以下类声明都定义在[frameworks/av/include/media/MediaExtractorPluginApi.h]文件中。此处一起展示出来。
struct CMediaExtractor {
void *data;
void (*free)(void *data);
size_t (*countTracks)(void *data);
CMediaTrack* (*getTrack)(void *data, size_t index);
media_status_t (*getTrackMetaData)(
void *data,
AMediaFormat *meta,
size_t index, uint32_t flags);
media_status_t (*getMetaData)(void *data, AMediaFormat *meta);
uint32_t (*flags)(void *data);
media_status_t (*setMediaCas)(void *data, const uint8_t* casToken, size_t size);
const char * (*name)(void *data);
};
struct CMediaTrack {
void *data;
void (*free)(void *data);
media_status_t (*start)(void *data, CMediaBufferGroup *bufferGroup);
media_status_t (*stop)(void *data);
media_status_t (*getFormat)(void *data, AMediaFormat *format);
media_status_t (*read)(void *data, CMediaBuffer **buffer, uint32_t options, int64_t seekPosUs);
bool (*supportsNonBlockingRead)(void *data);
};
struct CMediaBufferGroup {
void *handle;
bool (*init)(void *handle, size_t buffers, size_t buffer_size, size_t growthLimit);
void (*add_buffer)(void *handle, size_t size);
media_status_t (*acquire_buffer)(void *handle,
CMediaBuffer **buffer, bool nonBlocking, size_t requestedSize);
bool (*has_buffers)(void *handle);
};
/**
* only use CMediaBuffer allocated from the CMediaBufferGroup that is
* provided to CMediaTrack::start()
*/
struct CMediaBuffer {
void *handle;
void (*release)(void *handle);
void* (*data)(void *handle);
size_t (*size)(void *handle);
size_t (*range_offset)(void *handle);
size_t (*range_length)(void *handle);
void (*set_range)(void *handle, size_t offset, size_t length);
AMediaFormat* (*meta_data)(void *handle);
};
因此通过上面的分析,可以知道,其实硬件产品制造商也可以加入自己实现的特殊媒体文件格式的媒体提取器以防止别人偷窥数据。【当然要实现这一点很难,毕竟是新开发一个文件格式来存储音视频编码数据】
1.1、RegisterExtractor(new ExtractorPlugin(getDef(), libHandle, libPath), pluginList) 实现分析:
处理单个so库提取器信息。
此处调用了getDef()方法即执行了每个提取器so库中对应于GetExtractorDef方法指针的实现,返回了so库提取器定义信息结构对象ExtractorDef。
首先分析new ExtractorPlugin(getDef(), libHandle, libPath),因此ExtractorPlugin媒体提取器插件类声明和定义:
该类是声明和定义一起实现的。该类其实就相同于一个自动管理提取器定义结构对象信息的内部指针释放问题。相当于一个智能指针类似功能。
// [frameworks/av/media/libstagefright/MediaExtractorFactory.cpp]
struct ExtractorPlugin : public RefBase {
ExtractorDef def;
void *libHandle;
String8 libPath;
String8 uuidString;
ExtractorPlugin(ExtractorDef definition, void *handle, String8 &path)
: def(definition), libHandle(handle), libPath(path) {
for (size_t i = 0; i < sizeof ExtractorDef::extractor_uuid; i++) {
// uuid以十六进制表示的字符串存储
// %02x
// x 表示以十六进制形式输出
// 02 表示不足两位,,前面补0输出,如果超过两位,则以实际输出
uuidString.appendFormat("%02x", def.extractor_uuid.b[i]);
}
}
~ExtractorPlugin() {
// 析构函数中关闭打开的so库访问句柄指针
if (libHandle != nullptr) {
ALOGV("closing handle for %s %d", libPath.c_str(), def.extractor_version);
dlclose(libHandle);
}
}
};
RegisterExtractor() 方法实现:【static修饰符修饰的方法】
// [frameworks/av/media/libstagefright/MediaExtractorFactory.cpp]
// static
void MediaExtractorFactory::RegisterExtractor(const sp<ExtractorPlugin> &plugin,
std::list<sp<ExtractorPlugin>> &pluginList) {
// 检查提取器实现版本号,必须是这两个才支持,很旧的就不支持了
// sanity check check struct version, uuid, name
if (plugin->def.def_version != EXTRACTORDEF_VERSION_NDK_V1 &&
plugin->def.def_version != EXTRACTORDEF_VERSION_NDK_V2) {
ALOGE("don't understand extractor format %u, ignoring.", plugin->def.def_version);
return;
}
// 检查uuid是否有效
if (memcmp(&plugin->def.extractor_uuid, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) == 0) {
ALOGE("invalid UUID, ignoring");
return;
}
// 检查名称是否有效
if (plugin->def.extractor_name == NULL || strlen(plugin->def.extractor_name) == 0) {
ALOGE("extractors should have a name, ignoring");
return;
}
// 然后for循环处理已缓存的so库插件信息列表【从头遍历】
for (auto it = pluginList.begin(); it != pluginList.end(); ++it) {
// 比较uuid
// memcmp功能是把两个数据存储地址的前 16 个字节进行比较。相等返回0
if (memcmp(&((*it)->def.extractor_uuid), &plugin->def.extractor_uuid, 16) == 0) {
// there's already an extractor with the same uuid
// 若已缓存了相同uuid的extractor提取器信息,那么将比较提取器版本号,大的将被使用,小的就被丢弃
if (gIgnoreVersion || (*it)->def.extractor_version < plugin->def.extractor_version) {
// this one is newer, replace the old one
ALOGW("replacing extractor '%s' version %u with version %u",
plugin->def.extractor_name,
(*it)->def.extractor_version,
plugin->def.extractor_version);
pluginList.erase(it);
break;
} else {
ALOGW("ignoring extractor '%s' version %u in favor of version %u",
plugin->def.extractor_name,
plugin->def.extractor_version,
(*it)->def.extractor_version);
return;
}
}
}
// 往插件列表缓存中添加新的插件信息对象
ALOGV("registering extractor for %s", plugin->def.extractor_name);
pluginList.push_back(plugin);
}
2、关于每个so库提取器具体如何实现GetExtractorDef方法指针,这里只列举一下高通该功能的实现,其他so库实现方式都是类似的。
由于篇幅长度原因,放入下一章节分析,请查看:
Android底层音视频播放媒体提取器【MediaExtractor】的解复用模块demuxers模块化加载和注册流程实现源码分析【Part 2】