From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <ffmpeg-devel-bounces@ffmpeg.org>
Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100])
	by master.gitmailbox.com (Postfix) with ESMTPS id 9E2CF4CF2E
	for <ffmpegdev@gitmailbox.com>; Tue, 15 Apr 2025 18:15:34 +0000 (UTC)
Received: from [127.0.1.1] (localhost [127.0.0.1])
	by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 35D92687A81;
	Tue, 15 Apr 2025 21:14:47 +0300 (EEST)
Received: from relay8-d.mail.gandi.net (relay8-d.mail.gandi.net
 [217.70.183.201])
 by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 6D089687D2C
 for <ffmpeg-devel@ffmpeg.org>; Tue, 15 Apr 2025 21:14:38 +0300 (EEST)
Received: by mail.gandi.net (Postfix) with ESMTPSA id BC88043423
 for <ffmpeg-devel@ffmpeg.org>; Tue, 15 Apr 2025 18:14:37 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=niedermayer.cc;
 s=gm1; t=1744740878;
 h=from:from:reply-to:subject:subject:date:date:message-id:message-id:
 to:to:cc:mime-version:mime-version:content-type:content-type:
 content-transfer-encoding:content-transfer-encoding:
 in-reply-to:in-reply-to:references:references;
 bh=lqdXVV8Wot6+oaQCsIWchOb4pepsq02irVuw7XNvJ8s=;
 b=SCrqXe11/29Bp7udv9XhxnRhVmBf898oMka4A6EObBM2rxMKe4EOeiAvGDKmC/swuqrMmn
 g4VK4x/joPtkTOGgSrahA4GGxJDusn3g6dll6Usk+qBS4a9atOWiFyfxy5xaKyGAfjbF7p
 uAjFBDYFxwdw8E97qqrxqxBXra7iQK75w97cOk2+iK8Er29FcVojhHX5kIKs/Lq1zp60FA
 N9DyK5e/sn4qRA7vRnIeJjh/fdSZELBisb93teg4/GJ7coZvlPtM/NeC9UNP7rVFoolG3c
 yTPkonIge2Djd4DiR5aGH+mpMyLC/apsDk0MFq/RH+MMJ5D0ib/vGR3cBl4N2Q==
From: Michael Niedermayer <michael@niedermayer.cc>
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Date: Tue, 15 Apr 2025 20:14:32 +0200
Message-ID: <20250415181433.530161-5-michael@niedermayer.cc>
X-Mailer: git-send-email 2.49.0
In-Reply-To: <20250415181433.530161-1-michael@niedermayer.cc>
References: <20250415181433.530161-1-michael@niedermayer.cc>
MIME-Version: 1.0
X-GND-State: clean
X-GND-Score: -70
X-GND-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddvvdegudekucetufdoteggodetrfdotffvucfrrhhofhhilhgvmecuifetpfffkfdpucggtfgfnhhsuhgsshgtrhhisggvnecuuegrihhlohhuthemuceftddunecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenfghrlhcuvffnffculdeftddmnecujfgurhephffvufffkffojghfgggtgfesthhqredtredtjeenucfhrhhomhepofhitghhrggvlhcupfhivgguvghrmhgrhigvrhcuoehmihgthhgrvghlsehnihgvuggvrhhmrgihvghrrdgttgeqnecuggftrfgrthhtvghrnhephfeufeeltdehgeehiedvveelkeevgeeftdfgueeivdegfeejgfdvhfevheegiefhnecukfhppeeguddrieeirdeijedruddufeenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepihhnvghtpeeguddrieeirdeijedruddufedphhgvlhhopehlohgtrghlhhhoshhtpdhmrghilhhfrhhomhepmhhitghhrggvlhesnhhivgguvghrmhgrhigvrhdrtggtpdhnsggprhgtphhtthhopedupdhrtghpthhtohepfhhfmhhpvghgqdguvghvvghlsehffhhmphgvghdrohhrgh
X-GND-Sasl: michael@niedermayer.cc
Subject: [FFmpeg-devel] [PATCH v2 5/6] libavutil: Add AVMap
X-BeenThere: ffmpeg-devel@ffmpeg.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: FFmpeg development discussions and patches <ffmpeg-devel.ffmpeg.org>
List-Unsubscribe: <https://ffmpeg.org/mailman/options/ffmpeg-devel>,
 <mailto:ffmpeg-devel-request@ffmpeg.org?subject=unsubscribe>
List-Archive: <https://ffmpeg.org/pipermail/ffmpeg-devel>
List-Post: <mailto:ffmpeg-devel@ffmpeg.org>
List-Help: <mailto:ffmpeg-devel-request@ffmpeg.org?subject=help>
List-Subscribe: <https://ffmpeg.org/mailman/listinfo/ffmpeg-devel>,
 <mailto:ffmpeg-devel-request@ffmpeg.org?subject=subscribe>
Reply-To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Content-Type: multipart/mixed; boundary="===============3952890375959664844=="
Errors-To: ffmpeg-devel-bounces@ffmpeg.org
Sender: "ffmpeg-devel" <ffmpeg-devel-bounces@ffmpeg.org>
Archived-At: <https://master.gitmailbox.com/ffmpegdev/20250415181433.530161-5-michael@niedermayer.cc/>
List-Archive: <https://master.gitmailbox.com/ffmpegdev/>
List-Post: <mailto:ffmpegdev@gitmailbox.com>

--===============3952890375959664844==
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable

AVL Tree based Map

compared to AVDictionary this has
 * clone is O(n) instead of O(n=C2=B2)
 * copy is O(n*log n) instead of O(n=C2=B2)
 * 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 i=
terator stays valid
 * copy is atomic, a failure means the dst is unchanged
 *
 * there are restrictions on what compare function can be used on get depen=
ding on how the Map was created
 * you can mix case sensitive and case insensitive compare with av_map_supe=
rcmp_*
 * Supports binary objects, not just strings

Still somewhat work in progress, help more welcome than complaints ;)

Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
---
 libavutil/Makefile        |   3 +
 libavutil/map.c           | 359 ++++++++++++++++++++++++++++++++++++++
 libavutil/map.h           | 230 ++++++++++++++++++++++++
 libavutil/tests/map.c     | 190 ++++++++++++++++++++
 libavutil/tree.c          |   6 +-
 libavutil/tree_internal.h |  28 +++
 tests/fate/libavutil.mak  |   4 +
 tests/ref/fate/map        |  27 +++
 8 files changed, 842 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 =3D adler32.h                                    =
                 \
           replaygain.h                                                  \
           ripemd.h                                                      \
           samplefmt.h                                                   \
+          map.h                                                         \
           sha.h                                                         \
           sha512.h                                                      \
           spherical.h                                                   \
@@ -173,6 +174,7 @@ OBJS =3D adler32.o                                     =
                   \
        rc4.o                                                            \
        ripemd.o                                                         \
        samplefmt.o                                                      \
+       map.o                                                            \
        side_data.o                                                      \
        sha.o                                                            \
        sha512.o                                                         \
@@ -290,6 +292,7 @@ TESTPROGS =3D 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..00dd5a1bd39
--- /dev/null
+++ b/libavutil/map.c
@@ -0,0 +1,359 @@
+/*
+ * 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-130=
1 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{
+    AVMapCompareFunc cmp_keyvalue;
+    AVMapCopyFunc copy;
+    AVMapFreeFunc freef;
+    int count;
+    int deleted;
+    int next;                   ///< index of entry in root array after al=
l 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) + si=
zeof(AVTreeNode) - 1) / sizeof (*I) + 1;
+}
+
+static inline AVTreeNode * internal_treenode(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.ke=
ylen;
+}
+
+static inline AVMapInternalEntry * keyvalue2internal(const uint8_t *keyval=
ue)
+{
+    return (AVMapInternalEntry*)(keyvalue - offsetof(AVMapInternalEntry, t=
reenode_and_keyvalue) - sizeof(AVTreeNode));
+}
+
+int av_map_strcmp_keyvalue(const char *a, const char *b)
+{
+    int v =3D strcmp(a,b);
+    if(!v)
+        v =3D strcmp(a + strlen(a) + 1, b + strlen(a) + 1); // please opti=
mize this dear compiler, we know the strlen after strcmp()
+    return v;
+}
+
+int av_map_supercmp_key(const char *a, const char *b)
+{
+    int v =3D av_strcasecmp(a,b);
+    if (!v)
+        v =3D strcmp(a,b);
+
+    return v;
+}
+
+int av_map_supercmp_keyvalue(const char *a, const char *b)
+{
+    int v =3D av_map_supercmp_key(a,b);
+    if (!v)
+        v =3D strcmp(a + strlen(a) + 1, b + strlen(a) + 1);
+
+    return v;
+}
+
+AVMap *av_map_new(AVMapCompareFunc cmp_keyvalue, AVMapCopyFunc copy, AVMap=
FreeFunc freef)
+{
+    AVMap *s =3D av_mallocz(sizeof(*s));
+    if (!s)
+        return NULL;
+
+    s->cmp_keyvalue  =3D cmp_keyvalue;
+    s->copy          =3D copy;
+    s->freef         =3D freef;
+
+    return s;
+}
+
+const AVMapEntry *av_map_get_multiple(const AVMap *s, const AVMapEntry *pr=
ev, const char *keyvalue, int (*cmp)(const void *keyvalue, const void *b))
+{
+    if (prev) {
+        void *next_node[2] =3D { NULL, NULL };
+        void *prev_keyvalue =3D av_tree_find2(s->tree_root, prev->key, s->=
cmp_keyvalue, next_node, 2);
+        av_assert2(prev_keyvalue);
+        if (!next_node[1] || cmp(next_node[1], keyvalue))
+            return NULL;
+
+        keyvalue =3D next_node[1];
+    } else {
+        void *next_node[4] =3D { NULL, NULL, NULL, NULL };
+        keyvalue =3D 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 =3D next_node[2];
+    }
+
+    if (!keyvalue)
+        return NULL;
+
+    return &keyvalue2internal(keyvalue)->map_entry;
+}
+
+const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue, int (*c=
mp)(const void *keyvalue, const void *b))
+{
+    keyvalue =3D av_tree_find2(s->tree_root, keyvalue, cmp, NULL, 0);
+
+    if (!keyvalue)
+        return NULL;
+
+    return &keyvalue2internal(keyvalue)->map_entry;
+}
+
+int av_map_realloc(AVMap *s, int extra_elements, int extra_space) {
+    int64_t advance =3D extra_elements + (extra_space + (int64_t)extra_ele=
ments*(sizeof(*s->internal_entries) + sizeof(AVTreeNode) - 1)) / sizeof(*s-=
>internal_entries);
+
+    if (advance > (INT32_MAX - s->next) / sizeof(AVMapInternalEntry))
+        return AVERROR(ENOMEM);
+
+    AVMapInternalEntry *new_root =3D av_fast_realloc(s->internal_entries, =
&s->internal_entries_len, (s->next + advance) * sizeof(AVMapInternalEntry));
+
+    if (!new_root)
+        return AVERROR(ENOMEM);
+
+    if (new_root !=3D s->internal_entries) {
+        if (s->tree_root) {
+            AVTreeNode *new_tree_root =3D s->tree_root - internal_treenode=
(s->internal_entries) + internal_treenode(new_root);
+            av_tree_move(new_tree_root, s->tree_root, new_root, s->interna=
l_entries);
+            s->tree_root =3D new_tree_root;
+        }
+
+        for(int i =3D 0; i<s->next; i++) {
+            if (new_root[i].map_entry.key !=3D &deleted_entry) {
+                new_root[i].map_entry.key   =3D internal_key  (new_root + =
i);
+                new_root[i].map_entry.value =3D internal_value(new_root + =
i);
+            }
+            i +=3D internal_entry_len(new_root + i) - 1;
+        }
+        s->internal_entries =3D new_root;
+    }
+    return advance;
+}
+
+int av_map_add(AVMap *s, const char *key, int keylen, const char *value, i=
nt valuelen, int flags)
+{
+    av_assert2(keylen || valuelen); // patch welcome but how would the com=
pare function compare a len=3D0 element without knowing it is a len 0 eleme=
nt
+
+    int advance =3D av_map_realloc(s, 1, keylen + valuelen);
+    if (advance < 0)
+        return advance;
+
+    AVMapEntry *entry =3D &s->internal_entries[s->next].map_entry;
+    AVTreeNode *next =3D internal_treenode(s->internal_entries + s->next);
+    memset(next, 0, sizeof(*next));
+    entry->keylen  =3D keylen;
+    entry->valuelen=3D valuelen;
+    entry->key     =3D internal_key  (s->internal_entries + s->next);
+    entry->value   =3D internal_value(s->internal_entries + s->next);
+    memcpy(entry->key  , key  , keylen);
+    memcpy(entry->value, value, valuelen);
+
+    void *elem =3D av_tree_insert(&s->tree_root, entry->key, s->cmp_keyval=
ue, &next);
+    int ret =3D 1;
+    if (elem !=3D entry->key && elem) {
+        av_assert2(next);
+        //we assume that new entries are more common than replacements
+        if (flags & AV_MAP_REPLACE) {
+            ret =3D av_map_del(s, entry->key, s->cmp_keyvalue, flags);
+            av_assert2(ret =3D=3D 1);
+            elem =3D av_tree_insert(&s->tree_root, entry->key, s->cmp_keyv=
alue, &next);
+            av_assert2(elem =3D=3D entry->key || !elem);
+            ret =3D 2;
+        } else
+            return 0; //entry already in the map
+    }
+    av_assert2(!next);
+    av_assert2(s->tree_root);
+    s->next +=3D advance;
+    s->count++;
+
+    return ret;
+}
+
+int av_map_del(AVMap *s, const char *keyvalue, int (*cmp)(const void *keyv=
alue, const void *b), int flags)
+{
+    uint8_t *old_keyvalue;
+    AVTreeNode *next =3D NULL;
+
+    if (cmp !=3D s->cmp_keyvalue) {
+        // The user asks us to remove a entry with a compare function diff=
erent from the one used to build the map
+        // we need to do 2 calls here, first with the users compare to fin=
d the entry she wants to remove
+        // and then to remove it while maintaining the correct order withi=
n the map
+        old_keyvalue =3D 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_keyvalue, &next=
);
+        av_assert2(next);
+    } else {
+        av_tree_insert(&s->tree_root, (char*)keyvalue, s->cmp_keyvalue, &n=
ext);
+        if (!next)
+            return 0;
+        old_keyvalue =3D next->elem; //TODO add a API to av_tree() to retu=
rn the elem of a AVTreeNode
+
+    }
+    AVMapInternalEntry *internal_entry =3D keyvalue2internal(old_keyvalue);
+    internal_entry->map_entry.key =3D &deleted_entry;
+
+    s->count--;
+    s->deleted++;
+
+    if ((flags & AV_MAP_ALLOW_REBUILD) && s->deleted > s->count) {
+        AVMap *news =3D av_map_new(s->cmp_keyvalue, s->copy, s->freef);
+        if(news) {
+            int ret =3D av_map_copy(news, s);
+            if (ret < 0) {
+                av_map_free(&news);
+            } else {
+                if (s->freef)
+                    for (int i=3D0; 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 =3D (AVMapInternalEntry*)((uint8_t*)prev - offsetof(AVMapInterna=
lEntry, map_entry));
+        I +=3D internal_entry_len(I);
+    } else {
+        I =3D s->internal_entries;
+    }
+    while (I < s->internal_entries + s->next && I->map_entry.key =3D=3D &d=
eleted_entry)
+        I +=3D internal_entry_len(I);
+
+    if (I =3D=3D 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 =3D *sp;
+
+    for (int i=3D0; i<s->count; i++) {
+        if (s->freef)
+            s->freef(&s->internal_entries[i].map_entry);
+    }
+    av_freep(&s->internal_entries);
+    s->next =3D
+    s->internal_entries_len =3D
+    s->count =3D 0;
+    av_freep(sp);
+}
+
+int av_map_copy(AVMap *dst, const AVMap *src)
+{
+    const AVMapEntry *t =3D NULL;
+    AVMap *bak =3D av_memdup(dst, sizeof(*dst));
+    if (!bak)
+        return AVERROR(ENOMEM);
+    bak->internal_entries =3D av_memdup(bak->internal_entries, bak->intern=
al_entries_len);
+
+    while ((t =3D av_map_iterate(src, t))) {
+        int ret =3D av_map_add(dst, t->key, t->keylen, t->value, t->valuel=
en, 0);
+
+        if (ret < 0) {
+            av_free(dst->internal_entries);
+            memcpy(dst, bak, sizeof(*dst));
+            return ret;
+        }
+    }
+    av_freep(&bak->internal_entries);
+    av_free(bak);
+
+    return 0;
+}
+
+AVMap *av_map_clone(AVMap *s)
+{
+    AVMap *dst =3D av_memdup(s, sizeof(AVMap));
+
+    if (!dst)
+        return NULL;
+
+    dst->internal_entries =3D av_memdup(s->internal_entries, s->internal_e=
ntries_len);
+
+    if (!dst->internal_entries)
+        goto err;
+
+    if (s->tree_root) {
+        dst->tree_root =3D s->tree_root - internal_treenode(s->internal_en=
tries) + internal_treenode(dst->internal_entries);
+        av_tree_move(dst->tree_root, s->tree_root, dst->internal_entries, =
s->internal_entries);
+    }
+
+    //TODO We could attempt to compact free space
+    for(int i =3D 0; i<s->next; i++) {
+        if (dst->internal_entries[i].map_entry.key !=3D &deleted_entry) {
+            dst->internal_entries[i].map_entry.key   =3D internal_key  (ds=
t->internal_entries + i);
+            dst->internal_entries[i].map_entry.value =3D internal_value(ds=
t->internal_entries + i);
+        }
+        i +=3D internal_entry_len(dst->internal_entries + i) - 1;
+    }
+
+    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..0c660260017
--- /dev/null
+++ b/libavutil/map.h
@@ -0,0 +1,230 @@
+/*
+ * 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-130=
1 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=C2=B2)
+ * copy is O(n*log n) instead of O(n=C2=B2)
+ * 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 depe=
nding on how the Map was created
+ * you can mix case sensitive and case insensitive compare with av_map_sup=
ercmp_*
+ * Supports binary objects, not just strings
+ */
+
+enum {
+    AV_MAP_ALLOW_REBUILD  =3D 1,   ///< when removing entries rebuild the =
map to reduce memory consumption, note, this invalidates previously retriev=
ed elements and iterate state.
+    AV_MAP_REPLACE        =3D 2,   ///< 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, siz=
e_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() an=
d 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 co=
ncatenated.
+ *                     it must form a strict total order on all elements y=
ou want to store. each key-value pair
+ *                     can only occur once. Though there can be multiple v=
alues for the same key. IF this function
+ *                     treats them as different.
+ *
+ * @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!=3Dx    X!=3Dx    av_map_supercmp_key, av_st=
rcasecmp, (trucated av_strcasecmp)
+ * av_map_supercmp_key      X!=3Dx            av_strcasecmp, (trucated av_=
strcasecmp)
+ * av_strcasecmp            X=3D=3Dx            truncation
+ *
+ * av_map_strcmp_keyvalue   X!=3Dx    X!=3Dx    strcmp, truncation
+ * strcmp                   X!=3Dx            truncation
+ *
+ *
+ */
+AVMap *av_map_new(AVMapCompareFunc keyvalue_cmp, AVMapCopyFunc clone, AVMa=
pFreeFunc freef);
+
+/**
+ * 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 ele=
ments
+ *
+ * @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 m=
ap, 2 if it was replaced
+ *                  otherwise an error code <0
+ */
+int av_map_add(AVMap *s, const char *key, int keylen, const char *value, i=
nt valuelen, int flags);
+
+/**
+ * Delete the given entry from a AVMap.
+ *
+ * @param s         Pointer AVMap struct.
+ * @param keyvalue  key or concatenated key+value
+ * @param cmp       compatible compare function that comapres key or keyva=
lues
+ * @param flags     AV_MAP_ALLOW_REBUILD or 0
+ *
+ * @return          1 if the entry was deleted, 0 if it was not found in t=
he map
+ *                  otherwise an error code <0
+ */
+int av_map_del(AVMap *s, const char *keyvalue, int (*cmp)(const void *keyv=
alue, const void *b), 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
+ * @param cmp   compare function, this will be passed keyvalue and the con=
catenated key+value
+ *              it must form a total order on all elements, that is a key =
can occur more than once.
+ *              But cmp2 must be a refinement of the cmp order, any disagr=
eement of the 2 compares
+ *              must be by cmp returning equal. If this only reads the key=
 part of keyvalue
+ *              then keyvalue can be just a key
+ *
+ * @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 *pr=
ev, const char *keyvalue, int (*cmp)(const void *keyvalue, const void *b));
+
+/**
+ * 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 (*c=
mp)(const void *keyvalue, const void *b));
+
+/**
+ * Iterate over a map
+ *
+ * Iterates through all entries in the map.
+ *
+ * @warning If you call any function with AV_SET_ALLOW_REBUILD set, then t=
he iterator is
+ * invalidated, and must not be used anymore. Otherwise av_map_add() (with=
out realloc) and av_map_del()
+ * can saftely be called during iteration.
+ *
+ * Typical usage:
+ * @code
+ * const AVMapEntry *e =3D NULL;
+ * while ((e =3D 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 *d=
st 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..38f0a153e68
--- /dev/null
+++ b/libavutil/tests/map.c
@@ -0,0 +1,190 @@
+/*
+ * 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-130=
1 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 =3D NULL;
+    while ((t =3D av_map_iterate(s, t)))
+        printf("%s=3D%s %d,%d   ", t->key, t->value, t->keylen, t->valuele=
n);
+    printf("\n");
+}
+
+int main(void)
+{
+    void *our_cmp[] =3D {
+        strcmp,
+        av_map_strcmp_keyvalue,
+        av_strcasecmp,
+        av_map_supercmp_keyvalue,
+        av_map_supercmp_keyvalue,
+    };
+    void *our_subcmp[] =3D {
+        strcmp,
+        strcmp,
+        av_strcasecmp,
+        av_map_supercmp_key,
+        av_strcasecmp,
+    };
+
+    for (int settype=3D0; settype<3; settype++) {
+        AVMap *set =3D av_map_new(our_cmp[settype], NULL, NULL);
+
+        printf("testing empty set\n");
+
+        const AVMapEntry *e =3D av_map_get(set, "foo", our_subcmp[settype]=
);
+        av_assert0(e =3D=3D NULL);
+
+        e =3D av_map_get(set, "foo", our_subcmp[settype]);
+        av_assert0(e =3D=3D NULL);
+
+        int ret =3D av_map_del(set, "foo", our_subcmp[settype], 0);
+        av_assert0(ret =3D=3D 0);
+
+        print_set(set);
+
+        printf("testing 1-set\n");
+
+        ret =3D av_map_add(set, "foo", 4, "bar", 4, 0);
+        av_assert0(ret =3D=3D 1);
+
+        ret =3D av_map_add(set, "foo", 4, "bear", 5, 0);
+        av_assert0(ret =3D=3D ((int[]){0,1,0})[settype]);
+
+        e =3D av_map_get(set, "foo", our_subcmp[settype]);
+        av_assert0(!strcmp(e->key, "foo"));
+        if (settype =3D=3D 1) {
+            av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value, "bar=
"));
+        } else {
+            av_assert0(!strcmp(e->value, "bar"));
+        }
+
+        ret =3D av_map_add(set, "foo", 4, "bear", 5, AV_MAP_REPLACE);
+        av_assert0(ret =3D=3D 2);
+
+        e =3D av_map_get(set, "foo", our_subcmp[settype]);
+        av_assert0(!strcmp(e->key, "foo"));
+        if (settype =3D=3D 1) {
+            av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value, "bar=
"));
+        } else {
+            av_assert0(!strcmp(e->value, "bear"));
+        }
+
+        e =3D av_map_get_multiple(set, NULL, "foo", our_subcmp[settype]);
+        av_assert0(!strcmp(e->key, "foo"));
+        if (settype =3D=3D 1) {
+            av_assert0(!strcmp(e->value, "bar"));
+        } else {
+            av_assert0(!strcmp(e->value, "bear"));
+        }
+        e =3D av_map_get_multiple(set, e, "foo", our_subcmp[settype]);
+        if (settype =3D=3D 1) {
+            av_assert0(!strcmp(e->key, "foo"));
+            av_assert0(!strcmp(e->value, "bear"));
+        } else {
+            av_assert0(e =3D=3D NULL);
+        }
+
+        ret =3D av_map_del(set, "foo", our_subcmp[settype], 0);
+        av_assert0(ret =3D=3D 1);
+
+        e =3D av_map_get(set, "foo", our_subcmp[settype]);
+        if (settype =3D=3D 1) {
+            av_assert0(!strcmp(e->key, "foo"));
+            av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value, "bar=
"));
+        } else {
+            av_assert0(e =3D=3D NULL);
+        }
+
+        ret =3D av_map_del(set, "foo", our_subcmp[settype], 0);
+        av_assert0(ret =3D=3D ((int[]){0,1,0})[settype]);
+
+
+        print_set(set);
+
+        printf("testing n-set\n");
+        unsigned r =3D 5;
+        int histogram[256] =3D {0};
+        for(int i=3D0; i<1000; i++) {
+            r =3D r*123 + 7;
+            unsigned char str[3] =3D {0};
+            str[0] =3D r;
+            ret =3D av_map_add(set, str, 2, str, 2 ,0);
+            if (i < 128) {
+                if (settype !=3D 2) {
+                    av_assert0(ret =3D=3D 1);
+                } else {
+                    av_assert0(ret =3D=3D !histogram[av_toupper(str[0])]);
+                    histogram[av_toupper(str[0])] =3D 1;
+                }
+            } else {
+                av_assert0(ret =3D=3D 0);
+            }
+            printf("%d", ret);
+        }
+        printf("\n");
+
+        r =3D 5;
+        for(int i=3D0; i<1000; i++) {
+            r =3D r*123 + 7;
+            char str[3] =3D {0};
+            str[0] =3D r;
+            e =3D av_map_get(set, str, our_subcmp[settype]);
+            if (settype !=3D 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 =3D av_map_get_multiple(set, NULL, str, our_subcmp[settype]);
+            if (settype !=3D 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 =3D av_map_add(set, str, 2, str, 2, 0);
+            av_assert0(ret =3D=3D 0);
+
+            str[1]=3D'x';
+
+            e =3D av_map_get(set, str, our_subcmp[settype]);
+            av_assert0(e =3D=3D NULL);
+            e =3D av_map_get_multiple(set, NULL, str, our_subcmp[settype]);
+            av_assert0(e =3D=3D NULL);
+        }
+        print_set(set);
+
+        av_map_free(&set);
+        av_assert0(!set);
+    }
+
+    return 0;
+}
diff --git a/libavutil/tree.c b/libavutil/tree.c
index 09c55f6752d..5cffab4563c 100644
--- a/libavutil/tree.c
+++ b/libavutil/tree.c
@@ -24,11 +24,7 @@
 #include "mem.h"
 #include "tree.h"
=20
-typedef struct AVTreeNode {
-    struct AVTreeNode *child[2];
-    void *elem;
-    int state;
-} AVTreeNode;
+#include "tree_internal.h"
=20
 const int av_tree_node_size =3D sizeof(AVTreeNode);
=20
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-130=
1 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 +=3D fate-dict
 fate-dict: libavutil/tests/dict$(EXESUF)
 fate-dict: CMD =3D run libavutil/tests/dict$(EXESUF)
=20
+FATE_LIBAVUTIL +=3D fate-map
+fate-map: libavutil/tests/map$(EXESUF)
+fate-map: CMD =3D run libavutil/tests/map$(EXESUF)
+
 FATE_LIBAVUTIL +=3D fate-encryption-info
 fate-encryption-info: libavutil/tests/encryption_info$(EXESUF)
 fate-encryption-info: CMD =3D run libavutil/tests/encryption_info$(EXESUF)
diff --git a/tests/ref/fate/map b/tests/ref/fate/map
new file mode 100644
index 00000000000..3333052a9f8
--- /dev/null
+++ b/tests/ref/fate/map
@@ -0,0 +1,27 @@
+testing empty set
+
+testing 1-set
+
+testing n-set
+11111111111111111111111111111111111111111111111111111111111111111111111111=
111111111111111111111111111111111111111111111111111111000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
00000000000000000000000000
+n=3Dn 2,2   =E1=3D=E1 2,2   "=3D" 2,2   ]=3D] 2,2   =B6=3D=B6 2,2   y=3Dy =
2,2   *=3D* 2,2   5=3D5 2,2   ~=3D~ 2,2   =91=3D=91 2,2   =B2=3D=B2 2,2   =
=8D=3D=8D 2,2   =C6=3D=C6 2,2   )=3D) 2,2   =BA=3D=BA 2,2   e=3De 2,2   =8E=
=3D=8E 2,2   A=3DA 2,2   B=3DB 2,2   =BD=3D=BD 2,2   =D6=3D=D6 2,2   =D9=3D=
=D9 2,2   J=3DJ 2,2   =95=3D=95 2,2   =9E=3D=9E 2,2   =F1=3D=F1 2,2   =D2=
=3D=D2 2,2   =ED=3D=ED 2,2   =E6=3D=E6 2,2   =89=3D=89 2,2   =DA=3D=DA 2,2 =
  =C5=3D=C5 2,2   =AE=3D=AE 2,2   =A1=3D=A1 2,2   b=3Db 2,2   =1D=3D=1D 2,2=
   =F6=3D=F6 2,2   9=3D9 2,2   j=3Dj 2,2   =F5=3D=F5 2,2   =BE=3D=BE 2,2   =
Q=3DQ 2,2   =F2=3D=F2 2,2   M=3DM 2,2   =06=3D=06 2,2   =E9=3D=E9 2,2   =FA=
=3D=FA 2,2   %=3D% 2,2   =CE=3D=CE 2,2   =01=3D=01 2,2   =82=3D=82 2,2   }=
=3D} 2,2   =16=3D=16 2,2   =99=3D=99 2,2   =8A=3D=8A 2,2   U=3DU 2,2   =DE=
=3D=DE 2,2   =B1=3D=B1 2,2   =12=3D=12 2,2   =AD=3D=AD 2,2   &=3D& 2,2   I=
=3DI 2,2   =1A=3D=1A 2,2   =85=3D=85 2,2   =EE=3D=EE 2,2   a=3Da 2,2   =A2=
=3D=A2 2,2   =DD=3D=DD 2,2   6=3D6 2,2   =F9=3D=F9 2,2   =AA=3D=AA 2,2   =
=B5=3D=B5 2,2   =FE=3D=FE 2,2   =11=3D=11 2,2   2=3D2 2,2   =0D=3D=0D 2,2  =
 F=3DF 2,2   =A9=3D=A9 2,2   :=3D: 2,2   =E5=3D=E5 2,2   =0E=3D=0E 2,2   =
=C1=3D=C1 2,2   =C2=3D=C2 2,2   =3D=3D=3D 2,2   V=3DV 2,2   Y=3DY 2,2   =CA=
=3D=CA 2,2   =15=3D=15 2,2   =1E=3D=1E 2,2   q=3Dq 2,2   R=3DR 2,2   m=3Dm =
2,2   f=3Df 2,2   	=3D	 2,2   Z=3DZ 2,2   E=3DE 2,2   .=3D. 2,2   !=3D! 2,2=
   =E2=3D=E2 2,2   =9D=3D=9D 2,2   v=3Dv 2,2   =B9=3D=B9 2,2   =EA=3D=EA 2,=
2   u=3Du 2,2   >=3D> 2,2   =D1=3D=D1 2,2   r=3Dr 2,2   =CD=3D=CD 2,2   =86=
=3D=86 2,2   i=3Di 2,2   z=3Dz 2,2   =A5=3D=A5 2,2   N=3DN 2,2   =81=3D=81 =
2,2   =02=3D=02 2,2   =FD=3D=FD 2,2   =96=3D=96 2,2   =19=3D=19 2,2=20=20=20
+=3D
+ 2,2   =D5=3D=D5 2,2   ^=3D^ 2,2   1=3D1 2,2   =92=3D=92 2,2   -=3D- 2,2  =
 =A6=3D=A6 2,2   =C9=3D=C9 2,2   =9A=3D=9A 2,2   =05=3D=05 2,2=20=20=20
+testing empty set
+
+testing 1-set
+
+testing n-set
+11111111111111111111111111111111111111111111111111111111111111111111111111=
111111111111111111111111111111111111111111111111111111000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
00000000000000000000000000
+n=3Dn 2,2   =E1=3D=E1 2,2   "=3D" 2,2   ]=3D] 2,2   =B6=3D=B6 2,2   y=3Dy =
2,2   *=3D* 2,2   5=3D5 2,2   ~=3D~ 2,2   =91=3D=91 2,2   =B2=3D=B2 2,2   =
=8D=3D=8D 2,2   =C6=3D=C6 2,2   )=3D) 2,2   =BA=3D=BA 2,2   e=3De 2,2   =8E=
=3D=8E 2,2   A=3DA 2,2   B=3DB 2,2   =BD=3D=BD 2,2   =D6=3D=D6 2,2   =D9=3D=
=D9 2,2   J=3DJ 2,2   =95=3D=95 2,2   =9E=3D=9E 2,2   =F1=3D=F1 2,2   =D2=
=3D=D2 2,2   =ED=3D=ED 2,2   =E6=3D=E6 2,2   =89=3D=89 2,2   =DA=3D=DA 2,2 =
  =C5=3D=C5 2,2   =AE=3D=AE 2,2   =A1=3D=A1 2,2   b=3Db 2,2   =1D=3D=1D 2,2=
   =F6=3D=F6 2,2   9=3D9 2,2   j=3Dj 2,2   =F5=3D=F5 2,2   =BE=3D=BE 2,2   =
Q=3DQ 2,2   =F2=3D=F2 2,2   M=3DM 2,2   =06=3D=06 2,2   =E9=3D=E9 2,2   =FA=
=3D=FA 2,2   %=3D% 2,2   =CE=3D=CE 2,2   =01=3D=01 2,2   =82=3D=82 2,2   }=
=3D} 2,2   =16=3D=16 2,2   =99=3D=99 2,2   =8A=3D=8A 2,2   U=3DU 2,2   =DE=
=3D=DE 2,2   =B1=3D=B1 2,2   =12=3D=12 2,2   =AD=3D=AD 2,2   &=3D& 2,2   I=
=3DI 2,2   =1A=3D=1A 2,2   =85=3D=85 2,2   =EE=3D=EE 2,2   a=3Da 2,2   =A2=
=3D=A2 2,2   =DD=3D=DD 2,2   6=3D6 2,2   =F9=3D=F9 2,2   =AA=3D=AA 2,2   =
=B5=3D=B5 2,2   =FE=3D=FE 2,2   =11=3D=11 2,2   2=3D2 2,2   =0D=3D=0D 2,2  =
 F=3DF 2,2   =A9=3D=A9 2,2   :=3D: 2,2   =E5=3D=E5 2,2   =0E=3D=0E 2,2   =
=C1=3D=C1 2,2   =C2=3D=C2 2,2   =3D=3D=3D 2,2   V=3DV 2,2   Y=3DY 2,2   =CA=
=3D=CA 2,2   =15=3D=15 2,2   =1E=3D=1E 2,2   q=3Dq 2,2   R=3DR 2,2   m=3Dm =
2,2   f=3Df 2,2   	=3D	 2,2   Z=3DZ 2,2   E=3DE 2,2   .=3D. 2,2   !=3D! 2,2=
   =E2=3D=E2 2,2   =9D=3D=9D 2,2   v=3Dv 2,2   =B9=3D=B9 2,2   =EA=3D=EA 2,=
2   u=3Du 2,2   >=3D> 2,2   =D1=3D=D1 2,2   r=3Dr 2,2   =CD=3D=CD 2,2   =86=
=3D=86 2,2   i=3Di 2,2   z=3Dz 2,2   =A5=3D=A5 2,2   N=3DN 2,2   =81=3D=81 =
2,2   =02=3D=02 2,2   =FD=3D=FD 2,2   =96=3D=96 2,2   =19=3D=19 2,2=20=20=20
+=3D
+ 2,2   =D5=3D=D5 2,2   ^=3D^ 2,2   1=3D1 2,2   =92=3D=92 2,2   -=3D- 2,2  =
 =A6=3D=A6 2,2   =C9=3D=C9 2,2   =9A=3D=9A 2,2   =05=3D=05 2,2=20=20=20
+testing empty set
+
+testing 1-set
+
+testing n-set
+11111111111111111111111111111111110111011111111111111111111111111011111111=
111111111110111010011011110110110110010111111111111111000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
000000000000000000000000000000000000000000000000000000000000000000000000000=
00000000000000000000000000
+n=3Dn 2,2   =E1=3D=E1 2,2   "=3D" 2,2   ]=3D] 2,2   =B6=3D=B6 2,2   y=3Dy =
2,2   *=3D* 2,2   5=3D5 2,2   ~=3D~ 2,2   =91=3D=91 2,2   =B2=3D=B2 2,2   =
=8D=3D=8D 2,2   =C6=3D=C6 2,2   )=3D) 2,2   =BA=3D=BA 2,2   e=3De 2,2   =8E=
=3D=8E 2,2   A=3DA 2,2   B=3DB 2,2   =BD=3D=BD 2,2   =D6=3D=D6 2,2   =D9=3D=
=D9 2,2   J=3DJ 2,2   =95=3D=95 2,2   =9E=3D=9E 2,2   =F1=3D=F1 2,2   =D2=
=3D=D2 2,2   =ED=3D=ED 2,2   =E6=3D=E6 2,2   =89=3D=89 2,2   =DA=3D=DA 2,2 =
  =C5=3D=C5 2,2   =AE=3D=AE 2,2   =A1=3D=A1 2,2   =1D=3D=1D 2,2   =F6=3D=F6=
 2,2   9=3D9 2,2   =F5=3D=F5 2,2   =BE=3D=BE 2,2   Q=3DQ 2,2   =F2=3D=F2 2,=
2   M=3DM 2,2   =06=3D=06 2,2   =E9=3D=E9 2,2   =FA=3D=FA 2,2   %=3D% 2,2  =
 =CE=3D=CE 2,2   =01=3D=01 2,2   =82=3D=82 2,2   }=3D} 2,2   =16=3D=16 2,2 =
  =99=3D=99 2,2   =8A=3D=8A 2,2   U=3DU 2,2   =DE=3D=DE 2,2   =B1=3D=B1 2,2=
   =12=3D=12 2,2   =AD=3D=AD 2,2   &=3D& 2,2   I=3DI 2,2   =1A=3D=1A 2,2   =
=85=3D=85 2,2   =EE=3D=EE 2,2   =A2=3D=A2 2,2   =DD=3D=DD 2,2   6=3D6 2,2  =
 =F9=3D=F9 2,2   =AA=3D=AA 2,2   =B5=3D=B5 2,2   =FE=3D=FE 2,2   =11=3D=11 =
2,2   2=3D2 2,2   =0D=3D=0D 2,2   F=3DF 2,2   =A9=3D=A9 2,2   :=3D: 2,2   =
=E5=3D=E5 2,2   =0E=3D=0E 2,2   =C1=3D=C1 2,2   =C2=3D=C2 2,2   =3D=3D=3D 2=
,2   V=3DV 2,2   =CA=3D=CA 2,2   =15=3D=15 2,2   =1E=3D=1E 2,2   R=3DR 2,2 =
  	=3D	 2,2   Z=3DZ 2,2   .=3D. 2,2   !=3D! 2,2   =E2=3D=E2 2,2   =9D=3D=9D=
 2,2   =B9=3D=B9 2,2   =EA=3D=EA 2,2   >=3D> 2,2   =D1=3D=D1 2,2   =CD=3D=
=CD 2,2   =86=3D=86 2,2   =A5=3D=A5 2,2   =81=3D=81 2,2   =02=3D=02 2,2   =
=FD=3D=FD 2,2   =96=3D=96 2,2   =19=3D=19 2,2=20=20=20
+=3D
+ 2,2   =D5=3D=D5 2,2   ^=3D^ 2,2   1=3D1 2,2   =92=3D=92 2,2   -=3D- 2,2  =
 =A6=3D=A6 2,2   =C9=3D=C9 2,2   =9A=3D=9A 2,2   =05=3D=05 2,2=20=20=20
--=20
2.49.0


--===============3952890375959664844==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

--===============3952890375959664844==--