基于 FFmpeg 的自定义 Media Extractor(3):自定义 Extractor 的实现方法

前言

在上一篇文章中,简要介绍了 Extractor 组件选择及创建过程。本文将继续 基于 Android 11 探索自定义 Extractor 的实现,及其接入到 Android 多媒体框架中的方法。

C/NDK API 简介

在上一篇文章中我们知道所有的 extractor 组件都需遵循特定的设计规则:

  1. 实现 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);
  1. 编译到指定路径,且库名称符合 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 APIC++ API
CDataSourceDataSourceHelper
CMediaTrackReadOptionsMediaTrackHelper::ReadOptions
CMediaBufferMediaBufferHelper
CMediaBufferGroupMediaBufferGroupHelper
CMediaTrackMediaTrackHelper
CMediaExtractorMediaExtractorPluginHelper

再次回顾 MP3Extractor.cpp 代码。媒体数据源 CDataSource(如 MP3 文件) 被封装为 DataSourceHelper 对象;MP3ExtractorDataSourceHelper 读取媒体数据,解析媒体信息,并创建 MP3SourceMP3Source 则用于提取媒体文件中的 MP3 音频数据。其中各模块的关系如下:
MP3Extractor 类图
上图中的 MP3ExtractorMP3Source 均为 C++ 对象,我们还需将他们转换为 C/NDK API 中的 CMediaExtractorCMediaTrackMediaExtractorPluginHelper.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 组件的完整创建流程:

  1. 调用 GETEXTRACTORDEF 函数获取 ExtractorDef 对象;
  2. 调用 ExtractorDefv3.sniff 函数检测媒体源,并获取 CreatorFunc 函数指针;
  3. 调用 CreatorFunc 创建派生自 MediaExtractorPluginHelperMP3Extractor 对象,封装为 CMediaExtractor 对象并返回;
  4. MP3Source 则是在调用 CMediaExtractorgetTrack 方法时自动转换为 CMediaTrack

实现自定义 Extractor

通过前面的梳理,我们已经知道了 Extractor 的完成创建流程。本章我们参考 AOSP 中的 MP3Extractor 源码,来实现一个自定义的 Extractor 组件。我们将自定义的 Extractor 类命名为 FakeExtractor,暂不实现实际的功能。梳理类图如下:
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:
FakeExtractor 编译
编译完成后,我们可以看到 Android 源码 out 路径下已生成 libfakeextractor.so 文件:
FakeExtractor so 库查找
将 libfakeextractor.so 文件 push 到 Android 设备上,重启设备,使用 dumpsys media.extractor 指令,可以看到 FakeExtractor 已成功加载[1]
FakeExtractor 验证

参考资料

[1] 自定义媒体组件-创建提取器
[2] Visibility - GCC Wiki

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值