[FFmpeg-devel] [PATCH v3] libavutil: Add AVMap
Leo Izen
leo.izen at gmail.com
Sat Apr 19 13:36:56 EEST 2025
On 4/17/25 12:52, Michael Niedermayer wrote:
> 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
Indices into arrays and counts and the like should be size_t.
> + unsigned internal_entries_len;
> + AVTreeNode *tree_root;
> + AVMapInternalEntry *internal_entries;
> +};
> +
> +static uint8_t deleted_entry;
static const should allow the compiler to put it in a different section
if it wishes. You can still address it even then.
> +
> +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);
Need to check for cmp_flags >= 27U before indexing into the array. The
compiler may pull this array off the stack cause it's static const so
you risk hitting a dead page here.
> +
> + 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);
No check for return value. av_map_add_cmp_func can return
AVERROR(EINVAL) depending on 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);
> + }
Code style: remove {}
> + 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
> +
This is a public function so we should be returning AVERROR(EINVAL) on
invalid input rather than asserting. Since a library user could do that.
> + 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;
We don't allow mixed delcarations and statements. Hoist the defintion
above if (!bak) and then put the assignment below it.
> +
> + 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;
You leak bak here. Possibly new_internal_entries as well.
> + }
> + }
> +
> + 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);
> + }
Code style: remove {}
> + 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;
Any particular reason to use unsigned char here rather than just char
when working with strings? Most string stuff expects signed char.
> + int keylen;
> + int valuelen;
size_t for lengths
> +} 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);
I don't believe it's clear if and when a user should call these
functions or simply pass them by address. If so, we should use the
typedef, or at least reference it in the doc comment.
> +
> +
> +/**
> + *
> + * @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
This may just be my email client messing up, but many of these appear to
be nonprintable. I don't know why.
> +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
>
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".
Leo Izen (Traneptora)
More information about the ffmpeg-devel
mailing list