首页 > 其他分享 >基于 FFmpeg 的自定义 Media Extractor(2):自定义 Extractor 的实现方法

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

时间:2024-04-08 21:34:05浏览次数:22  
标签:Extractor return FFmpeg 自定义 media API FakeTrack FakeExtractor data

文章目录

前言

在上一篇文章中,简要介绍了 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

标签:Extractor,return,FFmpeg,自定义,media,API,FakeTrack,FakeExtractor,data
From: https://blog.csdn.net/yumoxj/article/details/137524184

相关文章

  • uniapp 微信小程序自定义tabbar
    为什么要自定义?自定义tabbar可做事件拦截,可自定义样式等。第一步:隐藏原生tabbar第二步:page.json中定义路径 第三步:创建自定义组件目录 代码<template> <viewclass='tabbar'> <view class='tab' v-for="(item,index)intabbarList" :key='index......
  • ffmpeg对视频进行裁减crop
    ffmpeg-i input.mp4 -r 50 -vf crop=800:900:150:200 output.mp4input.mp4:你需要裁减的视频50:裁减之后的视频的帧率crop=800:900:150:200:150:200表示的是从视频的左上角(150,200)这个位置开始对视频进行裁减。其中800表示裁减后的视频的w是800,900表示h。out......
  • Spring Data JPA应用之自定义Repository实现
    在SpringBoot对SpringDataJPA的支持中可以观察到对于数据访问并没有复杂的业务逻辑,可以知道SpringDataJPA提供了代理模式进行处理。跟踪源码可以知道其使用了SimpleJapRepository。那么这个类的有什么特点呢?通......
  • 题目 1035: [编程入门]自定义函数之字符类型统计
    一、题目 题目描述编写一函数,由实参传来一个字符串,统计此字符串中字母、数字、空格和其它字符的个数,在主函数中输入字符串以及输出上述结果。只要结果,别输出什么提示信息。输入格式一行字符串输出格式统计数据,4个数字,空格分开。样例输入!@#$%^QWERT   1234567......
  • 使用未安装的自定义字体
    默认宋体效果:使用程序目录下未安装的自定义字体“华文琥珀”:PrivateSubButton1_Click(senderAsObject,eAsEventArgs)HandlesButton1.ClickDimPFCAsNewDrawing.Text.PrivateFontCollection()PFC.AddFontFile(AppDomain.CurrentDomain.Base......
  • Chromium 自定义缓存策略
    目录CefRequestHandler在什么位置实现我如何将本地资源作为该请求资源返回呢?我怎么缓存网络资源呢,比如图片和视频?CefResourceHandler如何实现缓存图片和视频,缓存时间无限长,设置缓存路径?demoMyResourceHandler在哪里设置?ChromiumEmbeddedFramework(CEF)是一个开源库,用于......
  • 野外监测图传解决方案 l 自定义数据回传最大200倍压缩,天通野外摄像机PS02
    在物联网时代的巨大浪潮中,我们见证了技术的飞速发展和应用的广泛渗透。然而,传统的人工巡检方式在这一进程中显得越来越力不从心,其效率低下和响应迟缓的问题日益凸显。在许多情况下,人工巡检无法实时捕捉到潜在的风险和异常情况,常常是在事故发生后才能察觉,这种滞后性严重制约了......
  • Ascend C 自定义PRelu算子
    本文分享自华为云社区《AscendC自定义PRelu算子》,作者:jackwangcumt。1PRelu算子概述PReLU是ParametricRectifiedLinearUnit的缩写,首次由何凯明团队提出,和LeakyReLU非常类似,是Relu的改进版本,在几乎没有增加额外参数的前提下既可以提升模型的拟合能力,又能减小过拟合风险。......
  • Android11 - 添加自定义服务注意事项
    添加自定义服务注意事项:a:(Android11)快速编译framework.jar./prebuilts/build-tools/linux-x86/bin/ninja-fout/combined-xx.ninjaframework-minus-apexb:在framework/base/core目录下添加文件java和aidl文件后,编译时需要先makeupdate-api去更新current.txt文件,然后才能......
  • csdn博客自定义模块:显示实时天气、日历、随机语录代码
    目录1.样式说明2.效果展示3.代码下载1.样式说明vip会员或者博客专家可以自定义模块代码,比如我博客的样式,有这几部分组成:灯笼祝福(我这里是龙年快乐,可以自定义更改任何字)、滚动欢迎语(我这里是欢迎访问我的博客,可以自定义更改任何欢迎语)github链接、知乎链接、邮箱发......