原创:https://blog.csdn.net/ice_ly000/article/details/90599713?spm=1001.2014.3001.5501
ffmpeg中字典类型的描述:
字典类型 API使用简介
/**
* @文件
* 公共字典API。
* @已弃用
* 提供 AVDictionary 是为了与 libav 兼容。它既在
* 实现以及 API 效率低下。它没有规模,是
* 使用大词典时速度极慢。
* 建议新代码使用我们来自 tree.c/h 的树容器
* 在适用的情况下,它使用 AVL 树来实现 O(log n) 性能。
*/
#ifndef AVUTIL_DICT_H
#define AVUTIL_DICT_H
#include <stdint.h>
#include "version.h"
* @addtogroup lavu_dict AVDictionary
* @ingroup lavu_data
* @brief 简单键:值存储
* @{
* 字典用于存储键值对。创造
* 一个 AVDictionary,只需将一个 NULL 指针的地址传递给
* av_dict_set()。 NULL 可以在任何地方用作空字典
* 需要一个指向 AVDictionary 的指针。
* 使用 av_dict_get() 检索条目或遍历所有条目
* 条目,最后 av_dict_free() 释放字典
* 及其所有内容。
/*
@代码
AVDictionary *d = NULL; // “创建”一个空字典
AVDictionaryEntry *t = NULL;
av_dict_set(&d, "foo", "bar", 0); // 添加一个条目
char *k = av_strdup("key"); // 如果你的字符串已经被分配,
char *v = av_strdup("value"); // 你可以避免像这样复制它们
av_dict_set(&d, k, v, AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL);
而 (t = av_dict_get(d, "", t, AV_DICT_IGNORE_SUFFIX)) {
<....> // 遍历 d 中的所有条目
}
av_dict_free(&d);
@endcode
*/
#define AV_DICT_MATCH_CASE 1 /**< Only get an entry with exact-case key match. Only relevant in av_dict_get(). */
#define AV_DICT_IGNORE_SUFFIX 2 /**< Return first entry in a dictionary whose first part corresponds to the search key,
ignoring the suffix of the found key string. Only relevant in av_dict_get(). */
#define AV_DICT_DONT_STRDUP_KEY 4 /**< Take ownership of a key that's been
allocated with av_malloc() or another memory allocation function. */
#define AV_DICT_DONT_STRDUP_VAL 8 /**< Take ownership of a value that's been
allocated with av_malloc() or another memory allocation function. */
#define AV_DICT_DONT_OVERWRITE 16 ///< Don't overwrite existing entries.
#define AV_DICT_APPEND 32 /**< If the entry already exists, append to it. Note that no
delimiter is added, the strings are simply concatenated. */
#define AV_DICT_MULTIKEY 64 /**< Allow to store several equal keys in the dictionary */
typedef struct AVDictionaryEntry {
char *key;
char *value;
} AVDictionaryEntry;
typedef struct AVDictionary AVDictionary;
av_dict_get()
* 获取具有匹配键的字典条目。
* 返回的条目键或值不得更改,否则将
* 导致未定义的行为。
* 要遍历所有字典条目,您可以设置匹配键
* 到空字符串 "" 并设置 AV_DICT_IGNORE_SUFFIX 标志。
*
* @param prev 设置为前一个匹配元素以查找下一个。
* 如果设置为 NULL,则返回第一个匹配元素。
* @param key 匹配key
* @param 标记了一组控制如何检索条目的 AV_DICT_* 标记
* @return 找到的条目或 NULL 以防在字典中找不到匹配的条目
AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key,
const AVDictionaryEntry *prev, int flags);
av_dict_get() 源码:
源码比较简单,分析见注释。这儿主要把两个flag拿出来单独看下:
AV_DICT_MATCH_CASE标志表示key的匹配是大小写敏感的。
AV_DICT_IGNORE_SUFFIX标志表示key只要与条目中的key的前面的字符相同,那么就算是匹配上了。
AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key,
const AVDictionaryEntry *prev, int flags)
{
unsigned int i, j;
// AVDictionary为空,自然条目为空
if (!m)
return NULL;
// 计算prev下一个entry是第几个,注意计算方式
// 若prev是不属于AVDictionary的条目或者已经是最后一个,
// 那么计算出来的i必然是越界的。
if (prev)
i = prev - m->elems + 1;
else
i = 0;
// 从第i个条目进行匹配
for (; i < m->count; i++) {
const char *s = m->elems[i].key;
// 大小写敏感,则不进行字符串转换
if (flags & AV_DICT_MATCH_CASE)
for (j = 0; s[j] == key[j] && key[j]; j++)
;
// 大小写不铭感,则都转换成大写字母,然后进行匹配
else
for (j = 0; av_toupper(s[j]) == av_toupper(key[j]) && key[j]; j++)
;
// 经过上述匹配过程,key中还有余留字符没有进行匹配,说明没有匹配上
// 因此进入下一个循环
if (key[j])
continue;
// 经过上述匹配过程,key中已无余留字符,但是条目的key中还有余留字符
// 若是AV_DICT_IGNORE_SUFFIX标志存在,那么也算是匹配上,否则,就是要求
// 全部都必须匹配上,那么也直接进入下一个循环
if (s[j] && !(flags & AV_DICT_IGNORE_SUFFIX))
continue;
// 上述条件都通过,那么当前条目就是满足匹配条件的条目,返回该条目
return &m->elems[i];
}
// 经过for循环还未从函数返回,那么没找到匹配的条目,此时返回NULL
return NULL;
}
av_dict_count()
返回dictionary中的条目数
* 获取字典中的条目数。
*
* @param m 字典
* @return 字典中的条目数
int av_dict_count(const AVDictionary *m);
av_dict_count()源码
int av_dict_count(const AVDictionary *m)
{
return m ? m->count : 0;
}
av_dict_set()
* 在 *pm 中设置给定条目,覆盖现有条目。
*
* 注意:如果设置了 AV_DICT_DONT_STRDUP_KEY 或 AV_DICT_DONT_STRDUP_VAL,
* 这些参数将在出错时被释放。
*
* 警告:向字典添加新条目会使所有现有条目无效
* 之前用 av_dict_get 返回。
*
* @param pm 指向字典结构指针的指针。 如果 *pm 为 NULL
* 一个字典结构被分配并放入 *pm。
* @param key 输入键添加到 *pm (将被 av_strduped 或作为新键添加,具体取决于标志)
* @param value 要添加到 *pm 的条目值(将被 av_strduped 或作为新键添加,具体取决于标志)。
* 传递 NULL 值将导致删除现有条目。
* @return >= 0 成功否则错误代码 <0
int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);
int av_dict_set(AVDictionary **pm, const char *key, const char *value,
int flags)
{
AVDictionary *m = *pm;
AVDictionaryEntry *tag = NULL;
char *oldval = NULL, *copy_key = NULL, *copy_value = NULL;
// AV_DICT_MULTIKEY存在,表示AVDictionary的条目中允许出现重复的key值
// 如果不允许出现重复的key,那么获取传入key值所对应的条目
if (!(flags & AV_DICT_MULTIKEY)) {
tag = av_dict_get(m, key, NULL, flags);
}
// AV_DICT_DONT_STRDUP_KEY用于告知对于传入的key是否需要为其新分配空间
// 如果需要重新分配,则调用av_strdup来拷贝一份新的字符串,并将新串地址返给copy_key
// 如果不需要,则直接利用入参传入的key值所占据的空间
if (flags & AV_DICT_DONT_STRDUP_KEY)
copy_key = (void *)key;
else
copy_key = av_strdup(key);
// 同上
if (flags & AV_DICT_DONT_STRDUP_VAL)
copy_value = (void *)value;
else if (copy_key)
copy_value = av_strdup(value);
// 如果不存在AVDictionary,那么新创建一个
if (!m)
m = *pm = av_mallocz(sizeof(*m));
// 以下三种情况认为是出错:
// 1 如果m还为空,那边就是AVDictionary空间分配失败
// 2 如果传入的key不为空,但是内部copy_key为空,也即分配空间失败
// 3 如果传入的value不为空,但是内部copy_value为空,也即分配空间失败
if (!m || (key && !copy_key) || (value && !copy_value))
goto err_out; // 出现空间分配失败,跳转到err_out标签处理
// tag存在,也即不允许存在重复key的情况下,找到了传入key对应的已存在条目
// 那么就不需要创建新的条目了
if (tag) {
// AV_DICT_DONT_OVERWRITE表示不允许覆盖已有值
// 如果不允许重写value值,则直接释放掉copy_key和copy_value所占用的空间,并且返回0
if (flags & AV_DICT_DONT_OVERWRITE) {
av_free(copy_key);
av_free(copy_value);
return 0;
}
// AV_DICT_APPEND表示允许在当前value之后追加值
// 如果允许重写,允许在当前value后追加,那么使用oldval变量记住条目中的value值
// 如果允许重写,但是不许追加,那么释放掉条目中的value空间
if (flags & AV_DICT_APPEND)
oldval = tag->value;
else
av_free(tag->value);
av_free(tag->key); // 释放调用条目中key占用的空间
// 将最后一个条目内容复制到tag这个条目,这是干啥呀?
// 请看后文!!!并且注意啊,m->count在这个时候自减一了!!!!
*tag = m->elems[--m->count];
// 需要创建新条目的场景
} else if (copy_value) {
// 使用av_realloc()来扩展m->elems指向的空间,扩展一个AVDictionaryEntry槽位
AVDictionaryEntry *tmp = av_realloc(m->elems,
(m->count + 1) * sizeof(*m->elems));
// 分配失败,跳转到err_out标签处理
if (!tmp)
goto err_out;
// 由于av_realloc扩展后的地址可能与之前的不一致,因此
// 还需将m->elems指向分配后的空间
m->elems = tmp;
}
// copy_value不为空,意味着要进行设置
// 不论是新创建条目的场景还是设置旧有条目的场景,最后
// 一个条目都是目的条目!!注意设置旧有条目的场景的最后一个语句干啥了~~
if (copy_value) {
// 更新条目key && value指针
m->elems[m->count].key = copy_key;
m->elems[m->count].value = copy_value;
// 如果存在旧值,并且value是允许append,那么进行如下操作
if (oldval && flags & AV_DICT_APPEND) {
size_t len = strlen(oldval) + strlen(copy_value) + 1; // 计算旧值+新值总大小
char *newval = av_mallocz(len); // 为新旧二者创建新的空间
if (!newval) // 如果创建失败,跳转err_out标签进行处理
goto err_out;
// 将旧值和新值都拷贝到新分配的空间,并释放旧空间
av_strlcat(newval, oldval, len);
av_freep(&oldval);
av_strlcat(newval, copy_value, len);
m->elems[m->count].value = newval; // 更新条目的value指针
av_freep(©_value);
}
m->count++; // 条目数加1
// copy_value为空,意味着要删除条目,那为啥这儿只做了释放copy_key的操作?
// 奇怪呢~~为啥这儿删除条目,m->elems按理说应该减一才对
// 为啥不减呢?结合上面的代码认真思考
} else {
av_freep(©_key);
}
// 若删除到无条目的情况下,释放空间
if (!m->count) {
av_freep(&m->elems);
av_freep(pm);
}
// 返回成功
return 0;
err_out:
if (m && !m->count) {
av_freep(&m->elems);
av_freep(pm);
}
av_free(copy_key);
av_free(copy_value);
return AVERROR(ENOMEM);
}
代码最开始,需要对传入参数进行一系列的验证:
1.1) 验证AVDictionary结构体是否存在,若不存在则需要新建一个
1.2) 验证flags提供的属性:
1.2.1)根据入参flags相应标志位AV_DICT_MULTIKEY && AV_DICT_DONT_STRDUP_VAL 是否置位来决定是否需要新建空间,拷贝key和value的字符串
1.2.2)根据入参flags相应标志位AV_DICT_MULTIKEY是否置位来确定是操作新的条目,还是操作AVDictionary中已经存在的条目
根据是操作新建条目还是操作旧有条目进行区分处理,目标:其实就是针对这两种情况整理出一个统一的条目出来,以便后续操作能统一起来。
2.1)操作旧有条目:
2.1.1) 如果入参flags的AV_DICT_DONT_OVERWRITE被置位,那么意思就是不能重写旧条目,“既要操作旧条目,又不能重写旧条目”,那就没法操作了,所以释放分配key && value空间,函数退出。
2.1.2)如果入参flags的AV_DICT_APPEND置位,意思就是新value拼接到旧value之后,那么旧value还有用,否则没用了就释放旧value所占空间。
2.1.3)释放掉旧有key。还记否?flags中有两个标志位是控制key匹配条件的,一个是控制匹配时大小写是否敏感,一个控制是需要完全匹配还是只需前面的字符能匹配上即可。因此,注意key可是会被替换掉的,所以,应用上要根据场景正确的使用,否则,会造成不必要的困惑。
2.1.4)*tag = m->elems[–m->count]; 这句话是重点需要理解的地方,首先是一点是将最后一个条目内的值复制到需要操作的条目上,这儿的操作是为了让最后一个条目成为需要操作的旧有条目,与新分配条目达成一致,因为新分配条目也会附加到最后的一个条目!!!其次,m->count会自减一,那么最后一个条目会先从当前所有条目中除名,这个也是为了后续能统一操作!!!
2.2)操作新条目:
2.2.1)使用av_realloc()来扩展空间,增加一个条目结构体空间;
2.2.2)由于av_realloc()处理之后的空间地址不一定与原地址相同,因此还需要m->elems = tmp;
2.2.3)注意,此时并没有m->count加1,因此新增的条目结构体还未纳入AVDictionary的版图。
根据copy_value是否为NULL,来决定是进行条目的删除操作还是修改操作
3.1)copy_value为NULL,则是需要进行条目的删除。
3.3.1)旧有条目的情形:曾记否?旧有条目的最后一个在步骤2中已经提前除名了,因此,此处只需要释放必要的空间旧ok了,那就是将key值得字符串释放就行了
3.3.2)新建条目的情形:虽然已经新增了一个条目的空间,但是m->count并未加1,也就还未纳入AVDictionary的版图。因此,此处的操作跟3.3.1一致,但是注意这儿是否有内存泄漏?没有的,因为m->elems还指向这个分配的空间呢~
3.2)copy_value不为NULL的情况,那么就是重写条目咯~ 注意,在该情况下,对于m->elems进行了越界访问,由于步骤2中的处理,这儿就能统一处理了,这儿就不赘述了。当然对于重写条目时是进行append的情况进一步处理,见源码,此处也不再多做讲解。
av_dict_set_int()
* av_dict_set 的便利包装器,用于将值转换为字符串
* 并存储它。
*
* 注意:如果设置了 AV_DICT_DONT_STRDUP_KEY,key 会在出错时被释放。
int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, int flags);
av_dict_parse_string()
* 解析键/值对列表并将解析的条目添加到字典中。
*
* 如果失败,所有成功设置的条目都存储在
* *下午。 您可能需要手动释放创建的字典。
*
* @param key_val_sep 一个以 0 结尾的字符列表,用于分隔
* 键值
* @param pair_sep 一个以 0 结尾的字符列表,用于分隔
* 两对彼此
* @param 标记添加到字典时使用的标志。
* AV_DICT_DONT_STRDUP_KEY 和 AV_DICT_DONT_STRDUP_VAL
* 被忽略,因为键/值标记将始终
* 被复制。
* @return 成功时返回 0,失败时返回负的 AVERROR 代码
int av_dict_parse_string(AVDictionary **pm, const char *str,
const char *key_val_sep, const char *pairs_sep,
int flags);
av_dict_free()
声明:释放所有为AVDictionary结构体分配的空间,以及所有的keys和values所占用的空间
* 释放为 AVDictionary 结构分配的所有内存
* 以及所有键和值。
void av_dict_free(AVDictionary **m);
void av_dict_free(AVDictionary **pm)
{
AVDictionary *m = *pm;
if (m) {
while (m->count--) {
av_freep(&m->elems[m->count].key);
av_freep(&m->elems[m->count].value);
}
av_freep(&m->elems);
}
av_freep(pm);
}
一层层释放空间:迭代AVDictionary中的AVDictionaryEntry,不断释放key和value指向的空间 --> 释放AVDictionaryEntry列表空间 -->释放AVDictionary结构体空间。
av_dict_get_string()
* 以字符串形式获取字典条目。
*
* 创建一个包含字典条目的字符串。
* 这样的字符串可以传递回 av_dict_parse_string()。
* @note 字符串用反斜杠 ('\') 转义。
*
* @param[in] m 字典
* @param[out] buffer 指向将分配有包含条目的字符串的缓冲区的指针。
* 缓冲区在不再需要时必须由调用者释放。
* @param[in] key_val_sep 用于分隔键值的字符
* @param[in]pairs_sep 用于将两对彼此分开的字符
* @return >= 0 成功,负数
* @warning 分隔符不能既不是 '\\' 也不能是 '\0'。 它们也不能相同。
int av_dict_get_string(const AVDictionary *m, char **buffer,
const char key_val_sep, const char pairs_sep);
av_dict_get_string()
int av_dict_get_string(const AVDictionary *m, char **buffer,
const char key_val_sep, const char pairs_sep)
{
AVDictionaryEntry *t = NULL;
AVBPrint bprint;
int cnt = 0;
char special_chars[] = {pairs_sep, key_val_sep, '\0'};
if (!buffer || pairs_sep == '\0' || key_val_sep == '\0' || pairs_sep == key_val_sep ||
pairs_sep == '\\' || key_val_sep == '\\')
return AVERROR(EINVAL);
if (!av_dict_count(m)) {
*buffer = av_strdup("");
return *buffer ? 0 : AVERROR(ENOMEM);
}
av_bprint_init(&bprint, 64, AV_BPRINT_SIZE_UNLIMITED);
while ((t = av_dict_get(m, "", t, AV_DICT_IGNORE_SUFFIX))) {
if (cnt++)
av_bprint_append_data(&bprint, &pairs_sep, 1);
av_bprint_escape(&bprint, t->key, special_chars, AV_ESCAPE_MODE_BACKSLASH, 0);
av_bprint_append_data(&bprint, &key_val_sep, 1);
av_bprint_escape(&bprint, t->value, special_chars, AV_ESCAPE_MODE_BACKSLASH, 0);
}
return av_bprint_finalize(&bprint, buffer);
}