[经验分享] 拆·应用 第九期:基于FFmpeg解封装WMV和M4V格式

开源鸿蒙知行录 显示全部楼层 发表于 2026-1-22 16:51:59

引言 开源鸿蒙具备多格式音视频播放能力,其播放器需依次完成解协议、解封装、解码、渲染四大核心步骤,方可将音视频内容完整呈现给用户;其中,解封装作为衔接协议解析与音视频解码的关键环节,具有极高的研究价值;本文将以扩展 WMV、M4V 格式解封装能力为实践切入点,带大家深入熟悉开源鸿蒙的多媒体框架。

开源鸿蒙多媒体解封装框架

  1. FFmpeg层扩展M4V、WMV的解封装能力
  • 步骤1:修改 FFmpeg ohos_config.sh配置,打开M4V、WMV的解封装能力。需要注意:M4V与MOV封装协议一致,WMV与ASF封装格式一致。

    --enable-demuxer=mov,asf
  • 步骤2:修改BUILD.gn,配置模块增加ASF宏定义。

"-DCONFIG_ASF_DEMUXER", "-DCONFIG_MOV_DEMUXER",
  • 步骤3: 查看libavformat模块MakeFile文件,确认M4V和WMV解封装涉及的文件。
OBJS-$(CONFIGMOVDEMUXER) += mov.o mov_chan.o mov_esds.o qtpalette.o replaygain.o dovi_isom.oOBJS-$(CONFIGASFDEMUXER) += asfdec_f.o asf.o asfcrypt.o  asf_tags.o avlanguage.o
  • 步骤4:打开 BUILD.gn中增加WMV和M4V的文件编译。
"//third_party/ffmpeg/libavformat/dovi_isom.c", "//third_party/ffmpeg/libavformat/mov.c",
"//third_party/ffmpeg/libavformat/mov_chan.c", "//third_party/ffmpeg/libavformat/mov_esds.c",
"//third_party/ffmpeg/libavformat/movenc.c", "//third_party/ffmpeg/libavformat/qtpalette.c",
"//third_party/ffmpeg/libavformat/replaygain.c", "//third_party/ffmpeg/libavformat/asf.c",
"//third_party/ffmpeg/libavformat/asfcrypt.c", "//third_party/ffmpeg/libavformat/asfdec_f.c",
"//third_party/ffmpeg/libavformat/asfdec_o.c",
  1. 多媒体框架层扩展WMV、M4V解封装能力
  • 步骤1:修改 PluginList,注册WMV、M4V解封装插件描述信息。
void PluginList::AddMovDemuxerPlugin()
{
    PluginDescription movDemuxerPlugin;
    movDemuxerPlugin.pluginName = "avdemux_mov,mp4,m4a,3gp,3g2,mj2";
    movDemuxerPlugin.packageName = "FFmpegDemuxer";
    movDemuxerPlugin.pluginType = PluginType::DEMUXER;
    movDemuxerPlugin.cap = "";
    movDemuxerPlugin.rank = DEFAULT_RANK;
    pluginDescriptionList_.push_back(movDemuxerPlugin);
}

void PluginList::AddAsfDemuxerPlugin()
{
    PluginDescription asfDemuxerPlugin;
    asfDemuxerPlugin.pluginName = "avdemux_asf";
    asfDemuxerPlugin.packageName = "FFmpegDemuxer";
    asfDemuxerPlugin.pluginType = PluginType::DEMUXER;
    asfDemuxerPlugin.cap = "";
    asfDemuxerPlugin.rank = DEFAULT_RANK;
    pluginDescriptionList_.push_back(asfDemuxerPlugin);
}
  • 步骤2:修改 MediaType, 定义M4V和WMV的 MediaType类型。
enum class FileType : int32_t {   
    M4V = 111,   
    WMV = 112
}
  • 步骤3:修改 FFmpegFormatHelper,完善和M4V、WMV的探测逻辑。
FileType FFmpegFormatHelper::GetFileTypeByName(const AVFormatContext& avFormatContext)
{
    FALSE_RETURN_V_MSG_E(avFormatContext.iformat != nullptr, FileType::UNKNOW, "Iformat is nullptr");
    const char* fileName = avFormatContext.iformat->name;
    FileType fileType = FileType::UNKNOW;
    if (StartWith(fileName, "mov,mp4,m4a"))
    {
        if (StartWith(type->value, "m4v") || StartWith(type->value, "M4V")) { fileType = FileType::M4V; }
    }
    else { if (g_convertFfmpegFileType.count(fileName) != 0) { fileType = g_convertFfmpegFileType[fileName]; } }
    return fileType;
}
  • 步骤4:修改 MediaFileUtils,扩展图库支持M4V、WMV格式的显示。
static const std::unordered_map<std::string, std::vectorstd::string> MEDIA_MIME_TYPE_MAP = {
    {"video/x-ms-asf", {"asf", "asx"}}, 
    {"video/mp4", {"m4v", "f4v", "mp4v", "mpeg4", "mp4"}},
}

解封装性能优化

  1. 插件加载优化 插件是以 so的形式存储到本地,起播时需要加载注册解封装插件,对插件做Cache处理,已加载的插件直接从内存中获取,防止二次加载插件耗时。

    std::shared_ptr<PluginDefBase>CachedPluginPackage::GetPluginDef(PluginDescriptionpluginDescription)
     {
     AutoLocklock(pluginMutex);
     std::vector<std::shared_ptr<PluginPackage>>::iterator itPluginPackage;
     for (itPluginPackage = pluginPackageList_.begin();
         itPluginPackage != pluginPackageList_.end(); itPluginPackage++) {
         if (*itPluginPackage == nullptr) {
             return nullptr;
         }
         std::vector<std::shared_ptr<PluginDefBase>> pluginDefList = (*itPluginPackage)->GetAllPlugins();
         std::vector<std::shared_ptr<PluginDefBase>>::iterator itPluginDef;
         for (itPluginDef = pluginDefList.begin(); itPluginDef != pluginDefList.end(); itPluginDef++) {
             if (*itPluginDef == nullptr) {
                 return nullptr;
             }
             if (strcmp((*itPluginDef)->name.c_str(), pluginDescription.pluginName.c_str()) == 0) {
                 return (*itPluginDef);
             }
         }
     }
     std::shared_ptr<PluginPackage> pluginPackage = std::make_shared<PluginPackage>();
     bool ret = pluginPackage->LoadPluginPackage(pluginDescription.packageName);
     if (!ret) {
         return nullptr;
     }
     pluginPackageList_.push_back(pluginPackage);
     std::vector<std::shared_ptr<PluginDefBase>> pluginDefList = pluginPackage->GetAllPlugins();
     std::vector<std::shared_ptr<PluginDefBase>>::iterator itPluginDef;
     for (itPluginDef = pluginDefList.begin(); itPluginDef != pluginDefList.end(); itPluginDef++) {
         if (*itPluginDef == nullptr) {
             return nullptr;
         }
         if (strcmp((*itPluginDef)->name.c_str(), pluginDescription.pluginName.c_str()) == 0) {
             return (*itPluginDef);
         }
     }
    return nullptr;
     }
  2. 探测遍历优化 文件格式探测时,会遍历所有的 demuxer,找出评分最高的 demuxerFFmpeg本身支持的 demuxer比较多,限定探索范围为 PluginList中已注册的 demuxer, 可以加快起播速度。

    std::string PluginManagerV2::SnifferPlugin(PluginType pluginType, std::shared_ptrdataSource)
     {
     MEDIA_LOG_I("SnifferPlugin pluginType: " PUBLIC_LOG_D32, pluginType);
     std::vectormatchedPluginsDescriptions =
         PluginList::GetInstance().GetPluginsByType(pluginType);
     int maxProb = 0;
     std::vector::iterator it;
     PluginDescription bestMatchedPlugin;
     for (it = matchedPluginsDescriptions.begin(); it != matchedPluginsDescriptions.end(); it++) {
         std::shared_ptrpluginDef = cachedPluginPackage_->GetPluginDef(*it);
         if (pluginDef != nullptr) {
             auto prob = pluginDef->GetSniffer()(pluginDef->name, dataSource);
             if (prob > maxProb) {
                 maxProb = prob;
                 bestMatchedPlugin = (*it);
             }
         }
     }
     return bestMatchedPlugin.pluginName;
     }
  3. 限定探测文件大小 文件格式探测时,需要读取文件,然后解析相应的属性,并限定 sniff的大小,防止过多读取导致的起播延时和内存开销。

    int Sniff(const std::string& pluginName, std::shared_ptr<DataSource> dataSource)
     {
     FALSE_RETURN_V_MSG_E(!pluginName.empty(), 0, "Plugin name is empty");
     FALSE_RETURN_V_MSG_E(dataSource != nullptr, 0, "DataSource is nullptr");
     return SniffWithSize(pluginName, dataSource, DEFAULT_SNIFF_SIZE);
     }
  4. 优化丢帧逻辑 目前的丢帧逻辑:HiStreamer管道有音画同步逻辑,音频管道解析较快,在 sink阶段,会丢弃超时的渲染帧,这种丢帧逻辑其实无法提高解码和解封装的性能。 优化后的丢帧逻辑:在管道的 Demuxer阶段,当探测到视频帧延时,丢弃非参考帧,提升视频管道的速度。

  5. 优化内存

  • 步骤一HiStreamer管道中不同插件传递 Buffer数据时使用共享内存, 减少帧复制。
  • 步骤二、建立内存池,循环使用已分配好的内存,减少内存创建耗时。
  • 步骤三Demuxer解析后的数据直接放入解码器内存,不进行复制,减少耗时。

解封装能力测试

  1. 设计测试用例 用思维导图整理要测试的逻辑。
  2. 编写测试用例
  • 步骤1、首先准备测试文件,使用 FFMpeg探测文件中的帧数和关键帧。 执行 ffprobe命令,获取总帧数 2641
ffprobe -v error -select_streams v:0 -count_frames -show_entries stream=nb_read_frames wmv_wmv3_no.wmv

执行 ffprobe命令,获取关键帧数为 68

ffprobe -loglevel error -select_streams v:0 -show_frames -show_entries frame=pict_type wmv_wmv3_no.wmv | grep -c pict_type=I
  • 步骤2、打开 avcodec工程,在 unitest/demuxer_test目录单独创建m4v和wmv的单元测试文件, 编写测试用例。
HWTEST_F(DemuxerUnitTest, Demuxer_WMV_ReadSample_0001, TestSize.Level1)
{
    InitResource(g_wmvPath, LOCAL);
    ASSERTTRUE(initStatus);
    ASSERT_EQ(demuxer_->SelectTrackByID(0), AV_ERR_OK);
    sharedMem_ = AVMemoryMockFactory::CreateAVMemoryMock(bufferSize);
    ASSERT_NE(sharedMem_, nullptr);
    ASSERT_TRUE(SetInitValue());
    while (!isEOS(eosFlag))
    {
        for (autoidx
        :
        selectedTrackIds
        )
        {
            ASSERT_EQ(demuxer_->ReadSample(idx, sharedMem, &info, flag), AV_ERR_OK);
            CountFrames(idx);
        }
    }
    printf("frames_[0]=%d | kFrames[0]=%d\n", frames_[0], keyFrames_[0]);
    ASSERTEQ(frames[0], 2641);
    ASSERTEQ(keyFrames[0], 68);
    RemoveValue();
}
  1. 编译运行测试用例
  • 步骤1、编译测试用例。
./build.sh  --product-name rk3568 --ccache -fast-rebuild --build-target=demuxer_native_module_test
  • 步骤2、生成产物推入板卡 /bin目录。
rhdc shell mount -o remount,rw /
rhdc file send E:\share\demuxer_native_module_test /bin/
  • 步骤3、将资源文件推入板卡。
rhdc file send test/moduletest/resources/demuxer data/test/media/
  • 步骤4、执行测试用例。
demuxer_native_module_test --gtest_filter=Demuxer3gpFuncNdkTest.*
demuxer_native_module_test --gtest_filter=Demuxer3G2FuncNdkTest.*
demuxer_native_module_test --gtest_filter=DemuxerM4vFuncNdkTest.*
demuxer_native_module_test --gtest_filter=DemuxerWmvFuncNdkTest.*
demuxer_native_module_test --gtest_filter=DemuxerVobFuncNdkTest.*

总结

通过以上内容,不仅能帮助开发者深入理解开源鸿蒙多媒体框架的运行逻辑,熟练掌握多媒体解封装能力的扩展方法;还能针对解封装环节进行性能调优,并通过编写单元测试用例,从功能与性能双重维度,保障了解封装模块的鲁棒性与高效性。

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2025  OpenHarmony开发者论坛  京ICP备2020036654号-3 | 京公网安备11030102011662号 |技术支持 Discuz!

返回顶部