基于FFmpeg解封装WMV和M4V格式

描述

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

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

1.1、HiStreamer播放器管道

播放管道构建:框架启动播放时,会先创建一条完整的播放管道,并按处理流程依次连接过滤器(Filter)、解封装器、解码器与渲染器,形成音视频数据的处理链路;

插件加载与注册:每个过滤器会根据待处理文件的格式,加载系统固定目录下以 so 文件形式存储的对应插件(Plugin);插件加载完成后,会自动触发注册接口调用,将自身的插件描述信息(如支持的格式、处理能力等)存入框架的插件列表中;

插件匹配与任务执行:当具体文件开始播放时,音视频数据会沿播放管道从左至右在各过滤器间流转。

每个过滤器会遍历插件列表中的描述信息,筛选并返回评分最高的插件,由该插件完成 “解协议”“解封装”“解码”“音视频渲染” 等对应处理任务。

1.2、HiStreamer解封装插件加载流程

插件加载主体:解封装插件的加载操作由PluginManager(插件管理器) 统一执行;

扩展前置条件:若要扩展解封装框架,首要步骤是在PluginList(插件列表) 中完成解封装插件描述信息的注册;

插件调度逻辑:当音视频数据流经解封装器时,框架会遍历 PluginList 中所有已注册的解封装插件,最终选取评分最高的插件执行解封装核心任务。

开源鸿蒙扩展M4V、WMV解封装能力

2.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.o
OBJS-$(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",

 

2.2、 多媒体框架层扩展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 MEDIA_MIME_TYPE_MAP = {
{ "video/x-ms-asf", { "asf", "asx" }
},
{ "video/mp4", { "m4v", "f4v", "mp4v", "mpeg4", "mp4" }
},
}

 

解封装性能优化

3.1、 插件加载优化

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

 

std::shared_ptrCachedPluginPackage::GetPluginDef(PluginDescriptionpluginDescription)
{
   AutoLocklock(pluginMutex);
   std::vector>::iterator itPluginPackage;
   for (itPluginPackage = pluginPackageList_.begin();
       itPluginPackage != pluginPackageList_.end(); itPluginPackage++) {
       if (*itPluginPackage == nullptr) {
           return nullptr;
      }
       std::vector> pluginDefList = (*itPluginPackage)->GetAllPlugins();
       std::vector>::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 = std::make_shared();
   bool ret = pluginPackage->LoadPluginPackage(pluginDescription.packageName);
   if (!ret) {
       return nullptr;
  }
   pluginPackageList_.push_back(pluginPackage);
   std::vector> pluginDefList = pluginPackage->GetAllPlugins();
   std::vector>::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;
}

 

3.2、探测遍历优化

文件格式探测时,会遍历所有的demuxer,找出评分最高的demuxer,FFmpeg本身支持的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::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.3、 限定探测文件大小

文件格式探测时,需要读取文件,然后解析相应的属性,并限定sniff的大小,防止过多读取导致的起播延时和内存开销。

 

int Sniff(const std::string& pluginName, std::shared_ptr 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);
}

 

3.4、 优化丢帧逻辑

目前的丢帧逻辑:HiStreamer管道有音画同步逻辑,音频管道解析较快,在sink阶段,会丢弃超时的渲染帧,这种丢帧逻辑其实无法提高解码和解封装的性能。

优化后的丢帧逻辑:在管道的Demuxer阶段,当探测到视频帧延时,丢弃非参考帧,提升视频管道的速度。

3.5、 优化内存

步骤一、HiStreamer管道中不同插件传递Buffer数据时使用共享内存, 减少帧复制。

步骤二、建立内存池,循环使用已分配好的内存,减少内存创建耗时。

步骤三、Demuxer解析后的数据直接放入解码器内存,不进行复制,减少耗时。

解封装能力测试

4.1 设计测试用例

用思维导图整理要测试的逻辑。

4.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
", frames_[0], keyFrames_[0]);
  ASSERTEQ(frames[0],2641);
  ASSERTEQ(keyFrames[0],68);
   RemoveValue();
}

 

4.3 编译运行测试用例

步骤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:sharedemuxer_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.*

 

总结

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

供稿:李晓飞、傅巧妮、魏宏亮

责编:开发者与活动运营组 李健

编审:品牌管理组 丽娜

审核:开源鸿蒙项目群工作委员会执行总监 陶铭

开源鸿蒙项目群工作委员会执行秘书 曹云菲 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分