OpenHarmony开发者论坛

标题: 拆·应用 第九期:基于FFmpeg解封装WMV和M4V格式 [打印本页]

作者: 开源鸿蒙知行录    时间: 2026-1-22 16:51
标题: 拆·应用 第九期:基于FFmpeg解封装WMV和M4V格式
[md]> **引言**
> 开源鸿蒙具备多格式音视频播放能力,其播放器需依次完成解协议、解封装、解码、渲染四大核心步骤,方可将音视频内容完整呈现给用户;其中,解封装作为衔接协议解析与音视频解码的关键环节,具有极高的研究价值;本文将以扩展 WMV、M4V 格式解封装能力为实践切入点,带大家深入熟悉开源鸿蒙的多媒体框架。

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

1. `FFmpeg`层扩展M4V、WMV的解封装能力

- **步骤1**:修改 `FFmpeg ohos_config.sh`配置,打开M4V、WMV的解封装能力。需要注意:M4V与MOV封装协议一致,WMV与ASF封装格式一致。
  
  ```bash
  --enable-demuxer=mov,asf
  ```
- **步骤2**:修改`BUILD.gn`,配置模块增加ASF宏定义。

```cmake
"-DCONFIG_ASF_DEMUXER", "-DCONFIG_MOV_DEMUXER",
```

- **步骤3**: 查看`libavformat`模块MakeFile文件,确认M4V和WMV解封装涉及的文件。

```makefile
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的文件编译。

```makefile
"//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. 多媒体框架层扩展WMV、M4V解封装能力

- **步骤1**:修改 `PluginList`,注册WMV、M4V解封装插件描述信息。

```cpp
void PluginList::AddMovDemuxerPlugin()
{
    PluginDescription movDemuxerPlugin;
    movDemuxerPlugin.pluginName = "avdemux_mov,mp4,m4a,3gp,3g2,mj2";
    movDemuxerPlugin.packageName = "FFmpegDemuxer";
    movDemuxerPlugin.pluginType = PluginType:EMUXER;
    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:EMUXER;
    asfDemuxerPlugin.cap = "";
    asfDemuxerPlugin.rank = DEFAULT_RANK;
    pluginDescriptionList_.push_back(asfDemuxerPlugin);
}
```

- **步骤2**:修改 `MediaType`, 定义M4V和WMV的 `MediaType`类型。

```cpp
enum class FileType : int32_t {   
    M4V = 111,   
    WMV = 112
}
```

- **步骤3**:修改 `FFmpegFormatHelper`,完善和M4V、WMV的探测逻辑。

```cpp
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格式的显示。

```cpp
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`处理,已加载的插件直接从内存中获取,防止二次加载插件耗时。
   
   ```cpp
   std::shared_ptr<luginDefBase>CachedPluginPackage::GetPluginDef(PluginDescriptionpluginDescription)
    {
    AutoLocklock(pluginMutex);
    std::vector<std::shared_ptr<luginPackage>>::iterator itPluginPackage;
    for (itPluginPackage = pluginPackageList_.begin();
        itPluginPackage != pluginPackageList_.end(); itPluginPackage++) {
        if (*itPluginPackage == nullptr) {
            return nullptr;
        }
        std::vector<std::shared_ptr<luginDefBase>> pluginDefList = (*itPluginPackage)->GetAllPlugins();
        std::vector<std::shared_ptr<luginDefBase>>::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<luginPackage> pluginPackage = std::make_shared<luginPackage>();
    bool ret = pluginPackage->LoadPluginPackage(pluginDescription.packageName);
    if (!ret) {
        return nullptr;
    }
    pluginPackageList_.push_back(pluginPackage);
    std::vector<std::shared_ptr<luginDefBase>> pluginDefList = pluginPackage->GetAllPlugins();
    std::vector<std::shared_ptr<luginDefBase>>::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`,找出评分最高的 `demuxer`,`FFmpeg`本身支持的 `demuxer`比较多,限定探索范围为 `PluginList`中已注册的 `demuxer`, 可以加快起播速度。
   
   ```cpp
   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`的大小,防止过多读取导致的起播延时和内存开销。
   
   ```cpp
   int Sniff(const std::string& pluginName, std::shared_ptr<DataSource> dataSource)
    {
    FALSE_RETURN_V_MSG_E(!pluginName.empty(), 0, "lugin 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`。

```bash
ffprobe -v error -select_streams v:0 -count_frames -show_entries stream=nb_read_frames wmv_wmv3_no.wmv
```

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

```bash
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的单元测试文件, 编写测试用例。

```cpp
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();
}
```

3. **编译运行测试用例**

- *步骤1*、编译测试用例。

```bash
./build.sh  --product-name rk3568 --ccache -fast-rebuild --build-target=demuxer_native_module_test
```

- *步骤2*、生成产物推入板卡 `/bin`目录。

```bash
rhdc shell mount -o remount,rw /
rhdc file send E:\share\demuxer_native_module_test /bin/
```

- *步骤3*、将资源文件推入板卡。

```bash
rhdc file send test/moduletest/resources/demuxer data/test/media/
```

- *步骤4*、执行测试用例。

```bash
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.*
```

# 总结

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

[/md]




欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/) Powered by Discuz! X3.5