[FFmpeg-devel] [PATCH v3] libavutil: Add AVMap

Michael Niedermayer michael at niedermayer.cc
Thu Apr 17 19:52:41 EEST 2025


AVL Tree based Map

compared to AVDictionary this has
 * clone is O(n) instead of O(n²)
 * copy is O(n*log n) instead of O(n²)
 * O(log n) malloc() calls by default and O(1) if av_map_realloc() is used instead of O(n)
 * get/add/delete is O(log n)
 *
 * You can add (if memory is realloced before) and remove entries while a iterator stays valid
 * copy is atomic, a failure means the dst is unchanged
 *
 * there are restrictions on what compare function can be used on get depending on how the Map was created
 * you can mix case sensitive and case insensitive compare with av_map_supercmp_*
 * Supports binary objects, not just strings

Signed-off-by: Michael Niedermayer <michael at niedermayer.cc>
---
 libavutil/Makefile        |   3 +
 libavutil/map.c           | 415 ++++++++++++++++++++++++++++++++++++++
 libavutil/map.h           | 270 +++++++++++++++++++++++++
 libavutil/tests/map.c     | 221 ++++++++++++++++++++
 libavutil/tree.c          |   6 +-
 libavutil/tree_internal.h |  28 +++
 tests/fate/libavutil.mak  |   4 +
 tests/ref/fate/map        |  27 +++
 8 files changed, 969 insertions(+), 5 deletions(-)
 create mode 100644 libavutil/map.c
 create mode 100644 libavutil/map.h
 create mode 100644 libavutil/tests/map.c
 create mode 100644 libavutil/tree_internal.h
 create mode 100644 tests/ref/fate/map

diff --git a/libavutil/Makefile b/libavutil/Makefile
index 9ef118016bb..3a92748a482 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -81,6 +81,7 @@ HEADERS = adler32.h                                                     \
           replaygain.h                                                  \
           ripemd.h                                                      \
           samplefmt.h                                                   \
+          map.h                                                         \
           sha.h                                                         \
           sha512.h                                                      \
           spherical.h                                                   \
@@ -173,6 +174,7 @@ OBJS = adler32.o                                                        \
        rc4.o                                                            \
        ripemd.o                                                         \
        samplefmt.o                                                      \
+       map.o                                                            \
        side_data.o                                                      \
        sha.o                                                            \
        sha512.o                                                         \
@@ -290,6 +292,7 @@ TESTPROGS = adler32                                                     \
             random_seed                                                 \
             rational                                                    \
             ripemd                                                      \
+            map                                                         \
             sha                                                         \
             sha512                                                      \
             side_data_array                                             \
diff --git a/libavutil/map.c b/libavutil/map.c
new file mode 100644
index 00000000000..59b87a1f074
--- /dev/null
+++ b/libavutil/map.c
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) 2025 Michael Niedermayer
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <inttypes.h>
+#include <string.h>
+
+#include "avassert.h"
+#include "avstring.h"
+#include "error.h"
+#include "mem.h"
+#include "map.h"
+
+#include "tree_internal.h" // For improved readability with AVTreeNode, do NOT touch AVTreeNode internals
+
+typedef struct{
+    AVMapEntry map_entry;
+    uint8_t treenode_and_keyvalue[0];
+} AVMapInternalEntry;
+
+struct AVMap{
+#define CMP_MASK 31
+    AVMapCompareFunc cmp[27];
+    AVMapCopyFunc copy;
+    AVMapFreeFunc freef;
+    int count;
+    int deleted;
+    int next;                   ///< index of entry in root array after all used entries
+    unsigned internal_entries_len;
+    AVTreeNode *tree_root;
+    AVMapInternalEntry *internal_entries;
+};
+
+static uint8_t deleted_entry;
+
+static inline int internal_entry_len(AVMapInternalEntry *I) {
+    return (I->map_entry.keylen + I->map_entry.valuelen + sizeof (*I) + sizeof(AVTreeNode) - 1) / sizeof (*I) + 1;
+}
+
+static inline AVTreeNode * internal_treenode(const AVMapInternalEntry *I)
+{
+    return (AVTreeNode *)I->treenode_and_keyvalue;
+}
+
+static inline uint8_t * internal_key(AVMapInternalEntry *I)
+{
+    return I->treenode_and_keyvalue + sizeof(AVTreeNode);
+}
+
+static inline uint8_t * internal_value(AVMapInternalEntry *I)
+{
+    return I->treenode_and_keyvalue + sizeof(AVTreeNode) + I->map_entry.keylen;
+}
+
+static inline AVMapInternalEntry * keyvalue2internal(const uint8_t *keyvalue)
+{
+    return (AVMapInternalEntry*)(keyvalue - offsetof(AVMapInternalEntry, treenode_and_keyvalue) - sizeof(AVTreeNode));
+}
+
+int av_map_strcmp_keyvalue(const char *a, const char *b)
+{
+    int v = strcmp(a,b);
+    if(!v)
+        v = strcmp(a + strlen(a) + 1, b + strlen(a) + 1); // please optimize this dear compiler, we know the strlen after strcmp()
+    return v;
+}
+
+int av_map_supercmp_key(const char *a, const char *b)
+{
+    int v = av_strcasecmp(a,b);
+    if (!v)
+        v = strcmp(a,b);
+
+    return v;
+}
+
+int av_map_supercmp_keyvalue(const char *a, const char *b)
+{
+    int v = av_map_supercmp_key(a,b);
+    if (!v)
+        v = strcmp(a + strlen(a) + 1, b + strlen(a) + 1);
+
+    return v;
+}
+
+int av_map_add_cmp_func(AVMap *m, AVMapCompareFunc cmp, int cmp_flags)
+{
+    static const uint8_t sensitivity[27][3] = {
+        {0,0, 0},{1,0, 0},{2,0, 0}, {0,3, 0},{1,3, 0},{2,3, 0}, {0,6, 0},{1,6, 0},{2,6, 0},
+        {0,0, 9},{1,0, 9},{2,0, 9}, {0,3, 9},{1,3, 9},{2,3, 9}, {0,6, 9},{1,6, 9},{2,6, 9},
+        {0,0,18},{1,0,18},{2,0,18}, {0,3,18},{1,3,18},{2,3,18}, {0,6,18},{1,6,18},{2,6,18},};
+    int case_sensitive       =  sensitivity[cmp_flags][0];
+    int keyvalue_sensitive   =  sensitivity[cmp_flags][1];
+    int truncated_sensitive  =  sensitivity[cmp_flags][2];
+
+    if (!keyvalue_sensitive || !truncated_sensitive || cmp_flags >= 27U)
+        return AVERROR(EINVAL);
+
+    av_assert1(case_sensitive + keyvalue_sensitive + truncated_sensitive == cmp_flags);
+
+    if (     case_sensitive == AV_MAP_CMP_CASE_SENSITIVE && m->cmp[keyvalue_sensitive + AV_MAP_CMP_CASE_INSENSITIVE])
+        return AVERROR(EINVAL);
+    if ( keyvalue_sensitive == AV_MAP_CMP_KEYVALUE       && m->cmp[AV_MAP_CMP_KEY])
+        return AVERROR(EINVAL);
+    if (truncated_sensitive == AV_MAP_CMP_NON_TRUNCATED  && m->cmp[keyvalue_sensitive + AV_MAP_CMP_TRUNCATED])
+        return AVERROR(EINVAL);
+
+    //max functions is KV NT CS -> KV NT CI -> KV T CI (CI/T is about value only) -> K NT CS -> K NT CI -> K T CI
+    //missing is KV T CS and K T CS, with them we can have KV NT CS -> KV T CS -> K NT CS -> K T CS
+
+    for (int i=0; i<8; i++) {
+        int flags = 0;
+        if (i&1) flags += case_sensitive;
+        if (i&2) flags += keyvalue_sensitive;
+        if (i&4) flags += truncated_sensitive;
+
+        if (!m->cmp[flags])
+            m->cmp[flags] = cmp;
+    }
+    return 0;
+}
+
+int av_map_is_cmp_flags_supported(AVMap *m, int cmp_flags)
+{
+    if (cmp_flags >= 27U)
+        return AVERROR(EINVAL);
+    return !!m->cmp[cmp_flags];
+}
+
+AVMap *av_map_new(AVMapCompareFunc cmp_keyvalue, int cmp_flags, AVMapCopyFunc copy, AVMapFreeFunc freef)
+{
+    AVMap *s = av_mallocz(sizeof(*s));
+    if (!s)
+        return NULL;
+
+    s->copy          = copy;
+    s->freef         = freef;
+
+    av_map_add_cmp_func(s, cmp_keyvalue, cmp_flags);
+
+    return s;
+}
+
+const AVMapEntry *av_map_get_multiple(const AVMap *s, const AVMapEntry *prev, const char *keyvalue, int flags)
+{
+    AVMapCompareFunc cmp = s->cmp[flags & CMP_MASK];
+
+    if (prev) {
+        void *next_node[2] = { NULL, NULL };
+        void *prev_keyvalue = av_tree_find2(s->tree_root, prev->key, s->cmp[0], next_node, 2);
+        av_assert2(prev_keyvalue);
+        if (!next_node[1] || cmp(next_node[1], keyvalue))
+            return NULL;
+
+        keyvalue = next_node[1];
+    } else {
+        void *next_node[4] = { NULL, NULL, NULL, NULL };
+        keyvalue = av_tree_find2(s->tree_root, keyvalue, cmp, next_node, 4);
+        if (next_node[2]) // If we have a leftmost equal keyvalue, use it instead
+            keyvalue = next_node[2];
+    }
+
+    if (!keyvalue)
+        return NULL;
+
+    return &keyvalue2internal(keyvalue)->map_entry;
+}
+
+const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue, int flags)
+{
+    AVMapCompareFunc cmp = s->cmp[flags & CMP_MASK];
+
+    keyvalue = av_tree_find2(s->tree_root, keyvalue, cmp, NULL, 0);
+
+    if (!keyvalue)
+        return NULL;
+
+    return &keyvalue2internal(keyvalue)->map_entry;
+}
+
+static void update_pointers(AVMap *dst, AVMapInternalEntry *dst_internal_entries, const AVMapInternalEntry *src_internal_entries)
+{
+    AVTreeNode *src_tree_root = dst->tree_root;
+
+    if (src_tree_root) {
+        dst->tree_root = src_tree_root - internal_treenode(src_internal_entries) + internal_treenode(dst_internal_entries);
+        av_tree_move(dst->tree_root, src_tree_root, dst_internal_entries, src_internal_entries);
+    }
+
+    //TODO We could attempt to compact free space
+    for(int i = 0; i<dst->next; i++) {
+        if (dst_internal_entries[i].map_entry.key != &deleted_entry) {
+            dst_internal_entries[i].map_entry.key   = internal_key  (dst_internal_entries + i);
+            dst_internal_entries[i].map_entry.value = internal_value(dst_internal_entries + i);
+        }
+        i += internal_entry_len(dst_internal_entries + i) - 1;
+    }
+    dst->internal_entries = dst_internal_entries;
+}
+
+int av_map_realloc(AVMap *s, int extra_elements, int extra_space) {
+    int64_t advance = extra_elements + (extra_space + (int64_t)extra_elements*(sizeof(*s->internal_entries) + sizeof(AVTreeNode) - 1)) / sizeof(*s->internal_entries);
+
+    if (advance > (INT32_MAX - s->next) / sizeof(AVMapInternalEntry))
+        return AVERROR(ENOMEM);
+
+    AVMapInternalEntry *new_internal_entries = av_fast_realloc(s->internal_entries, &s->internal_entries_len, (s->next + advance) * sizeof(AVMapInternalEntry));
+
+    if (!new_internal_entries)
+        return AVERROR(ENOMEM);
+
+    if (new_internal_entries != s->internal_entries) {
+        update_pointers(s, new_internal_entries, s->internal_entries);
+    }
+    return advance;
+}
+
+int av_map_add(AVMap *s, const char *key, int keylen, const char *value, int valuelen, int flags)
+{
+    av_assert2(keylen || valuelen); // patch welcome but how would the compare function compare a len=0 element without knowing it is a len 0 element
+
+    int advance = av_map_realloc(s, 1, keylen + valuelen);
+    if (advance < 0)
+        return advance;
+
+    AVMapEntry *entry = &s->internal_entries[s->next].map_entry;
+    AVTreeNode *next = internal_treenode(s->internal_entries + s->next);
+    memset(next, 0, sizeof(*next));
+    entry->keylen  = keylen;
+    entry->valuelen= valuelen;
+    entry->key     = internal_key  (s->internal_entries + s->next);
+    entry->value   = internal_value(s->internal_entries + s->next);
+    memcpy(entry->key  , key  , keylen);
+    memcpy(entry->value, value, valuelen);
+
+    void *elem = av_tree_insert(&s->tree_root, entry->key, s->cmp[0], &next);
+    int ret = 1;
+    if (elem != entry->key && elem) {
+        av_assert2(next);
+        //we assume that new entries are more common than replacements
+        if (flags & AV_MAP_REPLACE) {
+            ret = av_map_del(s, entry->key, flags & ~CMP_MASK);
+            av_assert2(ret == 1);
+            elem = av_tree_insert(&s->tree_root, entry->key, s->cmp[0], &next);
+            av_assert2(elem == entry->key || !elem);
+            ret = 2;
+        } else
+            return 0; //entry already in the map
+    }
+    av_assert2(!next);
+    av_assert2(s->tree_root);
+    s->next += advance;
+    s->count++;
+
+    return ret;
+}
+
+int av_map_add_strings(AVMap *s, const char *key, const char *value, int flags)
+{
+    return av_map_add(s, key, strlen(key)+1, value, strlen(value)+1, flags);
+}
+
+int av_map_del(AVMap *s, const char *keyvalue, int flags)
+{
+    uint8_t *old_keyvalue;
+    AVTreeNode *next = NULL;
+    AVMapCompareFunc cmp = s->cmp[flags & CMP_MASK];
+
+    if (cmp != s->cmp[0]) {
+        // The user asks us to remove a entry with a compare function different from the one used to build the map
+        // we need to do 2 calls here, first with the users compare to find the entry she wants to remove
+        // and then to remove it while maintaining the correct order within the map
+        old_keyvalue = av_tree_find2(s->tree_root, keyvalue, cmp, NULL, 0);
+        if (!old_keyvalue)
+            return 0;
+
+        av_tree_insert(&s->tree_root, old_keyvalue, s->cmp[0], &next);
+        av_assert2(next);
+    } else {
+        av_tree_insert(&s->tree_root, (char*)keyvalue, s->cmp[0], &next);
+        if (!next)
+            return 0;
+        old_keyvalue = next->elem; //TODO add a API to av_tree() to return the elem of a AVTreeNode
+
+    }
+    AVMapInternalEntry *internal_entry = keyvalue2internal(old_keyvalue);
+    internal_entry->map_entry.key = &deleted_entry;
+
+    s->count--;
+    s->deleted++;
+
+    if ((flags & AV_MAP_ALLOW_REBUILD) && s->deleted > s->count) {
+        AVMap *news = av_map_new(s->cmp[0], AV_MAP_CMP_KEYVALUE + AV_MAP_CMP_NON_TRUNCATED, s->copy, s->freef);
+        if(news) {
+            memcpy(news->cmp, s->cmp, sizeof(news->cmp));
+            int ret = av_map_copy(news, s);
+            if (ret < 0) {
+                av_map_free(&news);
+            } else {
+                if (s->freef)
+                    for (int i=0; i<s->count; i++)
+                        s->freef(&s->internal_entries[i].map_entry);
+                av_freep(&s->internal_entries);
+                memcpy(s, news, sizeof(*s));
+            }
+        }
+    }
+
+    return 1;
+}
+
+const AVMapEntry *av_map_iterate(const AVMap *s,
+                                 const AVMapEntry *prev)
+{
+    AVMapInternalEntry *I;
+    if (prev) {
+        I = (AVMapInternalEntry*)((uint8_t*)prev - offsetof(AVMapInternalEntry, map_entry));
+        I += internal_entry_len(I);
+    } else {
+        I = s->internal_entries;
+    }
+    while (I < s->internal_entries + s->next && I->map_entry.key == &deleted_entry)
+        I += internal_entry_len(I);
+
+    if (I == s->internal_entries + s->next)
+        return NULL;
+
+    return &I->map_entry;
+}
+
+int av_map_count(const AVMap *s)
+{
+    return s->count;
+}
+
+void av_map_free(AVMap **sp)
+{
+    AVMap *s = *sp;
+
+    for (int i=0; i<s->count; i++) {
+        if (s->freef)
+            s->freef(&s->internal_entries[i].map_entry);
+    }
+    av_freep(&s->internal_entries);
+    s->next =
+    s->internal_entries_len =
+    s->count = 0;
+    av_freep(sp);
+}
+
+int av_map_copy(AVMap *dst, const AVMap *src)
+{
+    const AVMapEntry *t = NULL;
+    AVMap *bak = av_memdup(dst, sizeof(*dst));
+    if (!bak)
+        return AVERROR(ENOMEM);
+
+    AVMapInternalEntry *new_internal_entries = av_memdup(bak->internal_entries, bak->internal_entries_len);
+    AVMapInternalEntry *old_internal_entries = dst->internal_entries;
+
+    while ((t = av_map_iterate(src, t))) {
+        int ret = av_map_add(dst, t->key, t->keylen, t->value, t->valuelen, 0);
+
+        if (ret < 0) {
+            update_pointers(bak, new_internal_entries, old_internal_entries);
+            av_free(dst->internal_entries);
+            memcpy(dst, bak, sizeof(*dst));
+            return ret;
+        }
+    }
+
+    av_freep(&new_internal_entries);
+    av_free(bak);
+
+    return 0;
+}
+
+AVMap *av_map_clone(AVMap *s)
+{
+    AVMap *dst = av_memdup(s, sizeof(AVMap));
+
+    if (!dst)
+        return NULL;
+
+    dst->internal_entries = av_memdup(s->internal_entries, s->internal_entries_len);
+
+    if (!dst->internal_entries)
+        goto err;
+
+    update_pointers(dst, dst->internal_entries, s->internal_entries);
+
+    return dst;
+err:
+    if (dst) {
+        av_freep(&dst->internal_entries);
+    }
+    av_free(dst);
+    return NULL;
+}
diff --git a/libavutil/map.h b/libavutil/map.h
new file mode 100644
index 00000000000..03572960306
--- /dev/null
+++ b/libavutil/map.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2025 Michael Niedermayer
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Public map API.
+ */
+
+#ifndef AVUTIL_MAP_H
+#define AVUTIL_MAP_H
+
+#include <stdint.h>
+
+#include "tree.h"
+
+/**
+ * compared to AVDictionary this has
+ * clone is O(n) instead of O(n²)
+ * copy is O(n*log n) instead of O(n²)
+ * O(log n) malloc() calls by default and O(1) if av_map_realloc() is used instead of O(n)
+ * get/add/delete is O(log n)
+ *
+ * You can add (if memory is realloced before) and remove entries while a iterator stays valid
+ * copy is atomic, a failure means the dst is unchanged
+ *
+ * there are restrictions on what compare function can be used on get depending on how the Map was created
+ * you can mix case sensitive and case insensitive compare with av_map_supercmp_*
+ * Supports binary objects, not just strings
+ */
+
+enum {
+//use + not | to combine these flags
+    AV_MAP_CMP_CASE_SENSITIVE   = 1,
+    AV_MAP_CMP_CASE_INSENSITIVE = 2,
+    AV_MAP_CMP_KEY              = 3,
+    AV_MAP_CMP_KEYVALUE         = 6,
+    AV_MAP_CMP_TRUNCATED        = 9,
+    AV_MAP_CMP_NON_TRUNCATED    = 18,
+
+    AV_MAP_ALLOW_REBUILD        = 256,   ///< when removing entries rebuild the map to reduce memory consumption, note, this invalidates previously retrieved elements and iterate state.
+    AV_MAP_REPLACE              = 512,   ///< replace keyvalue if already in the map
+
+};
+
+typedef struct AVMapEntry {
+    uint8_t *key;
+    uint8_t *value;
+    int keylen;
+    int valuelen;
+} AVMapEntry;
+
+typedef struct AVMap AVMap;
+typedef void (* AVMapFreeFunc)(AVMapEntry *c);
+typedef void (* AVMapCopyFunc)(AVMapEntry *dst, const AVMapEntry *src, size_t len);
+typedef int  (* AVMapCompareFunc)(const void *keyvalue, const void *b);
+
+/**
+ * like strcmp() but compares concatenated keyvalues.
+ *
+ * A map initialized with this will allow duplicate keys as long as their values differ.
+ */
+int av_map_strcmp_keyvalue(const char *a, const char *b);
+
+/**
+ * like av_map_strcmp_keyvalue() but is compatible with av_strcasecmp() and av_map_supercmp_key.
+ *
+ * A map initialized with this will allow duplicate keys as long as their values differ.
+ */
+int av_map_supercmp_keyvalue(const char *a, const char *b);
+
+/**
+ * like strcmp() but is compatible with av_strcasecmp().
+ *
+ * A map initialized with this will not allow duplicate keys.
+ */
+int av_map_supercmp_key(const char *a, const char *b);
+
+
+/**
+ *
+ * @param keyvalue_cmp compare function, will be passed the key + value concatenated.
+ *                     it should form a strict total order on all elements you want to store. each key-value pair
+ *                     can only occur once. Though there can be multiple values for the same key. IF this function
+ *                     treats them as different.
+ *
+ *                     if the keyvalue_cmp is inconsistant, for example a < b && b < a, reading may still work as long
+ *                     as the function later used for reading treats all elements in the inconsistant subset as
+ *                     equal this is not supported or recommanded but rather documented for completeness. Deleting
+ *                     elements of such inconsistant subsets should not be expected to work.
+ *
+ * @param freef receives a AVMapEntry and should free any resources except the AVMapEntry->key/value pointer itself
+ *              for flat structs like strings, this is simply NULL
+ *
+ *                          Key     Value   compaibility
+ * av_map_supercmp_keyvalue X!=x    X!=x    av_map_supercmp_key, av_strcasecmp, (trucated av_strcasecmp)
+ * av_map_supercmp_key      X!=x            av_strcasecmp, (trucated av_strcasecmp)
+ * av_strcasecmp            X==x            truncation
+ *
+ * av_map_strcmp_keyvalue   X!=x    X!=x    strcmp, truncation
+ * strcmp                   X!=x            truncation
+ *
+ *
+ */
+AVMap *av_map_new(AVMapCompareFunc keyvalue_cmp, int cmp_flags, AVMapCopyFunc clone, AVMapFreeFunc freef);
+
+
+/**
+ * Add a compatible compare function to the map.
+ * The function will later be selected by using AV_MAP_CMP_* flags
+ *
+ * Functions must be added from most specific to least specific, that is if a AVMap is build
+ * with a case insensitive compare no case sensitive compare functions can be added. This is
+ * to avoid building non functional AVMaps.
+ *
+ *
+ * @see av_map_new
+ *
+ * @param cmp compare function to be added.
+ *            cmp(a,b) must return 0 or be equal to the previously added compare function for (a,b), if it returns 0 it also must do so for all
+ *            elements between a and b
+ *
+ * @param cmp_flags a combination of AV_MAP_CMP_*, note key/keyvalue and truncated vs non truncated
+ *                  are mandatory to be specified
+ *
+ * @return a negative error code if the cmp_flags are illegal or unsupported for this AVMap
+ *         If you know your flags are valid, then you dont need to check the return
+ */
+int av_map_add_cmp_func(AVMap *m, AVMapCompareFunc cmp, int cmp_flags);
+
+/**
+ *
+ * @return 1 if the provided AV_MAP_CMP_* flag combination is supported by this map.
+ *         0 otherwise
+ */
+int av_map_is_cmp_flags_supported(AVMap *m, int cmp_flags);
+
+/**
+ * realloc internal space to accomodate the specified new elements
+ *
+ * This can be used to avoid repeated memory reallocation.
+ *
+ * @param extra_elements number of new elements to be added
+ * @param extra_space    sum of keylen and valuelen of all to be added elements
+ *
+ * @return          <0 on error
+ */
+int av_map_realloc(AVMap *s, int extra_elements, int extra_space);
+
+/**
+ * Add the given entry into a AVMap.
+ *
+ * @param s         Pointer AVMap struct.
+ * @param value     Entry value to add to *s
+ * @param valuelen  length of value
+ * @param flags     0, AV_MAP_ALLOW_REBUILD, AV_MAP_REPLACE
+ *
+ * @return          1 if the entry was added, 0 if it was already in the map, 2 if it was replaced
+ *                  otherwise an error code <0
+ */
+int av_map_add(AVMap *s, const char *key, int keylen, const char *value, int valuelen, int flags);
+int av_map_add_strings(AVMap *s, const char *key, const char *value, int flags);
+
+/**
+ * Delete the given entry from a AVMap.
+ *
+ * @param s         Pointer AVMap struct.
+ * @param keyvalue  key or concatenated key+value
+ * @param flags     AV_MAP_ALLOW_REBUILD or 0
+ *
+ * @return          1 if the entry was deleted, 0 if it was not found in the map
+ *                  otherwise an error code <0
+ */
+int av_map_del(AVMap *s, const char *keyvalue, int flags);
+
+/**
+ * Iterate over possibly multiple matching map entries.
+ *
+ * The returned entry must not be changed, or it will
+ * cause undefined behavior.
+ *
+ * @param prev  Set to the previous matching element to find the next.
+ *              If set to NULL the first matching element is returned.
+ * @param keyvalue Matching key or key + value
+ *
+ * @return      Found entry or NULL in case no matching entry was found in the dictionary
+ */
+const AVMapEntry *av_map_get_multiple(const AVMap *s, const AVMapEntry *prev, const char *keyvalue, int flags);
+
+/**
+ * Like av_map_get_multiple() but only returns one matching entry
+ *
+ * The returned entry cannot be used as initial prev entry for av_map_get_multiple()
+ */
+const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue, int flags);
+
+/**
+ * Iterate over a map
+ *
+ * Iterates through all entries in the map.
+ *
+ * @warning If you call any function with AV_SET_ALLOW_REBUILD set, then the iterator is
+ * invalidated, and must not be used anymore. Otherwise av_map_add() (without realloc) and av_map_del()
+ * can saftely be called during iteration.
+ *
+ * Typical usage:
+ * @code
+ * const AVMapEntry *e = NULL;
+ * while ((e = av_map_iterate(m, e))) {
+ *     // ...
+ * }
+ * @endcode
+ *
+ * @param s     The map to iterate over
+ * @param prev  Pointer to the previous AVMapEntry, NULL initially
+ *
+ * @retval AVMapEntry* The next element in the map
+ * @retval NULL        No more elements in the map
+ */
+const AVMapEntry *av_map_iterate(const AVMap *s,
+                                 const AVMapEntry *prev);
+
+/**
+ * Get number of entries in map.
+ *
+ * @param s map
+ * @return  number of entries in map
+ */
+int av_map_count(const AVMap *s);
+
+/**
+ * Free all the memory allocated for an AVMap struct
+ * and all values.
+ */
+void av_map_free(AVMap **s);
+
+AVMap *av_map_clone(AVMap *s);
+
+/**
+ * Copy entries from one AVMap struct into another.
+ *
+ * @param dst   Pointer to a pointer to a AVMap struct to copy into. If *dst is NULL,
+ *              this function will allocate a struct for you and put it in *dst
+ * @param src   Pointer to the source AVMap struct to copy items from.
+ * @param flags Flags to use when setting entries in *dst
+ *
+ * @see when the initial dst map is empty use av_map_clone() as its faster
+ *
+ * @return 0 on success, negative AVERROR code on failure.
+ */
+
+int av_map_copy(AVMap *dst, const AVMap *src);
+
+#endif /* AVUTIL_MAP_H */
diff --git a/libavutil/tests/map.c b/libavutil/tests/map.c
new file mode 100644
index 00000000000..7705eb31a95
--- /dev/null
+++ b/libavutil/tests/map.c
@@ -0,0 +1,221 @@
+/*
+ * copyright (c) 2025 Michael Niedermayer
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/mem.h"
+#include "libavutil/map.h"
+
+
+static void print_set(const AVMap *s)
+{
+    const AVMapEntry *t = NULL;
+    while ((t = av_map_iterate(s, t)))
+        printf("%s=%s %d,%d   ", t->key, t->value, t->keylen, t->valuelen);
+    printf("\n");
+}
+
+int main(void)
+{
+    void *our_cmp[] = {
+        strcmp,
+        av_map_strcmp_keyvalue,
+        av_strcasecmp,
+        av_map_supercmp_keyvalue,
+        av_map_supercmp_keyvalue,
+    };
+    int our_flags[] = {
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEYVALUE,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_INSENSITIVE + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEYVALUE,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEYVALUE,
+    };
+    void *our_subcmp[] = {
+        strcmp,
+        strcmp,
+        av_strcasecmp,
+        av_map_supercmp_key,
+        av_strcasecmp,
+    };
+    int our_subflags[] = {
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_INSENSITIVE + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_INSENSITIVE + AV_MAP_CMP_KEY,
+    };
+
+    for (int settype=0; settype<3; settype++) {
+        AVMap *set = av_map_new(our_cmp[settype], our_flags[settype], NULL, NULL);
+        av_map_add_cmp_func(set, our_subcmp[settype], our_subflags[settype]);
+
+        printf("testing empty set\n");
+
+        const AVMapEntry *e = av_map_get(set, "foo", our_subflags[settype]);
+        av_assert0(e == NULL);
+
+        e = av_map_get(set, "foo", our_subflags[settype]);
+        av_assert0(e == NULL);
+
+        int ret = av_map_del(set, "foo", our_subflags[settype]);
+        av_assert0(ret == 0);
+
+        print_set(set);
+
+        printf("testing 1-set\n");
+
+        ret = av_map_add(set, "foo", 4, "bar", 4, 0);
+        av_assert0(ret == 1);
+
+        ret = av_map_add(set, "foo", 4, "bear", 5, 0);
+        av_assert0(ret == ((int[]){0,1,0})[settype]);
+
+        e = av_map_get(set, "foo", our_subflags[settype]);
+        av_assert0(!strcmp(e->key, "foo"));
+        if (settype == 1) {
+            av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value, "bar"));
+        } else {
+            av_assert0(!strcmp(e->value, "bar"));
+        }
+
+        ret = av_map_add(set, "foo", 4, "bear", 5, AV_MAP_REPLACE);
+        av_assert0(ret == 2);
+
+        e = av_map_get(set, "foo", our_subflags[settype]);
+        av_assert0(!strcmp(e->key, "foo"));
+        if (settype == 1) {
+            av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value, "bar"));
+        } else {
+            av_assert0(!strcmp(e->value, "bear"));
+        }
+
+        e = av_map_get_multiple(set, NULL, "foo", our_subflags[settype]);
+        av_assert0(!strcmp(e->key, "foo"));
+        if (settype == 1) {
+            av_assert0(!strcmp(e->value, "bar"));
+        } else {
+            av_assert0(!strcmp(e->value, "bear"));
+        }
+        e = av_map_get_multiple(set, e, "foo", our_subflags[settype]);
+        if (settype == 1) {
+            av_assert0(!strcmp(e->key, "foo"));
+            av_assert0(!strcmp(e->value, "bear"));
+        } else {
+            av_assert0(e == NULL);
+        }
+
+        ret = av_map_del(set, "foo", our_subflags[settype]);
+        av_assert0(ret == 1);
+
+        e = av_map_get(set, "foo", our_subflags[settype]);
+        if (settype == 1) {
+            av_assert0(!strcmp(e->key, "foo"));
+            av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value, "bar"));
+        } else {
+            av_assert0(e == NULL);
+        }
+
+        ret = av_map_del(set, "foo", our_subflags[settype]);
+        av_assert0(ret == ((int[]){0,1,0})[settype]);
+
+
+        print_set(set);
+
+        printf("testing n-set\n");
+        unsigned r = 5;
+        int histogram[256] = {0};
+        for(int i=0; i<1000; i++) {
+            r = r*123 + 7;
+            unsigned char str[3] = {0};
+            str[0] = r;
+            ret = av_map_add(set, str, 2, str, 2 ,0);
+            if (i < 128) {
+                if (settype != 2) {
+                    av_assert0(ret == 1);
+                } else {
+                    av_assert0(ret == !histogram[av_toupper(str[0])]);
+                    histogram[av_toupper(str[0])] = 1;
+                }
+            } else {
+                av_assert0(ret == 0);
+            }
+            printf("%d", ret);
+        }
+        printf("\n");
+
+        r = 5;
+        for(int i=0; i<1000; i++) {
+            r = r*123 + 7;
+            char str[3] = {0};
+            str[0] = r;
+
+            if (i == 51) {
+                AVMap *old = set;
+                set = av_map_clone(set);
+                av_map_del(old, str, 0);
+                av_map_free(&old);
+            }
+            if (i == 73) {
+                AVMap *old = set;
+                set = av_map_new(our_cmp[settype], our_flags[settype], NULL, NULL);
+                av_map_add_cmp_func(set, our_subcmp[settype], our_subflags[settype]);
+                ret = av_map_add_strings(set, "the key", "the value", 0);
+                av_map_copy(set, old);
+                av_map_del(old, str, 0);
+                av_map_free(&old);
+            }
+            e = av_map_get(set, str, our_subflags[settype]);
+            if (settype != 2) {
+                av_assert0(!strcmp(e->key, str));
+                av_assert0(!strcmp(e->value, str));
+            } else {
+                av_assert0(!av_strcasecmp(e->key, str));
+                av_assert0(!av_strcasecmp(e->value, str));
+            }
+            e = av_map_get_multiple(set, NULL, str, our_subflags[settype]);
+            if (settype != 2) {
+                av_assert0(!strcmp(e->key, str));
+                av_assert0(!strcmp(e->value, str));
+            } else {
+                av_assert0(!av_strcasecmp(e->key, str));
+                av_assert0(!av_strcasecmp(e->value, str));
+            }
+            ret = av_map_add(set, str, 2, str, 2, 0);
+            av_assert0(ret == 0);
+
+            str[1]='x';
+
+            e = av_map_get(set, str, our_subflags[settype]);
+            av_assert0(e == NULL);
+            e = av_map_get_multiple(set, NULL, str, our_subflags[settype]);
+            av_assert0(e == NULL);
+        }
+        print_set(set);
+
+        av_map_free(&set);
+        av_assert0(!set);
+    }
+
+    return 0;
+}
diff --git a/libavutil/tree.c b/libavutil/tree.c
index 708d4013f04..3007dc26d29 100644
--- a/libavutil/tree.c
+++ b/libavutil/tree.c
@@ -24,11 +24,7 @@
 #include "mem.h"
 #include "tree.h"
 
-typedef struct AVTreeNode {
-    struct AVTreeNode *child[2];
-    void *elem;
-    int state;
-} AVTreeNode;
+#include "tree_internal.h"
 
 const int av_tree_node_size = sizeof(AVTreeNode);
 
diff --git a/libavutil/tree_internal.h b/libavutil/tree_internal.h
new file mode 100644
index 00000000000..6207c321a8e
--- /dev/null
+++ b/libavutil/tree_internal.h
@@ -0,0 +1,28 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_TREE_INTERNAL_H
+#define AVUTIL_TREE_INTERNAL_H
+
+typedef struct AVTreeNode {
+    struct AVTreeNode *child[2];
+    void *elem;
+    int state;
+} AVTreeNode;
+
+#endif /* AVUTIL_TREE_INTERNAL_H */
diff --git a/tests/fate/libavutil.mak b/tests/fate/libavutil.mak
index 6bf03b24386..b7df499a29c 100644
--- a/tests/fate/libavutil.mak
+++ b/tests/fate/libavutil.mak
@@ -74,6 +74,10 @@ FATE_LIBAVUTIL += fate-dict
 fate-dict: libavutil/tests/dict$(EXESUF)
 fate-dict: CMD = run libavutil/tests/dict$(EXESUF)
 
+FATE_LIBAVUTIL += fate-map
+fate-map: libavutil/tests/map$(EXESUF)
+fate-map: CMD = run libavutil/tests/map$(EXESUF)
+
 FATE_LIBAVUTIL += fate-encryption-info
 fate-encryption-info: libavutil/tests/encryption_info$(EXESUF)
 fate-encryption-info: CMD = run libavutil/tests/encryption_info$(EXESUF)
diff --git a/tests/ref/fate/map b/tests/ref/fate/map
new file mode 100644
index 00000000000..6d1699ef4b7
--- /dev/null
+++ b/tests/ref/fate/map
@@ -0,0 +1,27 @@
+testing empty set
+
+testing 1-set
+
+testing n-set
+1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+the key=the value 8,10   n=n 2,2   á=á 2,2   "=" 2,2   ]=] 2,2   ¶=¶ 2,2   y=y 2,2   *=* 2,2   5=5 2,2   ~=~ 2,2   ‘=‘ 2,2   ²=² 2,2   = 2,2   Æ=Æ 2,2   )=) 2,2   º=º 2,2   e=e 2,2   Ž=Ž 2,2   A=A 2,2   B=B 2,2   ½=½ 2,2   Ö=Ö 2,2   Ù=Ù 2,2   J=J 2,2   •=• 2,2   ž=ž 2,2   ñ=ñ 2,2   Ò=Ò 2,2   í=í 2,2   æ=æ 2,2   ‰=‰ 2,2   Ú=Ú 2,2   Å=Å 2,2   ®=® 2,2   ¡=¡ 2,2   b=b 2,2   = 2,2   ö=ö 2,2   9=9 2,2   j=j 2,2   õ=õ 2,2   ¾=¾ 2,2   Q=Q 2,2   ò=ò 2,2   M=M 2,2   = 2,2   é=é 2,2   ú=ú 2,2   %=% 2,2   Î=Î 2,2   = 2,2   ‚=‚ 2,2   }=} 2,2   = 2,2   ™=™ 2,2   Š=Š 2,2   U=U 2,2   Þ=Þ 2,2   ±=± 2,2   = 2,2   ­=­ 2,2   &=& 2,2   I=I 2,2   = 2,2   …=… 2,2   î=î 2,2   a=a 2,2   ¢=¢ 2,2   Ý=Ý 2,2   6=6 2,2   ù=ù 2,2   ª=ª 2,2   µ=µ 2,2   þ=þ 2,2   = 2,2   2=2 2,2   
=
 2,2   F=F 2,2   ©=© 2,2   :=: 2,2   å=å 2,2   = 2,2   Á=Á 2,2   Â= 2,2   === 2,2   V=V 2,2   Y=Y 2,2   Ê=Ê 2,2   = 2,2   = 2,2   q=q 2,2   R=R 2,2   m=m 2,2   f=f 2,2   	=	 2,2   Z=Z 2,2   E=E 2,2   .=. 2,2   !=! 2,2   â=â 2,2   = 2,2   v=v 2,2   ¹=¹ 2,2   ê=ê 2,2   u=u 2,2   >=> 2,2   Ñ=Ñ 2,2   r=r 2,2   Í=Í 2,2   †=† 2,2   i=i 2,2   z=z 2,2   ¥=¥ 2,2   N=N 2,2   = 2,2   = 2,2   ý=ý 2,2   –=– 2,2   = 2,2   
+=
+ 2,2   Õ=Õ 2,2   ^=^ 2,2   1=1 2,2   ’=’ 2,2   -=- 2,2   ¦=¦ 2,2   É=É 2,2   š=š 2,2   = 2,2   
+testing empty set
+
+testing 1-set
+
+testing n-set
+1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+the key=the value 8,10   n=n 2,2   á=á 2,2   "=" 2,2   ]=] 2,2   ¶=¶ 2,2   y=y 2,2   *=* 2,2   5=5 2,2   ~=~ 2,2   ‘=‘ 2,2   ²=² 2,2   = 2,2   Æ=Æ 2,2   )=) 2,2   º=º 2,2   e=e 2,2   Ž=Ž 2,2   A=A 2,2   B=B 2,2   ½=½ 2,2   Ö=Ö 2,2   Ù=Ù 2,2   J=J 2,2   •=• 2,2   ž=ž 2,2   ñ=ñ 2,2   Ò=Ò 2,2   í=í 2,2   æ=æ 2,2   ‰=‰ 2,2   Ú=Ú 2,2   Å=Å 2,2   ®=® 2,2   ¡=¡ 2,2   b=b 2,2   = 2,2   ö=ö 2,2   9=9 2,2   j=j 2,2   õ=õ 2,2   ¾=¾ 2,2   Q=Q 2,2   ò=ò 2,2   M=M 2,2   = 2,2   é=é 2,2   ú=ú 2,2   %=% 2,2   Î=Î 2,2   = 2,2   ‚=‚ 2,2   }=} 2,2   = 2,2   ™=™ 2,2   Š=Š 2,2   U=U 2,2   Þ=Þ 2,2   ±=± 2,2   = 2,2   ­=­ 2,2   &=& 2,2   I=I 2,2   = 2,2   …=… 2,2   î=î 2,2   a=a 2,2   ¢=¢ 2,2   Ý=Ý 2,2   6=6 2,2   ù=ù 2,2   ª=ª 2,2   µ=µ 2,2   þ=þ 2,2   = 2,2   2=2 2,2   
=
 2,2   F=F 2,2   ©=© 2,2   :=: 2,2   å=å 2,2   = 2,2   Á=Á 2,2   Â= 2,2   === 2,2   V=V 2,2   Y=Y 2,2   Ê=Ê 2,2   = 2,2   = 2,2   q=q 2,2   R=R 2,2   m=m 2,2   f=f 2,2   	=	 2,2   Z=Z 2,2   E=E 2,2   .=. 2,2   !=! 2,2   â=â 2,2   = 2,2   v=v 2,2   ¹=¹ 2,2   ê=ê 2,2   u=u 2,2   >=> 2,2   Ñ=Ñ 2,2   r=r 2,2   Í=Í 2,2   †=† 2,2   i=i 2,2   z=z 2,2   ¥=¥ 2,2   N=N 2,2   = 2,2   = 2,2   ý=ý 2,2   –=– 2,2   = 2,2   
+=
+ 2,2   Õ=Õ 2,2   ^=^ 2,2   1=1 2,2   ’=’ 2,2   -=- 2,2   ¦=¦ 2,2   É=É 2,2   š=š 2,2   = 2,2   
+testing empty set
+
+testing 1-set
+
+testing n-set
+1111111111111111111111111111111111011101111111111111111111111111101111111111111111111011101001101111011011011001011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+the key=the value 8,10   n=n 2,2   á=á 2,2   "=" 2,2   ]=] 2,2   ¶=¶ 2,2   y=y 2,2   *=* 2,2   5=5 2,2   ~=~ 2,2   ‘=‘ 2,2   ²=² 2,2   = 2,2   Æ=Æ 2,2   )=) 2,2   º=º 2,2   e=e 2,2   Ž=Ž 2,2   A=A 2,2   B=B 2,2   ½=½ 2,2   Ö=Ö 2,2   Ù=Ù 2,2   J=J 2,2   •=• 2,2   ž=ž 2,2   ñ=ñ 2,2   Ò=Ò 2,2   í=í 2,2   æ=æ 2,2   ‰=‰ 2,2   Ú=Ú 2,2   Å=Å 2,2   ®=® 2,2   ¡=¡ 2,2   = 2,2   ö=ö 2,2   9=9 2,2   õ=õ 2,2   ¾=¾ 2,2   Q=Q 2,2   ò=ò 2,2   M=M 2,2   = 2,2   é=é 2,2   ú=ú 2,2   %=% 2,2   Î=Î 2,2   = 2,2   ‚=‚ 2,2   }=} 2,2   = 2,2   ™=™ 2,2   Š=Š 2,2   U=U 2,2   Þ=Þ 2,2   ±=± 2,2   = 2,2   ­=­ 2,2   &=& 2,2   I=I 2,2   = 2,2   …=… 2,2   î=î 2,2   ¢=¢ 2,2   Ý=Ý 2,2   6=6 2,2   ù=ù 2,2   ª=ª 2,2   µ=µ 2,2   þ=þ 2,2   = 2,2   2=2 2,2   
=
 2,2   F=F 2,2   ©=© 2,2   :=: 2,2   å=å 2,2   = 2,2   Á=Á 2,2   Â= 2,2   === 2,2   V=V 2,2   Ê=Ê 2,2   = 2,2   = 2,2   R=R 2,2   	=	 2,2   Z=Z 2,2   .=. 2,2   !=! 2,2   â=â 2,2   = 2,2   ¹=¹ 2,2   ê=ê 2,2   >=> 2,2   Ñ=Ñ 2,2   Í=Í 2,2   †=† 2,2   ¥=¥ 2,2   = 2,2   = 2,2   ý=ý 2,2   –=– 2,2   = 2,2   
+=
+ 2,2   Õ=Õ 2,2   ^=^ 2,2   1=1 2,2   ’=’ 2,2   -=- 2,2   ¦=¦ 2,2   É=É 2,2   š=š 2,2   = 2,2   
-- 
2.49.0



More information about the ffmpeg-devel mailing list