文章目录
前言
在上一篇文章中,简要介绍了 Extractor 组件选择及创建过程。本文将继续 基于 Android 11 探索自定义 Extractor 的实现,及其接入到 Android 多媒体框架中的方法。
C/NDK API 简介
在上一篇文章中我们知道所有的 extractor 组件都需遵循特定的设计规则:
- 实现
GETEXTRACTORDEF
函数,该函数由MediaExtractorFactory::RegisterExtractors
调用;
GetExtractorDef getDef =
(GetExtractorDef) dlsym(libHandle, "GETEXTRACTORDEF");
CHECK(getDef != nullptr)
<< libPath.string() << " does not contain sniffer";
ALOGV("registering sniffer for %s", libPath.string());
RegisterExtractor(
new ExtractorPlugin(getDef(), libHandle, libPath), pluginList);
- 编译到指定路径,且库名称符合
lib[xxx]extractor.so
形式,如原生的 MP3Extractor 库;
/apex/com.android.media/lib64/extractors/libmp3extractor.so
回顾 MP3Extractor.cpp 代码,定义的 GETEXTRACTORDEF
函数如下:
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} }
};
}
该函数返回 ExtractorDef
对象,该对象包含 MP3Extractor 组件的版本、名称、uuid 、支持的格式(extensions)等基本信息,以及 Sniff
函数指针。Sniff
函数用于检测是否支持输入的媒体源,并返回置信度(confidence),以及用于创建 CMediaExtractor
对象的 CreatorFunc
函数指针。这些接口均定义在 MediaExtractorPluginApi.h 文件中:
- CDataSource 结构体:包含用于读取媒体数据的函数指针;
- CMediaTrack 结构体:定义了媒体轨道的操作,如开始、停止和读取;
- CMediaExtractor 结构体:提供了获取媒体轨道和元数据的方法;
- CMediaBuffer 结构体:数据缓冲区,CMediaTrack 的 read 函数中,则使用该结构体返回提取的音/视频数据;
- CMediaBufferGroup 结构体:辅助管理 CMediaBuffer 的初始化、申请及释放;
- ExtractorDef 结构体:包含了插件的版本号、唯一标识符和支持的类型等信息;
- CreatorFunc 函数指针:用于创建 CMediaExtractor 实例;
- 版本控制:定义了不同版本的 API,包括旧版 C++ API 和新版 C/NDK API。
// the C++ based API which first shipped in P and is no longer supported
const uint32_t EXTRACTORDEF_VERSION_LEGACY = 1;
// the first C/NDK based API
const uint32_t EXTRACTORDEF_VERSION_NDK_V1 = 2;
// the second C/NDK based API
const uint32_t EXTRACTORDEF_VERSION_NDK_V2 = 3;
Android 10 或更高版本仅支持 API 的最高版本[1],本文不考虑 EXTRACTORDEF_VERSION_LEGACY
版本情况。
C++ API 简介
从上一章节,我们已经了解 Extractor 组件的 C/NDK API。直接使用 C API 显然不太方便,因此,官方提供了 C++ API 来辅助我们实现自定义 Extractor。这些 C++ API 定义在 MediaExtractorPluginHelper.h 文件中,在 MediaExtractorPluginApi.h 定义的 C/NDK API 和该文件中定义的 C++ API 存在以下映射关系:
C/NDK API | C++ API |
---|---|
CDataSource | DataSourceHelper |
CMediaTrackReadOptions | MediaTrackHelper::ReadOptions |
CMediaBuffer | MediaBufferHelper |
CMediaBufferGroup | MediaBufferGroupHelper |
CMediaTrack | MediaTrackHelper |
CMediaExtractor | MediaExtractorPluginHelper |
再次回顾 MP3Extractor.cpp 代码。媒体数据源 CDataSource(如 MP3 文件) 被封装为 DataSourceHelper
对象;MP3Extractor
从 DataSourceHelper
读取媒体数据,解析媒体信息,并创建 MP3Source
;MP3Source
则用于提取媒体文件中的 MP3 音频数据。其中各模块的关系如下:
上图中的 MP3Extractor
和 MP3Source
均为 C++ 对象,我们还需将他们转换为 C/NDK API 中的 CMediaExtractor
和 CMediaTrack
。MediaExtractorPluginHelper.h 文件中已提供了相应的 API:
// 将 MediaExtractorPluginHelper 封装为 CMediaExtractor
inline CMediaExtractor *wrap(MediaExtractorPluginHelper *extractor) {
CMediaExtractor *wrapper = (CMediaExtractor*) malloc(sizeof(CMediaExtractor));
wrapper->data = extractor;
wrapper->free = [](void *data) -> void {
delete (MediaExtractorPluginHelper*)(data);
};
wrapper->countTracks = [](void *data) -> size_t {
return ((MediaExtractorPluginHelper*)data)->countTracks();
};
wrapper->getTrack = [](void *data, size_t index) -> CMediaTrack* {
// 此处调用 inline CMediaTrack *wrap(MediaTrackHelper *track) ,将 MediaTrackHelper 对象封装为 CMediaTrack
return wrap(((MediaExtractorPluginHelper*)data)->getTrack(index));
};
......
return wrapper;
}
// 将 MediaTrackHelper 封装为 CMediaTrack
inline CMediaTrack *wrap(MediaTrackHelper *track) {
if (track == nullptr) {
return nullptr;
}
CMediaTrack *wrapper = (CMediaTrack*) malloc(sizeof(CMediaTrack));
wrapper->data = track;
wrapper->free = [](void *data) -> void {
delete (MediaTrackHelper*)(data);
};
wrapper->start = [](void *data, CMediaBufferGroup *bufferGroup) -> media_status_t {
if (((MediaTrackHelper*)data)->mBufferGroup) {
// this shouldn't happen, but handle it anyway
delete ((MediaTrackHelper*)data)->mBufferGroup;
}
((MediaTrackHelper*)data)->mBufferGroup = new MediaBufferGroupHelper(bufferGroup);
return ((MediaTrackHelper*)data)->start();
};
......
wrapper->read = [](void *data, CMediaBuffer **buffer, uint32_t options, int64_t seekPosUs)
-> media_status_t {
MediaTrackHelper::ReadOptions opts(options, seekPosUs);
MediaBufferHelper *buf = NULL;
media_status_t ret = ((MediaTrackHelper*)data)->read(&buf, &opts);
if (ret == AMEDIA_OK && buf != nullptr) {
*buffer = buf->mBuffer;
}
return ret;
};
......
return wrapper;
}
inline CMediaExtractor *wrap(MediaExtractorPluginHelper *extractor)
被 CreateExtractor
(C/NDK API 定义的 CreatorFunc
函数指针) 调用:
static CMediaExtractor* CreateExtractor(
CDataSource *source,
void *meta) {
Mp3Meta *metaData = static_cast<Mp3Meta *>(meta);
return wrap(new MP3Extractor(new DataSourceHelper(source), metaData));
}
至此,我们已经知道了 MP3Extractor 组件的完整创建流程:
- 调用
GETEXTRACTORDEF
函数获取ExtractorDef
对象; - 调用
ExtractorDef
的v3.sniff
函数检测媒体源,并获取CreatorFunc
函数指针; - 调用
CreatorFunc
创建派生自MediaExtractorPluginHelper
的MP3Extractor
对象,封装为CMediaExtractor
对象并返回; MP3Source
则是在调用CMediaExtractor
的getTrack
方法时自动转换为CMediaTrack
。
实现自定义 Extractor
通过前面的梳理,我们已经知道了 Extractor 的完成创建流程。本章我们参考 AOSP 中的 MP3Extractor 源码,来实现一个自定义的 Extractor 组件。我们将自定义的 Extractor 类命名为 FakeExtractor
,暂不实现实际的功能。梳理类图如下:
创建 FakeExtractor.h 头文件:
#ifndef FAKE_EXTRACTOR_H_
#define FAKE_EXTRACTOR_H_
#include <utils/Errors.h>
#include <media/MediaExtractorPluginApi.h>
#include <media/MediaExtractorPluginHelper.h>
#include <media/NdkMediaFormat.h>
namespace android {
class DataSourceHelper;
class FakeExtractor : public MediaExtractorPluginHelper {
public:
FakeExtractor(CDataSource *source, void *meta);
~FakeExtractor();
size_t countTracks() override;
MediaTrackHelper *getTrack(size_t index) override;
media_status_t getTrackMetaData(AMediaFormat *meta,
size_t index, uint32_t flags) override;
media_status_t getMetaData(AMediaFormat *meta) override;
const char * name() override { return "FakeExtractor"; };
private:
FakeExtractor(const FakeExtractor &);
FakeExtractor &operator=(const FakeExtractor &);
};
} // namespace android
#endif // FAKE_EXTRACTOR_H_
创建 FakeExtractor.cpp 文件:
#include "FakeExtractor.h"
#include <media/stagefright/MediaDefs.h>
namespace android {
class FakeTrack : public MediaTrackHelper
{
public:
FakeTrack(FakeExtractor *extractor, size_t index);
media_status_t start() override;
media_status_t stop() override;
media_status_t getFormat(AMediaFormat *meta) override;
media_status_t read(MediaBufferHelper **buffer,
const ReadOptions *options = NULL) override;
protected:
~FakeTrack();
private:
FakeTrack(const FakeTrack &);
FakeTrack &operator=(const FakeTrack &);
};
FakeTrack::FakeTrack(FakeExtractor *extractor, size_t index)
{
}
FakeTrack::~FakeTrack()
{
}
media_status_t FakeTrack::start()
{
return AMEDIA_OK;
}
media_status_t FakeTrack::stop()
{
return AMEDIA_OK;
}
media_status_t FakeTrack::getFormat(AMediaFormat *meta)
{
return AMEDIA_OK;
}
media_status_t FakeTrack::read(
MediaBufferHelper **out, const ReadOptions *options)
{
return AMEDIA_OK;
}
// ################################# FakeTrack end ##################################
// ################################# FakeExtractor begin ##################################
FakeExtractor::FakeExtractor(CDataSource *source, void *meta)
{
}
FakeExtractor::~FakeExtractor()
{
}
size_t FakeExtractor::countTracks()
{
return 0;
}
MediaTrackHelper *FakeExtractor::getTrack(size_t index)
{
return new FakeTrack(this, index);
}
media_status_t FakeExtractor::getTrackMetaData(
AMediaFormat *meta,
size_t index, uint32_t /* flags */)
{
return AMEDIA_OK;
}
media_status_t FakeExtractor::getMetaData(AMediaFormat *meta)
{
return AMEDIA_OK;
}
// ################################# FakeExtractor end ##################################
static CMediaExtractor* CreateExtractor(
CDataSource *source,
void *meta)
{
return wrap(new FakeExtractor(source, meta));
}
static CreatorFunc Sniff(
CDataSource *source, float *confidence, void **meta,
FreeMetaFunc *freeMeta)
{
float newConfidence = 0.08f;
*confidence = newConfidence;
return CreateExtractor;
}
static const char *extensions[] = {
"fake",
NULL
};
extern "C" {
// This is the only symbol that needs to be exported
__attribute__ ((visibility ("default")))
ExtractorDef GETEXTRACTORDEF()
{
return {
EXTRACTORDEF_VERSION,
UUID("7d613858-1234-4a38-84c5-332d1cddee27"),
1, // version
"Fake Extractor",
{ .v3 = {Sniff, extensions} }
};
}
} // extern "C"
} // namespace android
创建 Android.bp 文件:
cc_library {
name: "libfakeextractor",
relative_install_path: "extractors",
cflags: [
"-fvisibility=hidden",
"-Wno-unused-parameter",
],
srcs: [
"FakeExtractor.cpp",
],
shared_libs: [
"libcutils",
"libmediandk",
"libstagefright_foundation",
],
}
-fvisibility=hidden
参数:隐藏 so 库中的所有符号,以提高运行效率和链接效率。在 FakeExtractor.cpp 文件中,我们在ExtractorDef GETEXTRACTORDEF()
函数前加上__attribute__((visibility("default")))
参数,以使得GETEXTRACTORDEF
函数可以被找到[2] ;- relative_install_path: “extractors”:指定 so 库的安装路径为 /system/lib[64]/extractors/。
编译自定义 Extractor
将上一章节创建的资源文件放置在同一目录下,如:vendor/qcom/proprietary/FakeExtractor,进入该目录执行 mma:
编译完成后,我们可以看到 Android 源码 out 路径下已生成 libfakeextractor.so 文件:
将 libfakeextractor.so 文件 push 到 Android 设备上,重启设备,使用 dumpsys media.extractor 指令,可以看到 FakeExtractor 已成功加载[1]:
参考资料
[1] 自定义媒体组件-创建提取器
[2] Visibility - GCC Wiki