ffmpeg中字典类型的描述

原创: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(&copy_value);
        }
        m->count++; // 条目数加1
        
    // copy_value为空,意味着要删除条目,那为啥这儿只做了释放copy_key的操作?
    // 奇怪呢~~为啥这儿删除条目,m->elems按理说应该减一才对
    // 为啥不减呢?结合上面的代码认真思考
    } else {
        av_freep(&copy_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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值