目录
- AVDictionary的使用介绍
- FFmpeg中AVDictionary的使用
- AVDictionary的源码学习
- 总结
参考
- [1] FMPEG Tips (5) 如何利用 AVDictionary 配置参数
- [2] FFmpeg/libavutil/dict.h
1. AVDictionary的使用介绍
AVDictionary是一个健值对存储工具,类似于c++中的map,ffmpeg中有很多 API 通过它来传递参数。
AVDictionary 的定义如下:
//libavutil/dict.h
typedef struct AVDictionaryEntry {
char *key;
char *value;
} AVDictionaryEntry;
struct AVDictionary {
int count;
AVDictionaryEntry *elems;
};
1.1. 基本用法
- 创建AVDictionary:将值为NULL的AVDictionary 指针变量的地址传递给av_dict_set()。
- AVDictionary结构体的具体实现没有在接口中暴露,不知道AVDictionary结构体占用的内存大小,所以使用者无法直接定义一个AVDictionary的变量,使用时需要定义一个AVDictionary指针的变量。
-
int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags)
,如果*pm == NULL
,会创建一个AVDictionary结构体,把地址赋值给*pm
。av_dict_set函数的完整签名:
AVDictionary *d = NULL;
av_dict_set(&d, "version", "1.0", AV_DICT_MATCH_CASE);
- flags可以设为不同的选项的组合,包含AV_DICT_MATCH_CASE时表示key的匹配是要区分大小写的,默认是不区分的,这一点要特别注意。
- 添加key-value对。
av_dict_set(&d, "version", "1.0", AV_DICT_MATCH_CASE);
av_dict_set_int(&d, "count", 3, AV_DICT_MATCH_CASE);
- 使用
AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key, const AVDictionaryEntry *prev, int flags)
检索条目(AVDictionaryEntry)或遍历字典。
- AVDictionaryEntry中包含(char *)类型的key和value。
- 返回的AVDictionaryEntry不应该被修改否则会发生未定义的行为。
检索条目
AVDictionaryEntry *t = NULL;
t = av_dict_get(d, "version", NULL, AV_DICT_MATCH_CASE);
av_log(NULL, AV_LOG_DEBUG, "version: %s", t->value);
t = av_dict_get(d, "count", NULL, AV_DICT_MATCH_CASE);
av_log(NULL, AV_LOG_DEBUG, "count: %d", (int) (*t->value));
遍历字典
AVDictionaryEntry *t = NULL;
while ((t = av_dict_get(d, "", t, AV_DICT_IGNORE_SUFFIX))) {
av_log(NULL, AV_LOG_DEBUG, "%s: %s", t->key, t->value);
}
- 最后使用av_dict_free()释放AVDictionary及其所有内容。
av_dict_free(&d);
1.2 flags的用法
av_dict_get()和av_dict_set()函数都有一个flags参数,下面对它的作用进行介绍,flags包含下面7个选项,可以进行组合使用:
#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 */
- AV_DICT_MATCH_CASE:在字典中检索key是需要区分大小写,默认是不区分大小写的,这点需要特别注意。看一下如下的示例。
AVDictionary *dict = NULL;
av_dict_set(&dict, "version", "1.0", 0);
av_dict_set(&dict, "VERsion", "2.0", 0);
AVDictionaryEntry *t = NULL;
while ((t = av_dict_get(dict, "", t, AV_DICT_IGNORE_SUFFIX))) {
fprintf(stdout, "%s: %s\n", t->key, t->value);
}
av_dict_free(&d);
程序的输出是
VERsion: 2.0
- av_dict_set在不设置AV_DICT_MATCH_CASE的情况下,如果key值与字典中的key值在忽略大小写的情况下是匹配的,会覆盖字典中匹配条目的key和value。
- AV_DICT_IGNORE_SUFFIX:如果key值是字典中某个条目key值的前缀,则认为是匹配的。
AVDictionary *dict = NULL;
av_dict_set(&dict, "language", "ch", 0);
av_dict_set(&dict, "version", "1.0", 0);
AVDictionaryEntry *t1 = av_dict_get(dict, "ver", NULL, AV_DICT_IGNORE_SUFFIX);
AVDictionaryEntry *t2 = av_dict_get(dict, "", NULL, AV_DICT_IGNORE_SUFFIX);
fprintf(stdout, "t1, %s: %s\n", t1->key, t1->value);
fprintf(stdout, "t2, %s: %s\n", t2->key, t2->value);
av_dict_free(&d);
上面示例的输出
t1, version: 1.0
t2, language: ch
- 由于flags包含了AV_DICT_IGNORE_SUFFIX的选项,所以key的匹配是按忽略后缀的方式(key是字典中条目的key的前缀)进行的,"ver"字符串是"version"字符串的前缀,所以获取到字典中key为"version"的条目。而“”空字符串是所有字符串的前缀,所以获取到字典中的第一个条目。
- AV_DICT_DONT_STRDUP_KEY或AV_DICT_DONT_STRDUP_VAL:接管使用key或value指针指向的内存的管理权,不对key或value的值进行复制。如下面示例所示。
AVDictionary *d = NULL;
AVDictionaryEntry *t = NULL;
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)) {
fprintf(stdout, "%s: %s\n", t->key, t->value);
}
av_dict_free(&d);
- AV_DICT_DONT_OVERWRITE:不覆盖现有条目。如下面示例所示。
AVDictionary *d = NULL;
AVDictionaryEntry *t = NULL;
av_dict_set(&d, "version", "1.0", 0);
av_dict_set(&d, "version", "2.0", AV_DICT_DONT_OVERWRITE);
while (t = av_dict_get(d, "", t, AV_DICT_IGNORE_SUFFIX)) {
fprintf(stdout, "%s: %s\n", t->key, t->value);
}
av_dict_free(&d);
输出结果为:version: 1.0
- AV_DICT_APPEND:如果目已经存在,则value值直接拼接到之前的值的后面。注意,没有添加分隔符,字符串只是连接在一起。如下面示例所示。
AVDictionary *d = NULL;
AVDictionaryEntry *t = NULL;
av_dict_set(&d, "version", "1.0", 0);
av_dict_set(&d, "version", "2.0", AV_DICT_APPEND);
while (t = av_dict_get(d, "", t, AV_DICT_IGNORE_SUFFIX)) {
fprintf(stdout, "%s: %s\n", t->key, t->value);
}
av_dict_free(&d);
输出结果为:version: 1.02.0
- AV_DICT_MULTIKEY:允许在字典中存储几个相等的key。如下面示例所示。
AVDictionary *dict = NULL;
av_dict_set(&dict, "version", "1.0", 0);
av_dict_set(&dict, "VERsion", "2.0", AV_DICT_MULTIKEY);
av_dict_set(&dict, "version", "3.0", AV_DICT_MULTIKEY);
AVDictionaryEntry *t = NULL;
while ((t = av_dict_get(dict, "", t, AV_DICT_IGNORE_SUFFIX))) {
fprintf(stdout, "%s: %s\n", t->key, t->value);
}
av_dict_free(&dict);
输出结果为:
version: 1.0
VERsion: 2.0
version: 3.0
2. FFmpeg中AVDictionary的使用
ffmpeg 中很多 API 都是靠 AVDictionary 来传递参数的,比如常用的:
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
最后一个参数就是AVDictionary,我们可以在打开码流前指定各种参数,比如:探测时间、超时时间、最大延时、支持的协议的白名单等等,例如:
AVDictionary *options = NULL;
av_dict_set(&options, “probesize”, “4096", 0);
av_dict_set(&options, “max_delay”, “5000000”, 0);
AVFormatContext *ic = avformat_alloc_context();
if (avformat_open_input(&ic, url, NULL, &options) < 0) {
LOGE("could not open source %s", url);
return -1;
}
那么,我们怎么知道 ffmpeg 的这个 API 支持哪些可配置的参数呢 ?
我们可以查看 ffmpeg 源码,比如 avformat_open_input 是结构体 AVFormatContext 提供的 API,在 libavformat/options_table.h 中定义了 AVFormatContext 所有支持的 options 选项,如下所示:doxygen文档
同理,AVCodec 相关 API 支持的 options 选项则可以在 libavcodec/options_table.h 文件中找到,如下所示:doxygen文档
3. AVDictionary的源码学习
主要看一下以下几个函数的实现:
- av_dict_get
- av_dict_set
- av_dict_set_int
- av_dict_copy
- av_dict_free
3.1 av_dict_get
av_dict_get的逻辑比较简单,代码如下所示。
//dict.c
AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key,
const AVDictionaryEntry *prev, int flags)
{
unsigned int i, j;
if (!m)
return NULL;
if (prev)
i = prev - m->elems + 1;
else
i = 0;
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++)
;
if (key[j])
continue;
if (s[j] && !(flags & AV_DICT_IGNORE_SUFFIX))
continue;
return &m->elems[i];
}
return NULL;
}
- 如果prev不为空,则从prev之后的条目开始检索,
prev - m->elems + 1
为prev的下一个条目的索引。 - 如果flags中包含AV_DICT_MATCH_CASE则直接对key的每个字符进行比较,否则都转换成大写的字符进行比较。
- 比较过程中,
if (key[j]) continue;
表示传入的key有未匹配完的字符即跟当前条目的key不匹配,所以再去跟下一个条目进行匹配。 - 当
if (s[j] && !(flags & AV_DICT_IGNORE_SUFFIX)) continue;
表示传入的key是当前条目的key的前缀,但不是按照AV_DICT_IGNORE_SUFFIX的方式进行匹配,即这个条目与检索的key是不匹配的,所以再去跟下一个条目进行匹配。
3.2 av_dict_set
av_dict_set逻辑要稍微复杂一些,源码如下所示。
//dict.c
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;
if (!(flags & AV_DICT_MULTIKEY)) {
tag = av_dict_get(m, key, NULL, flags);
}
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);
if (!m)
m = *pm = av_mallocz(sizeof(*m));
if (!m || (key && !copy_key) || (value && !copy_value))
goto err_out;
if (tag) {
if (flags & AV_DICT_DONT_OVERWRITE) {
av_free(copy_key);
av_free(copy_value);
return 0;
}
if (flags & AV_DICT_APPEND)
oldval = tag->value;
else
av_free(tag->value);
av_free(tag->key);
*tag = m->elems[--m->count];
} else if (copy_value) {
AVDictionaryEntry *tmp = av_realloc(m->elems,
(m->count + 1) * sizeof(*m->elems));
if (!tmp)
goto err_out;
m->elems = tmp;
}
if (copy_value) {
m->elems[m->count].key = copy_key;
m->elems[m->count].value = copy_value;
if (oldval && flags & AV_DICT_APPEND) {
size_t len = strlen(oldval) + strlen(copy_value) + 1;
char *newval = av_mallocz(len);
if (!newval)
goto err_out;
av_strlcat(newval, oldval, len);
av_freep(&oldval);
av_strlcat(newval, copy_value, len);
m->elems[m->count].value = newval;
av_freep(©_value);
}
m->count++;
} 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);
}
- 如果不支持AV_DICT_MULTIKEY,则在字典中检索key是否有对应的条目,保存为变量tag。
if (!(flags & AV_DICT_MULTIKEY)) {
tag = av_dict_get(m, key, NULL, flags);
}
- 如果支持AV_DICT_DONT_STRDUP_KEY,则key指向的字符串不进行拷贝。
if (flags & AV_DICT_DONT_STRDUP_KEY)
copy_key = (void *)key;
else
copy_key = av_strdup(key);
- 如果传入的字典为NULL,则创建一个AVDictionary。
if (!m)
m = *pm = av_mallocz(sizeof(*m));
- 如果key在字典中已经有对应的条目,并且flags中包含AV_DICT_DONT_OVERWRITE选项,即不覆盖已有的条目,则不对字典进行修改,释放key和value的内存。
if (tag) {
if (flags & AV_DICT_DONT_OVERWRITE) {
av_free(copy_key);
av_free(copy_value);
return 0;
}
- 如果key在字典中已经有对应的条目,并且flags中不包含AV_DICT_DONT_OVERWRITE选项,如果flags包含AV_DICT_APPEND的选项,则把tag->value赋给oldval,后续拼接使用。寻找到的条目是elems数组的一个元素,把一项的值赋值为最后一个条目的值,即修改的这个条目出现在elems数组的最后一个元素。
if (tag) {
...
if (flags & AV_DICT_APPEND)
oldval = tag->value;
else
av_free(tag->value);
av_free(tag->key);
*tag = m->elems[--m->count];
- 如果key在字典中没有对应的条目且value不为NULL,则需要对原有的elems数组扩容,增加一项。
} else if (copy_value) {
AVDictionaryEntry *tmp = av_realloc(m->elems,
(m->count + 1) * sizeof(*m->elems));
if (!tmp)
goto err_out;
m->elems = tmp;
}
- 如果value不为NULL,设置的key和value放置在elems最后一个元素,如果flags包含AV_DICT_APPEND,则需要对oldval和value进行拼接。如果value为NULL,则释放掉key的内存,即删除了这个key匹配到的条目。
if (copy_value) {
m->elems[m->count].key = copy_key;
m->elems[m->count].value = copy_value;
if (oldval && flags & AV_DICT_APPEND) {
size_t len = strlen(oldval) + strlen(copy_value) + 1;
char *newval = av_mallocz(len);
if (!newval)
goto err_out;
av_strlcat(newval, oldval, len);
av_freep(&oldval);
av_strlcat(newval, copy_value, len);
m->elems[m->count].value = newval;
av_freep(©_value);
}
m->count++;
} else {
av_freep(©_key);
}
- 如果条目个数为0了,则释放elems和AVDictionary的内存。
if (!m->count) {
av_freep(&m->elems);
av_freep(pm);
}
3.3 av_dict_set_int
av_dict_set_int的逻辑很简单
//dict.c
int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value,
int flags)
{
char valuestr[22];
snprintf(valuestr, sizeof(valuestr), "%"PRId64, value);
flags &= ~AV_DICT_DONT_STRDUP_VAL;
return av_dict_set(pm, key, valuestr, flags);
}
- 把int通过snprintf转换成字符串的形式,需要把flags中的AV_DICT_DONT_STRDUP_VAL位抹掉,因为valuestr是一个局部变量,需要进行拷贝,再调用av_dict_set把key和value保存在字典中。
3.4 av_dict_copy
//dict.c
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;
}
- 遍历src字典中的每个条目,然后设置到dst字典中。
3.5 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);
}
- 释放每个条目的key和value的内存、释放条目数组elems的内存、释放AVDictionary 字典的内存。
4. 总结
AVDictionary是一个健值对存储工具,内部是通过数组实现的。
- 通过av_dict_set保存一个key、value对到字典AVDictionary中,如果条目中没有对应的key,则新增一个条目;如果有则根据flags决定是否覆盖;如果value为NULL,则删除key匹配的第一个条目。
- 通过av_dict_get获取key匹配的第一个条目。
- av_dict_set和av_dict_get中的flags参数包含不同的匹配方式和设置选项。
- AV_DICT_MATCH_CASE:匹配大小写
- AV_DICT_IGNORE_SUFFIX:匹配时忽略后缀)
- AV_DICT_DONT_STRDUP_KEY:不对key进行拷贝
- AV_DICT_DONT_STRDUP_VAL:不对value进行拷贝
- AV_DICT_DONT_OVERWRITE:不覆盖现有条目
- AV_DICT_APPEND:如果目已经存在,则value值直接拼接到之前的值的后面。
- AV_DICT_MULTIKEY:允许在字典中存储相等的key。
[2]中提到AVDictionary在实现上和API上都是低效的,它不具有伸缩性,使用字典很大时速度非常慢,在允许的情况下建议新代码使用tree容器,它使用AVL树来实现O(log n)性能。