目錄
- AVDictionary的使用介紹
- FFmpeg中AVDictionary的使用
- AVDictionary的源碼學(xué)習(xí)
- 總結(jié)
參考
1. AVDictionary的使用介紹
AVDictionary是一個(gè)健值對(duì)存儲(chǔ)工具檀咙,類似于c++中的map,ffmpeg中有很多 API 通過它來傳遞參數(shù)璃诀。
AVDictionary 的定義如下:
//libavutil/dict.h
typedef struct AVDictionaryEntry {
char *key;
char *value;
} AVDictionaryEntry;
struct AVDictionary {
int count;
AVDictionaryEntry *elems;
};
1.1. 基本用法
- 創(chuàng)建AVDictionary:將值為NULL的AVDictionary 指針變量的地址傳遞給av_dict_set()弧可。
- AVDictionary結(jié)構(gòu)體的具體實(shí)現(xiàn)沒有在接口中暴露,不知道AVDictionary結(jié)構(gòu)體占用的內(nèi)存大小劣欢,所以使用者無法直接定義一個(gè)AVDictionary的變量棕诵,使用時(shí)需要定義一個(gè)AVDictionary指針的變量。
-
int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags)
凿将,如果*pm == NULL
年鸳,會(huì)創(chuàng)建一個(gè)AVDictionary結(jié)構(gòu)體,把地址賦值給*pm
丸相。av_dict_set函數(shù)的完整簽名:
AVDictionary *d = NULL;
av_dict_set(&d, "version", "1.0", AV_DICT_MATCH_CASE);
- flags可以設(shè)為不同的選項(xiàng)的組合搔确,包含AV_DICT_MATCH_CASE時(shí)表示key的匹配是要區(qū)分大小寫的,默認(rèn)是不區(qū)分的,這一點(diǎn)要特別注意膳算。
- 添加key-value對(duì)座硕。
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不應(yīng)該被修改否則會(huì)發(fā)生未定義的行為华匾。
檢索條目
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及其所有內(nèi)容。
av_dict_free(&d);
1.2 flags的用法
av_dict_get()和av_dict_set()函數(shù)都有一個(gè)flags參數(shù)机隙,下面對(duì)它的作用進(jìn)行介紹蜘拉,flags包含下面7個(gè)選項(xiàng),可以進(jìn)行組合使用:
#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是需要區(qū)分大小寫有鹿,默認(rèn)是不區(qū)分大小寫的旭旭,這點(diǎn)需要特別注意。看一下如下的示例葱跋。
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在不設(shè)置AV_DICT_MATCH_CASE的情況下持寄,如果key值與字典中的key值在忽略大小寫的情況下是匹配的,會(huì)覆蓋字典中匹配條目的key和value娱俺。
- AV_DICT_IGNORE_SUFFIX:如果key值是字典中某個(gè)條目key值的前綴稍味,則認(rèn)為是匹配的。
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的選項(xiàng)荠卷,所以key的匹配是按忽略后綴的方式(key是字典中條目的key的前綴)進(jìn)行的模庐,"ver"字符串是"version"字符串的前綴,所以獲取到字典中key為"version"的條目油宜。而“”空字符串是所有字符串的前綴掂碱,所以獲取到字典中的第一個(gè)條目。
- AV_DICT_DONT_STRDUP_KEY或AV_DICT_DONT_STRDUP_VAL:接管使用key或value指針指向的內(nèi)存的管理權(quán)验庙,不對(duì)key或value的值進(jìn)行復(fù)制顶吮。如下面示例所示。
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:不覆蓋現(xiàn)有條目粪薛。如下面示例所示悴了。
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);
輸出結(jié)果為:version: 1.0
- AV_DICT_APPEND:如果目已經(jīng)存在,則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);
輸出結(jié)果為:version: 1.02.0
- AV_DICT_MULTIKEY:允許在字典中存儲(chǔ)幾個(gè)相等的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);
輸出結(jié)果為:
version: 1.0
VERsion: 2.0
version: 3.0
2. FFmpeg中AVDictionary的使用
ffmpeg 中很多 API 都是靠 AVDictionary 來傳遞參數(shù)的才沧,比如常用的:
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
最后一個(gè)參數(shù)就是AVDictionary迈喉,我們可以在打開碼流前指定各種參數(shù),比如:探測時(shí)間温圆、超時(shí)時(shí)間挨摸、最大延時(shí)、支持的協(xié)議的白名單等等岁歉,例如:
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 的這個(gè) API 支持哪些可配置的參數(shù)呢 ?
我們可以查看 ffmpeg 源碼锅移,比如 avformat_open_input 是結(jié)構(gòu)體 AVFormatContext 提供的 API熔掺,在 libavformat/options_table.h 中定義了 AVFormatContext 所有支持的 options 選項(xiàng),如下所示:doxygen文檔
同理非剃,AVCodec 相關(guān) API 支持的 options 選項(xiàng)則可以在 libavcodec/options_table.h 文件中找到置逻,如下所示:doxygen文檔
3. AVDictionary的源碼學(xué)習(xí)
主要看一下以下幾個(gè)函數(shù)的實(shí)現(xiàn):
- 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的下一個(gè)條目的索引疯坤。 - 如果flags中包含AV_DICT_MATCH_CASE則直接對(duì)key的每個(gè)字符進(jìn)行比較,否則都轉(zhuǎn)換成大寫的字符進(jìn)行比較深浮。
- 比較過程中压怠,
if (key[j]) continue;
表示傳入的key有未匹配完的字符即跟當(dāng)前條目的key不匹配,所以再去跟下一個(gè)條目進(jìn)行匹配飞苇。 - 當(dāng)
if (s[j] && !(flags & AV_DICT_IGNORE_SUFFIX)) continue;
表示傳入的key是當(dāng)前條目的key的前綴菌瘫,但不是按照AV_DICT_IGNORE_SUFFIX的方式進(jìn)行匹配,即這個(gè)條目與檢索的key是不匹配的布卡,所以再去跟下一個(gè)條目進(jìn)行匹配雨让。
3.2 av_dict_set
av_dict_set邏輯要稍微復(fù)雜一些,源碼如下所示忿等。
//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是否有對(duì)應(yīng)的條目,保存為變量tag贸街。
if (!(flags & AV_DICT_MULTIKEY)) {
tag = av_dict_get(m, key, NULL, flags);
}
- 如果支持AV_DICT_DONT_STRDUP_KEY庵寞,則key指向的字符串不進(jìn)行拷貝。
if (flags & AV_DICT_DONT_STRDUP_KEY)
copy_key = (void *)key;
else
copy_key = av_strdup(key);
- 如果傳入的字典為NULL薛匪,則創(chuàng)建一個(gè)AVDictionary捐川。
if (!m)
m = *pm = av_mallocz(sizeof(*m));
- 如果key在字典中已經(jīng)有對(duì)應(yīng)的條目,并且flags中包含AV_DICT_DONT_OVERWRITE選項(xiàng)逸尖,即不覆蓋已有的條目古沥,則不對(duì)字典進(jìn)行修改瘸右,釋放key和value的內(nèi)存。
if (tag) {
if (flags & AV_DICT_DONT_OVERWRITE) {
av_free(copy_key);
av_free(copy_value);
return 0;
}
- 如果key在字典中已經(jīng)有對(duì)應(yīng)的條目岩齿,并且flags中不包含AV_DICT_DONT_OVERWRITE選項(xiàng)尊浓,如果flags包含AV_DICT_APPEND的選項(xiàng),則把tag->value賦給oldval纯衍,后續(xù)拼接使用栋齿。尋找到的條目是elems數(shù)組的一個(gè)元素,把一項(xiàng)的值賦值為最后一個(gè)條目的值襟诸,即修改的這個(gè)條目出現(xiàn)在elems數(shù)組的最后一個(gè)元素瓦堵。
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在字典中沒有對(duì)應(yīng)的條目且value不為NULL,則需要對(duì)原有的elems數(shù)組擴(kuò)容歌亲,增加一項(xiàng)菇用。
} 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,設(shè)置的key和value放置在elems最后一個(gè)元素陷揪,如果flags包含AV_DICT_APPEND惋鸥,則需要對(duì)oldval和value進(jìn)行拼接。如果value為NULL悍缠,則釋放掉key的內(nèi)存卦绣,即刪除了這個(gè)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);
}
- 如果條目個(gè)數(shù)為0了飞蚓,則釋放elems和AVDictionary的內(nèi)存滤港。
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轉(zhuǎn)換成字符串的形式,需要把flags中的AV_DICT_DONT_STRDUP_VAL位抹掉趴拧,因?yàn)関aluestr是一個(gè)局部變量溅漾,需要進(jìn)行拷貝,再調(diào)用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字典中的每個(gè)條目添履,然后設(shè)置到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);
}
- 釋放每個(gè)條目的key和value的內(nèi)存脑又、釋放條目數(shù)組elems的內(nèi)存暮胧、釋放AVDictionary 字典的內(nèi)存。
4. 總結(jié)
AVDictionary是一個(gè)健值對(duì)存儲(chǔ)工具挂谍,內(nèi)部是通過數(shù)組實(shí)現(xiàn)的叔壤。
- 通過av_dict_set保存一個(gè)key、value對(duì)到字典AVDictionary中口叙,如果條目中沒有對(duì)應(yīng)的key炼绘,則新增一個(gè)條目;如果有則根據(jù)flags決定是否覆蓋妄田;如果value為NULL俺亮,則刪除key匹配的第一個(gè)條目驮捍。
- 通過av_dict_get獲取key匹配的第一個(gè)條目。
- av_dict_set和av_dict_get中的flags參數(shù)包含不同的匹配方式和設(shè)置選項(xiàng)脚曾。
- AV_DICT_MATCH_CASE:匹配大小寫
- AV_DICT_IGNORE_SUFFIX:匹配時(shí)忽略后綴)
- AV_DICT_DONT_STRDUP_KEY:不對(duì)key進(jìn)行拷貝
- AV_DICT_DONT_STRDUP_VAL:不對(duì)value進(jìn)行拷貝
- AV_DICT_DONT_OVERWRITE:不覆蓋現(xiàn)有條目
- AV_DICT_APPEND:如果目已經(jīng)存在东且,則value值直接拼接到之前的值的后面。
- AV_DICT_MULTIKEY:允許在字典中存儲(chǔ)相等的key本讥。
[2]中提到AVDictionary在實(shí)現(xiàn)上和API上都是低效的珊泳,它不具有伸縮性,使用字典很大時(shí)速度非常慢拷沸,在允許的情況下建議新代碼使用tree容器色查,它使用AVL樹來實(shí)現(xiàn)O(log n)性能。