FFMPEG4.1源码分析之 字典类型AVDictionary及其相关APIs

目录

0 前言

0.1 ffmpeg中字典类型的描述

0.2 API使用简介

1 Structs && Flags

1.1 AVDictionary

1.2 AVDictionaryEntry

1.3 Flags

2 APIs

2.1 av_dict_count()

2.2 av_dict_free()

2.3 av_dict_copy() 

2.4 av_dict_get() 

2.5 av_dict_set() 


0 前言


在介绍字典操作api之前先介绍下字典的数据结构,由于是ffmpeg中的一个基础功能项,所以属于libavutil(lavu)库,头文件和源文件是dict.h和dict.c。

在头文件dict.h的开头部分有对ffmpeg中的该项功能做了简单的介绍,以及相应API使用的描述。

0.1 ffmpeg中字典类型的描述


/**
 * @file
 * Public dictionary API.
 * @deprecated
 *  AVDictionary is provided for compatibility with libav. It is both in
 *  implementation as well as API inefficient. It does not scale and is
 *  extremely slow with large dictionaries.
 *  It is recommended that new code uses our tree container from tree.c/h
 *  where applicable, which uses AVL trees to achieve O(log n) performance.
 */
  • 提供AVDictionary是为了兼容libav库。其APIs是效率低下的,无法扩展并且在大字典的情况下非常慢(由于是链表,其查询时间复杂度O(n))。

  • 新代码建议使用树状容器,定义在tree.c和tree.h中,其使用AVL树来实现O(log n)的效率。

0.2 API使用简介


/**
 * @addtogroup lavu_dict AVDictionary
 * @ingroup lavu_data
 *
 * @brief Simple key:value store
 *
 * @{
 * Dictionaries are used for storing key:value pairs. To create
 * an AVDictionary, simply pass an address of a NULL pointer to
 * av_dict_set(). NULL can be used as an empty dictionary wherever
 * a pointer to an AVDictionary is required.
 * Use av_dict_get() to retrieve an entry or iterate over all
 * entries and finally av_dict_free() to free the dictionary
 * and all its contents.
 *
 @code
   AVDictionary *d = NULL;           // "create" an empty dictionary
   AVDictionaryEntry *t = NULL;

   av_dict_set(&d, "foo", "bar", 0); // add an entry

   char *k = av_strdup("key");       // if your strings are already allocated,
   char *v = av_strdup("value");     // you can avoid copying them like this
   av_dict_set(&d, k, v, AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL);

   while (t = av_dict_get(d, "", t, AV_DICT_IGNORE_SUFFIX)) {
       <....>                             // iterate over all entries in d
   }
   av_dict_free(&d);
 @endcode
 */
  • AVDictionary用来存储键-值对;

  • av_dict_set()可以用来创建AVDictionary,通过给方法传入NULL指针,其内部会创建新的AVDictionary,并通过这个指针返回给用户新创建的AVDictionary对象;注意上面源码的示例,在key和value都已经分配好空间的时候,使用av_dict_set()时需要设置flags以便使得函数内部不再为key和value另分配空间了,直接利用外部已经分配好的。

  • av_dict_get() 可以用来获取AVDictionary的单个条目,进行迭代就可以获取AVDictionary的所有条目;

  • av_dict_free()用来释放AVDictionary所占用的所有空间。

1 Structs && Flags


1.1 AVDictionary


typedef struct AVDictionary AVDictionary;  // libavutil/dict.h

struct AVDictionary {                      // libavutil/dict.c
    int count;
    AVDictionaryEntry *elems;
};

1.2 AVDictionaryEntry


typedef struct AVDictionaryEntry {   // libavutil/dict.h
    char *key;
    char *value;
} AVDictionaryEntry;

1.3 Flags


对于以下7类flag,每个flag占据一个bit。代表的意思见如下源码中的英文释义

#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 */

2 APIs


2.1 av_dict_count()


av_dict_count() 声明:返回dictionary中的条目数

/**
 * Get number of entries in dictionary.
 *
 * @param m dictionary
 * @return  number of entries in dictionary
 */
int av_dict_count(const AVDictionary *m);

av_dict_count() 源码:

int av_dict_count(const AVDictionary *m)
{
    return m ? m->count : 0;
}

2.2 av_dict_free()


av_dict_free() 声明:

  • 声明:释放所有为AVDictionary结构体分配的空间,以及所有的keys和values所占用的空间
    ​
    /**
     * Free all the memory allocated for an AVDictionary struct
     * and all keys and values.
     */
    void av_dict_free(AVDictionary **m);
    
    ​

av_dict_free() 源码:

  • 源文件:
    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);
    }
  1.  一层层释放空间:迭代AVDictionary中的AVDictionaryEntry,不断释放key和value指向的空间 --> 释放AVDictionaryEntry列表空间 -->释放AVDictionary结构体空间。

  2. 注释释放空间时的av_freep()函数使用,传入的参数是个双重指针。详细分析见 FFMPEG4.1源码分析之 内存管理APIs av_freep() && av_free()

2.3 av_dict_copy() 


av_dict_free() 声明:

  • 声明:从一个AVDictionary复制条目到另外一个AVDictionary,注意以下几点
              1)dst的类型是AVDictionary**,*dst为空的时候会创建一个AVDictionary,并通过dst返回
              2)读取src的条目的时候使用AV_DICT_IGNORE_SUFFIX flag,以便可以遍历所有src的条目,在设置dst的条目的时候使用传入的flags
    /**
     * Copy entries from one AVDictionary struct into another.
     * @param dst pointer to a pointer to a AVDictionary struct. If *dst is NULL,
     *            this function will allocate a struct for you and put it in *dst
     * @param src pointer to source AVDictionary struct
     * @param flags flags to use when setting entries in *dst
     * @note metadata is read using the AV_DICT_IGNORE_SUFFIX flag
     * @return 0 on success, negative AVERROR code on failure. If dst was allocated
     *           by this function, callers should free the associated memory.
     */
    int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags);

av_dict_free() 源码:

int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags)
{
    AVDictionaryEntry *t = NULL;

    while ((t = av_dict_get(src, "", t, AV_DICT_IGNORE_SUFFIX))) {
        int ret = av_dict_set(dst, t->key, t->value, flags);
        if (ret < 0)
            return ret;
    }

    return 0;
}
  • av_dict_get()不停迭代获取条目,注意flag为AV_DICT_IGNORE_SUFFIX,这个标志使得进行key匹配的时候,只要传入的key字符串与条目的key字符串前面的字符能匹配上,则认为该条目是要查找的条目。此处传入的key字符串为"",因此可以匹配所有的条目。
  • av_dict_set() 将根据取出的每个条目,以及传入的flags值来设置目标AVDictionary。

2.4 av_dict_get() 


av_dict_get() 声明:

/**
 * 获取与key匹配的条目
 * Get a dictionary entry with matching key.
 *
 * 返回的key和value值不要进行修改,否则会导致未定义的行为
 * The returned entry key or value must not be changed, or it will
 * cause undefined behavior.
 *
 * 为了迭代所有的条目,匹配key串设置为"",并且flags设置为AV_DICT_IGNORE_SUFFIX 
 * To iterate through all the dictionary entries, you can set the matching key
 * to the null string "" and set the AV_DICT_IGNORE_SUFFIX flag.
 *
 * 入参prev指示查询将以prev为起始条目往下查询;当prev为NULL时,将从第一个条目起始进行查询
 * @param prev Set to the previous matching element to find the next.
 *             If set to NULL the first matching element is returned.
 *
 * 入参key是进行匹配的字符串
 * @param key matching key
 *
 * 入参flags将控制匹配条件,决定什么才算是key值匹配上了
 * @param flags a collection of AV_DICT_* flags controlling how the entry is retrieved
 *
 * 返回值是满足匹配条件的条目,或者是NULL,如果没找到满足条件的条目的时候。
 * @return found entry or NULL in case no matching entry was found in the dictionary
 */
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;
}

2.5 av_dict_set() 


av_dict_set() 声明:

/**
 * 设置一个指定的AVDictionary中的条目,覆盖已存在的条目
 * Set the given entry in *pm, overwriting an existing entry.
 *
 * 如果AV_DICT_DONT_STRDUP_KEY 或者AV_DICT_DONT_STRDUP_VAL被设置,那么
 * 在出错的时候入参key和value所占用空间将被释放
 * Note: If AV_DICT_DONT_STRDUP_KEY or AV_DICT_DONT_STRDUP_VAL is set,
 * these arguments will be freed on error.
 *
 * 向字典添加新条目会使以前使用av_dict_get()返回的所有现有条目无效。
 * Warning: Adding a new entry to a dictionary invalidates all existing entries
 * previously returned with av_dict_get.
 *
 * pm是双重指针,若*pm为空,那么函数内部会分配一个新的AVDictionary结构,并将地址给*pm,
 * 从而,调用方可以通过入参pm获取到分配的AVDictionary
 * @param pm pointer to a pointer to a dictionary struct. If *pm is NULL
 * a dictionary struct is allocated and put in *pm.
 *
 * @param key entry key to add to *pm (will either be av_strduped or added as a new key depending on flags)
 * @param value entry value to add to *pm (will be av_strduped or added as a new key depending on flags).
 *        传入NULL,将引发存在的条目被删除
 *        Passing a NULL value will cause an existing entry to be deleted.
 * @return >= 0 on success otherwise an error code <0
 */
int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);

av_dict_set() 源码:

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.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. 根据是操作新建条目还是操作旧有条目进行区分处理,目标:其实就是针对这两种情况整理出一个统一的条目出来,以便后续操作能统一起来。
    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的版图。
  3. 根据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的情况进一步处理,见源码,此处也不再多做讲解。
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值