简介: CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址: Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址: 多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

🍉🍉🍉文章目录🍉🍉🍉
🌻1.前言
本篇目的:理解
av_packet_pack_dictionary()如何将AVDictionary编码为字节序列,分析序列内存的所有权,并通过Packet侧数据完成打包与解包。
FFmpeg需要把字典形式的元数据附加到AVPacket时,不能直接把AVDictionary结构体作为Packet字节数据传递。av_packet_pack_dictionary()按键和值的顺序生成一段由NUL字符分隔的字节序列,供调用者添加为Packet侧数据。
它只负责生成序列,不会创建AVPacket、修改原字典或自动把序列附加到Packet。
🌻2.应用场景和用法
2.1把字典元数据放入Packet侧数据
这个接口适合把一组键值元数据打包后附加到Packet,例如图像序列解复用器把当前图像的来源路径和文件名保存到AV_PKT_DATA_STRINGS_METADATA侧数据中。
| 场景 | 调用动作 | 结果 |
|---|---|---|
| 为图像Packet添加来源信息 | 打包路径和文件名字典 | Packet携带对应侧数据 |
| 保存容器或流元数据 | 打包AVDictionary | 字典成为可传递的字节序列 |
| 需要在下游恢复字典 | 配合av_packet_unpack_dictionary() | 从侧数据重建键值项 |
| 元数据不存在 | 传入NULL字典 | 返回NULL并将长度设为0 |
2.2函数原型与内存所有权
函数原型如下:
uint8_t *
av_packet_pack_dictionary (AVDictionary *dict,
size_t *size);
| 参数或返回值 | 含义 |
|---|---|
dict | 待打包的字典,不会被函数修改 |
size | 必须指向有效的size_t变量,函数写入打包后的字节数 |
| 返回值 | 使用av_malloc()分配的序列;失败时返回NULL |
每个字典项依次写成key\0value\0,多项按字典迭代顺序连续排列。返回序列由调用者持有,成功交给av_packet_add_side_data()后,所有权转移给Packet;添加失败时仍由调用者释放。
函数没有独立错误码。返回NULL时,调用者无法从返回值区分字典为空、长度计算溢出或内存分配失败等情况。若字典是预期输入,应用通常需要把NULL当作打包未成功处理。
相邻接口职责如下:
| 接口 | 职责 |
|---|---|
av_packet_pack_dictionary() | 将字典打包成新分配的字节序列 |
av_packet_add_side_data() | 将字节序列关联到Packet指定类型的侧数据 |
av_packet_get_side_data() | 从Packet读取指定类型的侧数据 |
av_packet_unpack_dictionary() | 从字节序列恢复字典项 |
av_free() | 释放尚未转交Packet所有权的打包序列 |
🌻3.调用流程剖析
3.1两次遍历计算并生成字节序列
av_packet_pack_dictionary()先将*size设为0。字典为NULL时立即返回NULL。
有字典时,函数遍历两次。第一次遍历每个键和值,计算包括字符串结尾NUL字符在内的总长度,并检查size_t加法溢出。长度计算成功后,函数通过av_malloc()分配缓冲区。
第二次遍历按key\0value\0的顺序把字符串拷贝到缓冲区。每次拷贝都包含末尾NUL字符,因此相邻字典项可以连续存储。
3.2将打包结果附加并在读取侧恢复
打包函数返回后,调用者需要将结果交给av_packet_add_side_data()。添加成功后,Packet负责持有侧数据;添加失败时,调用者仍负责av_free()该缓冲区。
读取侧先通过av_packet_get_side_data()取得侧数据地址和长度,再调用av_packet_unpack_dictionary()恢复字典。解包函数会检查序列结尾及键值边界,遇到无效数据时返回负值。
🌻4.实战案例
4.1准备字典和空Packet
示例创建包含source_path和source_basename两项的AVDictionary,并分配一个空AVPacket。字典值使用普通C字符串,打包序列可以直接作为Packet侧数据保存。
4.2打包并添加为侧数据
程序调用av_packet_pack_dictionary()得到数据指针和长度,再将其添加为AV_PKT_DATA_STRINGS_METADATA。添加成功后,程序将本地指针置为NULL,避免清理阶段重复释放Packet已经接管的内存。
4.3完整代码
#include <libavcodec/packet.h>
#include <libavutil/dict.h>
#include <libavutil/mem.h>
#include <string.h>
int
main (void)
{
AVPacket *pkt = NULL;
AVDictionary *metadata = NULL;
AVDictionary *restored = NULL;
const AVDictionaryEntry *entry;
uint8_t *packed = NULL;
const uint8_t *side_data;
size_t packed_size = 0;
size_t side_data_size = 0;
int ret = 1;
/* 创建Packet,用于承载字符串元数据侧数据 */
pkt = av_packet_alloc ();
if (!pkt)
goto end;
/* 准备图像来源路径和文件名 */
if (av_dict_set (&metadata,
"lavf.image2dec.source_path",
"/data/frame001.jpg", 0) < 0)
goto end;
if (av_dict_set (&metadata,
"lavf.image2dec.source_basename",
"frame001.jpg", 0) < 0)
goto end;
/* 将字典打包为键值交替的NUL分隔字节序列 */
packed = av_packet_pack_dictionary (metadata, &packed_size);
if (!packed)
goto end;
/* 添加成功后由Packet接管packed缓冲区 */
if (av_packet_add_side_data (
pkt,
AV_PKT_DATA_STRINGS_METADATA,
packed,
packed_size) < 0)
goto end;
packed = NULL;
/* 从Packet中取回侧数据及其实际字节数 */
side_data = av_packet_get_side_data (
pkt,
AV_PKT_DATA_STRINGS_METADATA,
&side_data_size);
if (!side_data)
goto end;
/* 将打包的字节序列恢复为字典 */
if (av_packet_unpack_dictionary (
side_data,
side_data_size,
&restored) < 0)
goto end;
/* 检查恢复后的文件名字段 */
entry = av_dict_get (
restored,
"lavf.image2dec.source_basename",
NULL,
0);
if (!entry || strcmp (entry->value, "frame001.jpg") != 0)
goto end;
ret = 0;
end:
/* 仅释放尚未转交给Packet的打包缓冲区 */
av_free (packed);
av_dict_free (&metadata);
av_dict_free (&restored);
av_packet_free (&pkt);
return ret;
}
🌻5.总结
av_packet_pack_dictionary()把AVDictionary编码为NUL分隔的键值序列,供调用者添加到Packet侧数据,并可由av_packet_unpack_dictionary()恢复。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/u010164190/article/details/170193683




