PR #23422 opened by Marcos Ashton (MarcosAsh)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23422
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23422.patch

 ## Summary

  - pixdesc -- av_get_pix_fmt / av_get_pix_fmt_name round-trip across all pixel 
formats, av_color_{range,primaries,transfer,space}_name, 
av_chroma_location_name and av_alpha_mode_name round-trips through their 
from_name() counterparts using a name-equivalence check, 
av_chroma_location_enum_to_pos / pos_to_enum round-trip, and 
av_get_padded_bits_per_pixel / av_pix_fmt_count_planes for every format.

    Coverage for libavutil/pixdesc.c: 83.63% -> 90.03%

    Note: the reference encodes the fixed from_name() behavior, so this depends 
on #23421 being merged first.

  - downmix_info -- av_downmix_info_update_side_data() first-call allocation 
with zero-initialized defaults, field write/read-back, second-call reuse (same 
pointer, preserved values), and the OOM path via av_max_alloc. On a full FATE 
run the file is already covered by the AC-3 decoder sample tests. This 
exercises the API directly and keeps it covered on builds running without 
FATE_SAMPLES.

    Coverage for libavutil/downmix_info.c: 100.00% -> 100.00%

  - hdr_dynamic_metadata -- av_dynamic_hdr_plus_to_t35 in all three documented 
modes including  AVERROR_BUFFER_TOO_SMALL, av_dynamic_hdr_plus_from_t35 as a 
round-trip inverse for minimal and two-window rich payloads (peak luminance 
matrices, percentile distribution, tone mapping, color saturation), NULL and 
oversized error paths, and the SMPTE 2094-50 App5 alloc / create_side_data / 
to_t35 / from_t35 round trip. OOM paths via av_max_alloc.

    Coverage for libavutil/hdr_dynamic_metadata.c: 31.02% -> 91.56% (baseline 
comes from  fate-hevc-hdr10-plus-metadata, fate-png-mdcv and the Matroska/WebM 
HDR10+ remux tests)

  - side_data -- av_frame_side_data_desc/name on valid and invalid types, get 
and its _c variant, remove (including MULTI types with duplicates), 
remove_by_props, clone with and without REPLACE/UNIQUE including the EEXIST and 
EINVAL paths, and add in move mode, with NEW_REF and with REPLACE. Complements 
fate-side_data_array, which only covers new/name/free.

    Coverage for libavutil/side_data.c: 72.14% -> 86.43%



>From 30329a1f8318b5e413771e8d3f08bd39b59c8dc7 Mon Sep 17 00:00:00 2001
From: marcos ashton <[email protected]>
Date: Fri, 8 May 2026 15:47:00 +0100
Subject: [PATCH 1/4] tests/fate/libavutil: add FATE test for pixdesc

Test av_get_pix_fmt / av_get_pix_fmt_name round-trip across all
pixel formats, av_color_{range,primaries,transfer,space}_name,
av_chroma_location_name and av_alpha_mode_name round-trips
through their from_name() counterparts using a name-equivalence
check (two enum values may legitimately share the same name
string), av_chroma_location_enum_to_pos and pos_to_enum
round-trip across all valid chroma locations, and iterate every
pixel format printing av_get_padded_bits_per_pixel and
av_pix_fmt_count_planes.

Coverage for libavutil/pixdesc.c: 83.63% -> 90.03%

Numbers measured against a full FATE run. Remaining uncovered
lines are av_get_pix_fmt_string, the av_get_pix_fmt_loss /
av_find_best_pix_fmt_of_2 public wrappers and a few loss
branches, the palette branches of av_read_image_line and
av_write_image_line, and the *_ext name lookups in
av_color_{primaries,transfer}_from_name.

Signed-off-by: marcos ashton <[email protected]>
---
 .forgejo/CODEOWNERS       |   2 +
 libavutil/tests/pixdesc.c | 174 +++++++++++++++++++++--
 tests/fate/libavutil.mak  |   4 +
 tests/ref/fate/pixdesc    | 282 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 451 insertions(+), 11 deletions(-)
 create mode 100644 tests/ref/fate/pixdesc

diff --git a/.forgejo/CODEOWNERS b/.forgejo/CODEOWNERS
index 3ada7b3e18..427cae7a1a 100644
--- a/.forgejo/CODEOWNERS
+++ b/.forgejo/CODEOWNERS
@@ -236,6 +236,7 @@ libavutil/tests/ambient_viewing_environment.* @MarcosAsh
 libavutil/tests/buffer.* @MarcosAsh
 libavutil/tests/csp.* @MarcosAsh
 libavutil/tests/hdr_dynamic_vivid_metadata.* @MarcosAsh
+libavutil/tests/pixdesc.* @MarcosAsh
 libavutil/tests/tdrdi.* @MarcosAsh
 libavutil/tests/timestamp.* @MarcosAsh
 tests/ref/.*drawvg.* @ayosec
@@ -243,6 +244,7 @@ tests/ref/fate/ambient_viewing_environment @MarcosAsh
 tests/ref/fate/buffer @MarcosAsh
 tests/ref/fate/csp @MarcosAsh
 tests/ref/fate/hdr_dynamic_vivid_metadata @MarcosAsh
+tests/ref/fate/pixdesc @MarcosAsh
 tests/ref/fate/sub-mcc.* @programmerjake
 tests/ref/fate/tdrdi @MarcosAsh
 tests/ref/fate/timestamp @MarcosAsh
diff --git a/libavutil/tests/pixdesc.c b/libavutil/tests/pixdesc.c
index b13aba598b..ed8e74f204 100644
--- a/libavutil/tests/pixdesc.c
+++ b/libavutil/tests/pixdesc.c
@@ -19,25 +19,177 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
-#include "libavutil/log.h"
-#include "libavutil/pixdesc.c"
+#include <stdio.h>
+#include <string.h>
 
-int main(void){
-    int i;
-    int err=0;
+#include "libavutil/macros.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/pixfmt.h"
+
+/*
+ * from_name() may legitimately map to a different index when two enum
+ * values share the same name string (e.g. AVCOL_PRI_RESERVED0 and
+ * AVCOL_PRI_RESERVED both use "reserved"). Accept that as long as
+ * name(back) equals name(i).
+ */
+static int check_name_equivalence(const char *name, const char *back_name)
+{
+    return back_name && !strcmp(name, back_name);
+}
+
+static int test_pix_fmt_round_trip(void)
+{
+    int fail = 0;
+    for (int i = 0; i < AV_PIX_FMT_NB; i++) {
+        const char *name = av_get_pix_fmt_name(i);
+        if (!name)
+            continue;
+        if (av_get_pix_fmt(name) != i)
+            fail++;
+    }
+    return fail;
+}
+
+static int test_color_range_round_trip(void)
+{
+    int fail = 0;
+    for (int i = 0; i < AVCOL_RANGE_NB; i++) {
+        const char *name = av_color_range_name(i);
+        if (!name)
+            continue;
+        int back = av_color_range_from_name(name);
+        if (back < 0 || !check_name_equivalence(name, 
av_color_range_name(back)))
+            fail++;
+    }
+    return fail;
+}
+
+static int test_color_primaries_round_trip(void)
+{
+    int fail = 0;
+    for (int i = 0; i < AVCOL_PRI_NB; i++) {
+        const char *name = av_color_primaries_name(i);
+        if (!name)
+            continue;
+        int back = av_color_primaries_from_name(name);
+        if (back < 0 || !check_name_equivalence(name, 
av_color_primaries_name(back)))
+            fail++;
+    }
+    return fail;
+}
+
+static int test_color_transfer_round_trip(void)
+{
+    int fail = 0;
+    for (int i = 0; i < AVCOL_TRC_NB; i++) {
+        const char *name = av_color_transfer_name(i);
+        if (!name)
+            continue;
+        int back = av_color_transfer_from_name(name);
+        if (back < 0 || !check_name_equivalence(name, 
av_color_transfer_name(back)))
+            fail++;
+    }
+    return fail;
+}
+
+static int test_color_space_round_trip(void)
+{
+    int fail = 0;
+    for (int i = 0; i < AVCOL_SPC_NB; i++) {
+        const char *name = av_color_space_name(i);
+        if (!name)
+            continue;
+        int back = av_color_space_from_name(name);
+        if (back < 0 || !check_name_equivalence(name, 
av_color_space_name(back)))
+            fail++;
+    }
+    return fail;
+}
+
+static int test_chroma_location_round_trip(void)
+{
+    int fail = 0;
+    for (int i = 0; i < AVCHROMA_LOC_NB; i++) {
+        const char *name = av_chroma_location_name(i);
+        if (!name)
+            continue;
+        int back = av_chroma_location_from_name(name);
+        if (back < 0 || !check_name_equivalence(name, 
av_chroma_location_name(back)))
+            fail++;
+    }
+    return fail;
+}
+
+static int test_alpha_mode_round_trip(void)
+{
+    int fail = 0;
+    for (int i = 0; i < AVALPHA_MODE_NB; i++) {
+        const char *name = av_alpha_mode_name(i);
+        if (!name)
+            continue;
+        int back = av_alpha_mode_from_name(name);
+        if (back < 0 || !check_name_equivalence(name, 
av_alpha_mode_name(back)))
+            fail++;
+    }
+    return fail;
+}
+
+static int test_chroma_location_pos_round_trip(void)
+{
+    int fail = 0;
+    for (int i = AVCHROMA_LOC_UNSPECIFIED + 1; i < AVCHROMA_LOC_NB; i++) {
+        int xpos, ypos;
+        if (av_chroma_location_enum_to_pos(&xpos, &ypos, i) < 0) {
+            fail++;
+            continue;
+        }
+        if (av_chroma_location_pos_to_enum(xpos, ypos) != i)
+            fail++;
+    }
+    return fail;
+}
+
+int main(void)
+{
     int skip = 0;
 
-    for (i=0; i<AV_PIX_FMT_NB*2; i++) {
+    printf("Testing av_get_pix_fmt() / av_get_pix_fmt_name() round-trip\n");
+    printf("  result: %s\n",
+           test_pix_fmt_round_trip() ? "FAIL" : "OK");
+
+    printf("\nTesting av_color_*_name() / av_color_*_from_name() 
round-trips\n");
+    printf("  av_color_range:     %s\n",
+           test_color_range_round_trip()     ? "FAIL" : "OK");
+    printf("  av_color_primaries: %s\n",
+           test_color_primaries_round_trip() ? "FAIL" : "OK");
+    printf("  av_color_transfer:  %s\n",
+           test_color_transfer_round_trip()  ? "FAIL" : "OK");
+    printf("  av_color_space:     %s\n",
+           test_color_space_round_trip()     ? "FAIL" : "OK");
+    printf("  av_chroma_location: %s\n",
+           test_chroma_location_round_trip() ? "FAIL" : "OK");
+    printf("  av_alpha_mode:      %s\n",
+           test_alpha_mode_round_trip()      ? "FAIL" : "OK");
+
+    printf("\nTesting av_chroma_location_enum_to_pos / pos_to_enum 
round-trip\n");
+    printf("  result: %s\n",
+           test_chroma_location_pos_round_trip() ? "FAIL" : "OK");
+
+    printf("\nTesting pixel format descriptors\n");
+    for (int i = 0; i < AV_PIX_FMT_NB * 2; i++) {
         const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(i);
-        if(!desc || !desc->name) {
-            skip ++;
+        if (!desc || !desc->name) {
+            skip++;
             continue;
         }
         if (skip) {
-            av_log(NULL, AV_LOG_INFO, "%3d unused pixel format values\n", 
skip);
+            printf("  %3d unused pixel format values\n", skip);
             skip = 0;
         }
-        av_log(NULL, AV_LOG_INFO, "pix fmt %s avg_bpp:%d colortype:%d\n", 
desc->name, av_get_padded_bits_per_pixel(desc), get_color_type(desc));
+        printf("  pix fmt %s avg_bpp:%d planes:%d\n", desc->name,
+               av_get_padded_bits_per_pixel(desc),
+               av_pix_fmt_count_planes(i));
     }
-    return err;
+
+    return 0;
 }
diff --git a/tests/fate/libavutil.mak b/tests/fate/libavutil.mak
index a2ea57d939..eb116280a7 100644
--- a/tests/fate/libavutil.mak
+++ b/tests/fate/libavutil.mak
@@ -148,6 +148,10 @@ FATE_LIBAVUTIL += fate-parseutils
 fate-parseutils: libavutil/tests/parseutils$(EXESUF)
 fate-parseutils: CMD = run libavutil/tests/parseutils$(EXESUF)
 
+FATE_LIBAVUTIL += fate-pixdesc
+fate-pixdesc: libavutil/tests/pixdesc$(EXESUF)
+fate-pixdesc: CMD = run libavutil/tests/pixdesc$(EXESUF)
+
 FATE_LIBAVUTIL-$(CONFIG_PIXELUTILS) += fate-pixelutils
 fate-pixelutils: libavutil/tests/pixelutils$(EXESUF)
 fate-pixelutils: CMD = run libavutil/tests/pixelutils$(EXESUF)
diff --git a/tests/ref/fate/pixdesc b/tests/ref/fate/pixdesc
new file mode 100644
index 0000000000..84696e6a1b
--- /dev/null
+++ b/tests/ref/fate/pixdesc
@@ -0,0 +1,282 @@
+Testing av_get_pix_fmt() / av_get_pix_fmt_name() round-trip
+  result: OK
+
+Testing av_color_*_name() / av_color_*_from_name() round-trips
+  av_color_range:     OK
+  av_color_primaries: OK
+  av_color_transfer:  OK
+  av_color_space:     OK
+  av_chroma_location: OK
+  av_alpha_mode:      OK
+
+Testing av_chroma_location_enum_to_pos / pos_to_enum round-trip
+  result: OK
+
+Testing pixel format descriptors
+  pix fmt yuv420p avg_bpp:12 planes:3
+  pix fmt yuyv422 avg_bpp:16 planes:1
+  pix fmt rgb24 avg_bpp:24 planes:1
+  pix fmt bgr24 avg_bpp:24 planes:1
+  pix fmt yuv422p avg_bpp:16 planes:3
+  pix fmt yuv444p avg_bpp:24 planes:3
+  pix fmt yuv410p avg_bpp:9 planes:3
+  pix fmt yuv411p avg_bpp:12 planes:3
+  pix fmt gray avg_bpp:8 planes:1
+  pix fmt monow avg_bpp:1 planes:1
+  pix fmt monob avg_bpp:1 planes:1
+  pix fmt pal8 avg_bpp:8 planes:1
+  pix fmt yuvj420p avg_bpp:12 planes:3
+  pix fmt yuvj422p avg_bpp:16 planes:3
+  pix fmt yuvj444p avg_bpp:24 planes:3
+  pix fmt uyvy422 avg_bpp:16 planes:1
+  pix fmt uyyvyy411 avg_bpp:12 planes:1
+  pix fmt bgr8 avg_bpp:8 planes:1
+  pix fmt bgr4 avg_bpp:4 planes:1
+  pix fmt bgr4_byte avg_bpp:8 planes:1
+  pix fmt rgb8 avg_bpp:8 planes:1
+  pix fmt rgb4 avg_bpp:4 planes:1
+  pix fmt rgb4_byte avg_bpp:8 planes:1
+  pix fmt nv12 avg_bpp:12 planes:2
+  pix fmt nv21 avg_bpp:12 planes:2
+  pix fmt argb avg_bpp:32 planes:1
+  pix fmt rgba avg_bpp:32 planes:1
+  pix fmt abgr avg_bpp:32 planes:1
+  pix fmt bgra avg_bpp:32 planes:1
+  pix fmt gray16be avg_bpp:16 planes:1
+  pix fmt gray16le avg_bpp:16 planes:1
+  pix fmt yuv440p avg_bpp:16 planes:3
+  pix fmt yuvj440p avg_bpp:16 planes:3
+  pix fmt yuva420p avg_bpp:20 planes:4
+  pix fmt rgb48be avg_bpp:48 planes:1
+  pix fmt rgb48le avg_bpp:48 planes:1
+  pix fmt rgb565be avg_bpp:16 planes:1
+  pix fmt rgb565le avg_bpp:16 planes:1
+  pix fmt rgb555be avg_bpp:16 planes:1
+  pix fmt rgb555le avg_bpp:16 planes:1
+  pix fmt bgr565be avg_bpp:16 planes:1
+  pix fmt bgr565le avg_bpp:16 planes:1
+  pix fmt bgr555be avg_bpp:16 planes:1
+  pix fmt bgr555le avg_bpp:16 planes:1
+  pix fmt vaapi avg_bpp:0 planes:0
+  pix fmt yuv420p16le avg_bpp:24 planes:3
+  pix fmt yuv420p16be avg_bpp:24 planes:3
+  pix fmt yuv422p16le avg_bpp:32 planes:3
+  pix fmt yuv422p16be avg_bpp:32 planes:3
+  pix fmt yuv444p16le avg_bpp:48 planes:3
+  pix fmt yuv444p16be avg_bpp:48 planes:3
+  pix fmt dxva2_vld avg_bpp:0 planes:0
+  pix fmt rgb444le avg_bpp:16 planes:1
+  pix fmt rgb444be avg_bpp:16 planes:1
+  pix fmt bgr444le avg_bpp:16 planes:1
+  pix fmt bgr444be avg_bpp:16 planes:1
+  pix fmt ya8 avg_bpp:16 planes:1
+  pix fmt bgr48be avg_bpp:48 planes:1
+  pix fmt bgr48le avg_bpp:48 planes:1
+  pix fmt yuv420p9be avg_bpp:24 planes:3
+  pix fmt yuv420p9le avg_bpp:24 planes:3
+  pix fmt yuv420p10be avg_bpp:24 planes:3
+  pix fmt yuv420p10le avg_bpp:24 planes:3
+  pix fmt yuv422p10be avg_bpp:32 planes:3
+  pix fmt yuv422p10le avg_bpp:32 planes:3
+  pix fmt yuv444p9be avg_bpp:48 planes:3
+  pix fmt yuv444p9le avg_bpp:48 planes:3
+  pix fmt yuv444p10be avg_bpp:48 planes:3
+  pix fmt yuv444p10le avg_bpp:48 planes:3
+  pix fmt yuv422p9be avg_bpp:32 planes:3
+  pix fmt yuv422p9le avg_bpp:32 planes:3
+  pix fmt gbrp avg_bpp:24 planes:3
+  pix fmt gbrp9be avg_bpp:48 planes:3
+  pix fmt gbrp9le avg_bpp:48 planes:3
+  pix fmt gbrp10be avg_bpp:48 planes:3
+  pix fmt gbrp10le avg_bpp:48 planes:3
+  pix fmt gbrp16be avg_bpp:48 planes:3
+  pix fmt gbrp16le avg_bpp:48 planes:3
+  pix fmt yuva422p avg_bpp:24 planes:4
+  pix fmt yuva444p avg_bpp:32 planes:4
+  pix fmt yuva420p9be avg_bpp:40 planes:4
+  pix fmt yuva420p9le avg_bpp:40 planes:4
+  pix fmt yuva422p9be avg_bpp:48 planes:4
+  pix fmt yuva422p9le avg_bpp:48 planes:4
+  pix fmt yuva444p9be avg_bpp:64 planes:4
+  pix fmt yuva444p9le avg_bpp:64 planes:4
+  pix fmt yuva420p10be avg_bpp:40 planes:4
+  pix fmt yuva420p10le avg_bpp:40 planes:4
+  pix fmt yuva422p10be avg_bpp:48 planes:4
+  pix fmt yuva422p10le avg_bpp:48 planes:4
+  pix fmt yuva444p10be avg_bpp:64 planes:4
+  pix fmt yuva444p10le avg_bpp:64 planes:4
+  pix fmt yuva420p16be avg_bpp:40 planes:4
+  pix fmt yuva420p16le avg_bpp:40 planes:4
+  pix fmt yuva422p16be avg_bpp:48 planes:4
+  pix fmt yuva422p16le avg_bpp:48 planes:4
+  pix fmt yuva444p16be avg_bpp:64 planes:4
+  pix fmt yuva444p16le avg_bpp:64 planes:4
+  pix fmt vdpau avg_bpp:0 planes:0
+  pix fmt xyz12le avg_bpp:48 planes:1
+  pix fmt xyz12be avg_bpp:48 planes:1
+  pix fmt nv16 avg_bpp:16 planes:2
+  pix fmt nv20le avg_bpp:32 planes:2
+  pix fmt nv20be avg_bpp:32 planes:2
+  pix fmt rgba64be avg_bpp:64 planes:1
+  pix fmt rgba64le avg_bpp:64 planes:1
+  pix fmt bgra64be avg_bpp:64 planes:1
+  pix fmt bgra64le avg_bpp:64 planes:1
+  pix fmt yvyu422 avg_bpp:16 planes:1
+  pix fmt ya16be avg_bpp:32 planes:1
+  pix fmt ya16le avg_bpp:32 planes:1
+  pix fmt gbrap avg_bpp:32 planes:4
+  pix fmt gbrap16be avg_bpp:64 planes:4
+  pix fmt gbrap16le avg_bpp:64 planes:4
+  pix fmt qsv avg_bpp:0 planes:0
+  pix fmt mmal avg_bpp:0 planes:0
+  pix fmt d3d11va_vld avg_bpp:0 planes:0
+  pix fmt cuda avg_bpp:0 planes:0
+  pix fmt 0rgb avg_bpp:32 planes:1
+  pix fmt rgb0 avg_bpp:32 planes:1
+  pix fmt 0bgr avg_bpp:32 planes:1
+  pix fmt bgr0 avg_bpp:32 planes:1
+  pix fmt yuv420p12be avg_bpp:24 planes:3
+  pix fmt yuv420p12le avg_bpp:24 planes:3
+  pix fmt yuv420p14be avg_bpp:24 planes:3
+  pix fmt yuv420p14le avg_bpp:24 planes:3
+  pix fmt yuv422p12be avg_bpp:32 planes:3
+  pix fmt yuv422p12le avg_bpp:32 planes:3
+  pix fmt yuv422p14be avg_bpp:32 planes:3
+  pix fmt yuv422p14le avg_bpp:32 planes:3
+  pix fmt yuv444p12be avg_bpp:48 planes:3
+  pix fmt yuv444p12le avg_bpp:48 planes:3
+  pix fmt yuv444p14be avg_bpp:48 planes:3
+  pix fmt yuv444p14le avg_bpp:48 planes:3
+  pix fmt gbrp12be avg_bpp:48 planes:3
+  pix fmt gbrp12le avg_bpp:48 planes:3
+  pix fmt gbrp14be avg_bpp:48 planes:3
+  pix fmt gbrp14le avg_bpp:48 planes:3
+  pix fmt yuvj411p avg_bpp:12 planes:3
+  pix fmt bayer_bggr8 avg_bpp:8 planes:1
+  pix fmt bayer_rggb8 avg_bpp:8 planes:1
+  pix fmt bayer_gbrg8 avg_bpp:8 planes:1
+  pix fmt bayer_grbg8 avg_bpp:8 planes:1
+  pix fmt bayer_bggr16le avg_bpp:16 planes:1
+  pix fmt bayer_bggr16be avg_bpp:16 planes:1
+  pix fmt bayer_rggb16le avg_bpp:16 planes:1
+  pix fmt bayer_rggb16be avg_bpp:16 planes:1
+  pix fmt bayer_gbrg16le avg_bpp:16 planes:1
+  pix fmt bayer_gbrg16be avg_bpp:16 planes:1
+  pix fmt bayer_grbg16le avg_bpp:16 planes:1
+  pix fmt bayer_grbg16be avg_bpp:16 planes:1
+  pix fmt yuv440p10le avg_bpp:32 planes:3
+  pix fmt yuv440p10be avg_bpp:32 planes:3
+  pix fmt yuv440p12le avg_bpp:32 planes:3
+  pix fmt yuv440p12be avg_bpp:32 planes:3
+  pix fmt ayuv64le avg_bpp:64 planes:1
+  pix fmt ayuv64be avg_bpp:64 planes:1
+  pix fmt videotoolbox_vld avg_bpp:0 planes:0
+  pix fmt p010le avg_bpp:24 planes:2
+  pix fmt p010be avg_bpp:24 planes:2
+  pix fmt gbrap12be avg_bpp:64 planes:4
+  pix fmt gbrap12le avg_bpp:64 planes:4
+  pix fmt gbrap10be avg_bpp:64 planes:4
+  pix fmt gbrap10le avg_bpp:64 planes:4
+  pix fmt mediacodec avg_bpp:0 planes:0
+  pix fmt gray12be avg_bpp:16 planes:1
+  pix fmt gray12le avg_bpp:16 planes:1
+  pix fmt gray10be avg_bpp:16 planes:1
+  pix fmt gray10le avg_bpp:16 planes:1
+  pix fmt p016le avg_bpp:24 planes:2
+  pix fmt p016be avg_bpp:24 planes:2
+  pix fmt d3d11 avg_bpp:0 planes:0
+  pix fmt gray9be avg_bpp:16 planes:1
+  pix fmt gray9le avg_bpp:16 planes:1
+  pix fmt gbrpf32be avg_bpp:96 planes:3
+  pix fmt gbrpf32le avg_bpp:96 planes:3
+  pix fmt gbrapf32be avg_bpp:128 planes:4
+  pix fmt gbrapf32le avg_bpp:128 planes:4
+  pix fmt drm_prime avg_bpp:0 planes:0
+  pix fmt opencl avg_bpp:0 planes:0
+  pix fmt gray14be avg_bpp:16 planes:1
+  pix fmt gray14le avg_bpp:16 planes:1
+  pix fmt grayf32be avg_bpp:32 planes:1
+  pix fmt grayf32le avg_bpp:32 planes:1
+  pix fmt yuva422p12be avg_bpp:48 planes:4
+  pix fmt yuva422p12le avg_bpp:48 planes:4
+  pix fmt yuva444p12be avg_bpp:64 planes:4
+  pix fmt yuva444p12le avg_bpp:64 planes:4
+  pix fmt nv24 avg_bpp:24 planes:2
+  pix fmt nv42 avg_bpp:24 planes:2
+  pix fmt vulkan avg_bpp:0 planes:0
+  pix fmt y210be avg_bpp:32 planes:1
+  pix fmt y210le avg_bpp:32 planes:1
+  pix fmt x2rgb10le avg_bpp:32 planes:1
+  pix fmt x2rgb10be avg_bpp:32 planes:1
+  pix fmt x2bgr10le avg_bpp:32 planes:1
+  pix fmt x2bgr10be avg_bpp:32 planes:1
+  pix fmt p210be avg_bpp:32 planes:2
+  pix fmt p210le avg_bpp:32 planes:2
+  pix fmt p410be avg_bpp:48 planes:2
+  pix fmt p410le avg_bpp:48 planes:2
+  pix fmt p216be avg_bpp:32 planes:2
+  pix fmt p216le avg_bpp:32 planes:2
+  pix fmt p416be avg_bpp:48 planes:2
+  pix fmt p416le avg_bpp:48 planes:2
+  pix fmt vuya avg_bpp:32 planes:1
+  pix fmt rgbaf16be avg_bpp:64 planes:1
+  pix fmt rgbaf16le avg_bpp:64 planes:1
+  pix fmt vuyx avg_bpp:32 planes:1
+  pix fmt p012le avg_bpp:24 planes:2
+  pix fmt p012be avg_bpp:24 planes:2
+  pix fmt y212be avg_bpp:32 planes:1
+  pix fmt y212le avg_bpp:32 planes:1
+  pix fmt xv30be avg_bpp:32 planes:1
+  pix fmt xv30le avg_bpp:32 planes:1
+  pix fmt xv36be avg_bpp:64 planes:1
+  pix fmt xv36le avg_bpp:64 planes:1
+  pix fmt rgbf32be avg_bpp:96 planes:1
+  pix fmt rgbf32le avg_bpp:96 planes:1
+  pix fmt rgbaf32be avg_bpp:128 planes:1
+  pix fmt rgbaf32le avg_bpp:128 planes:1
+  pix fmt p212be avg_bpp:32 planes:2
+  pix fmt p212le avg_bpp:32 planes:2
+  pix fmt p412be avg_bpp:48 planes:2
+  pix fmt p412le avg_bpp:48 planes:2
+  pix fmt gbrap14be avg_bpp:64 planes:4
+  pix fmt gbrap14le avg_bpp:64 planes:4
+  pix fmt d3d12 avg_bpp:0 planes:0
+  pix fmt ayuv avg_bpp:32 planes:1
+  pix fmt uyva avg_bpp:32 planes:1
+  pix fmt vyu444 avg_bpp:24 planes:1
+  pix fmt v30xbe avg_bpp:32 planes:1
+  pix fmt v30xle avg_bpp:32 planes:1
+  pix fmt rgbf16be avg_bpp:48 planes:1
+  pix fmt rgbf16le avg_bpp:48 planes:1
+  pix fmt rgba128be avg_bpp:128 planes:1
+  pix fmt rgba128le avg_bpp:128 planes:1
+  pix fmt rgb96be avg_bpp:96 planes:1
+  pix fmt rgb96le avg_bpp:96 planes:1
+  pix fmt y216be avg_bpp:32 planes:1
+  pix fmt y216le avg_bpp:32 planes:1
+  pix fmt xv48be avg_bpp:64 planes:1
+  pix fmt xv48le avg_bpp:64 planes:1
+  pix fmt gbrpf16be avg_bpp:48 planes:3
+  pix fmt gbrpf16le avg_bpp:48 planes:3
+  pix fmt gbrapf16be avg_bpp:64 planes:4
+  pix fmt gbrapf16le avg_bpp:64 planes:4
+  pix fmt grayf16be avg_bpp:16 planes:1
+  pix fmt grayf16le avg_bpp:16 planes:1
+  pix fmt amf avg_bpp:0 planes:0
+  pix fmt gray32be avg_bpp:32 planes:1
+  pix fmt gray32le avg_bpp:32 planes:1
+  pix fmt yaf32be avg_bpp:64 planes:1
+  pix fmt yaf32le avg_bpp:64 planes:1
+  pix fmt yaf16be avg_bpp:32 planes:1
+  pix fmt yaf16le avg_bpp:32 planes:1
+  pix fmt gbrap32be avg_bpp:128 planes:4
+  pix fmt gbrap32le avg_bpp:128 planes:4
+  pix fmt yuv444p10msbbe avg_bpp:48 planes:3
+  pix fmt yuv444p10msble avg_bpp:48 planes:3
+  pix fmt yuv444p12msbbe avg_bpp:48 planes:3
+  pix fmt yuv444p12msble avg_bpp:48 planes:3
+  pix fmt gbrp10msbbe avg_bpp:48 planes:3
+  pix fmt gbrp10msble avg_bpp:48 planes:3
+  pix fmt gbrp12msbbe avg_bpp:48 planes:3
+  pix fmt gbrp12msble avg_bpp:48 planes:3
+  pix fmt ohcodec avg_bpp:0 planes:0
-- 
2.52.0


>From 50236888fc061ec09ef295d164487718f81a0568 Mon Sep 17 00:00:00 2001
From: marcos ashton <[email protected]>
Date: Sat, 18 Apr 2026 13:17:07 +0100
Subject: [PATCH 2/4] tests/fate/libavutil: add FATE test for downmix_info

Test av_downmix_info_update_side_data() including first-call
allocation with zero-initialized defaults, field write/read-back,
second-call reuse (same pointer, preserved values), and the OOM
path via av_max_alloc.

Coverage for libavutil/downmix_info.c: 100.00% -> 100.00%

On a full FATE run the file is already covered by the AC-3
decoder tests (fate-ac3-4.0 and friends), which require
FATE_SAMPLES. This test exercises the API directly and keeps the
file covered on builds that run without samples.

Signed-off-by: marcos ashton <[email protected]>
---
 .forgejo/CODEOWNERS            |  2 +
 libavutil/Makefile             |  1 +
 libavutil/tests/downmix_info.c | 86 ++++++++++++++++++++++++++++++++++
 tests/fate/libavutil.mak       |  4 ++
 tests/ref/fate/downmix_info    |  8 ++++
 5 files changed, 101 insertions(+)
 create mode 100644 libavutil/tests/downmix_info.c
 create mode 100644 tests/ref/fate/downmix_info

diff --git a/.forgejo/CODEOWNERS b/.forgejo/CODEOWNERS
index 427cae7a1a..87ab1abf62 100644
--- a/.forgejo/CODEOWNERS
+++ b/.forgejo/CODEOWNERS
@@ -235,6 +235,7 @@ tests/checkasm/riscv/.* @Courmisch
 libavutil/tests/ambient_viewing_environment.* @MarcosAsh
 libavutil/tests/buffer.* @MarcosAsh
 libavutil/tests/csp.* @MarcosAsh
+libavutil/tests/downmix_info.* @MarcosAsh
 libavutil/tests/hdr_dynamic_vivid_metadata.* @MarcosAsh
 libavutil/tests/pixdesc.* @MarcosAsh
 libavutil/tests/tdrdi.* @MarcosAsh
@@ -243,6 +244,7 @@ tests/ref/.*drawvg.* @ayosec
 tests/ref/fate/ambient_viewing_environment @MarcosAsh
 tests/ref/fate/buffer @MarcosAsh
 tests/ref/fate/csp @MarcosAsh
+tests/ref/fate/downmix_info @MarcosAsh
 tests/ref/fate/hdr_dynamic_vivid_metadata @MarcosAsh
 tests/ref/fate/pixdesc @MarcosAsh
 tests/ref/fate/sub-mcc.* @programmerjake
diff --git a/libavutil/Makefile b/libavutil/Makefile
index 2e8a5de551..57470bc6d4 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -278,6 +278,7 @@ TESTPROGS = adler32                                         
            \
             detection_bbox                                              \
             dict                                                        \
             display                                                     \
+            downmix_info                                                \
             encryption_info                                             \
             error                                                       \
             eval                                                        \
diff --git a/libavutil/tests/downmix_info.c b/libavutil/tests/downmix_info.c
new file mode 100644
index 0000000000..588927ce78
--- /dev/null
+++ b/libavutil/tests/downmix_info.c
@@ -0,0 +1,86 @@
+/*
+ * 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 <limits.h>
+#include <stdio.h>
+
+#include "libavutil/downmix_info.h"
+#include "libavutil/frame.h"
+#include "libavutil/mem.h"
+
+int main(void)
+{
+    AVFrame *frame;
+    AVDownmixInfo *info, *info2;
+
+    /* First call: allocates side data, zero-initialized. */
+    printf("Testing av_downmix_info_update_side_data()\n");
+    frame = av_frame_alloc();
+    if (!frame)
+        return 1;
+
+    info = av_downmix_info_update_side_data(frame);
+    if (info) {
+        printf("create: OK\n");
+        printf("defaults: type=%d center=%.1f center_ltrt=%.1f surround=%.1f"
+               " surround_ltrt=%.1f lfe=%.1f\n",
+               info->preferred_downmix_type,
+               info->center_mix_level, info->center_mix_level_ltrt,
+               info->surround_mix_level, info->surround_mix_level_ltrt,
+               info->lfe_mix_level);
+
+        /* Write fields and read them back. */
+        info->preferred_downmix_type  = AV_DOWNMIX_TYPE_LTRT;
+        info->center_mix_level        = -3.0;
+        info->center_mix_level_ltrt   = -3.0;
+        info->surround_mix_level      = -6.0;
+        info->surround_mix_level_ltrt = -6.0;
+        info->lfe_mix_level           = -10.0;
+        printf("write: type=%d center=%.1f center_ltrt=%.1f surround=%.1f"
+               " surround_ltrt=%.1f lfe=%.1f\n",
+               info->preferred_downmix_type,
+               info->center_mix_level, info->center_mix_level_ltrt,
+               info->surround_mix_level, info->surround_mix_level_ltrt,
+               info->lfe_mix_level);
+    } else {
+        printf("create: FAIL\n");
+    }
+
+    /* Second call must reuse the existing side data (same pointer, values 
kept). */
+    info2 = av_downmix_info_update_side_data(frame);
+    if (info && info2) {
+        printf("reuse: same_pointer=%s preserved_type=%d 
preserved_center=%.1f\n",
+               info == info2 ? "yes" : "no",
+               info2->preferred_downmix_type, info2->center_mix_level);
+    }
+
+    av_frame_free(&frame);
+
+    /* OOM path: av_max_alloc(1) forces the internal side data allocation to 
fail. */
+    printf("\nTesting OOM path\n");
+    frame = av_frame_alloc();
+    if (frame) {
+        av_max_alloc(1);
+        info = av_downmix_info_update_side_data(frame);
+        printf("OOM: %s\n", info ? "FAIL" : "OK");
+        av_max_alloc(INT_MAX);
+        av_frame_free(&frame);
+    }
+
+    return 0;
+}
diff --git a/tests/fate/libavutil.mak b/tests/fate/libavutil.mak
index eb116280a7..6cec293faf 100644
--- a/tests/fate/libavutil.mak
+++ b/tests/fate/libavutil.mak
@@ -90,6 +90,10 @@ FATE_LIBAVUTIL += fate-dict
 fate-dict: libavutil/tests/dict$(EXESUF)
 fate-dict: CMD = run libavutil/tests/dict$(EXESUF)
 
+FATE_LIBAVUTIL += fate-downmix_info
+fate-downmix_info: libavutil/tests/downmix_info$(EXESUF)
+fate-downmix_info: CMD = run libavutil/tests/downmix_info$(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/downmix_info b/tests/ref/fate/downmix_info
new file mode 100644
index 0000000000..085396865c
--- /dev/null
+++ b/tests/ref/fate/downmix_info
@@ -0,0 +1,8 @@
+Testing av_downmix_info_update_side_data()
+create: OK
+defaults: type=0 center=0.0 center_ltrt=0.0 surround=0.0 surround_ltrt=0.0 
lfe=0.0
+write: type=2 center=-3.0 center_ltrt=-3.0 surround=-6.0 surround_ltrt=-6.0 
lfe=-10.0
+reuse: same_pointer=yes preserved_type=2 preserved_center=-3.0
+
+Testing OOM path
+OOM: OK
-- 
2.52.0


>From 3a7bebbfa911e6ae3bb0a3d223bfaf4bdbf0a01e Mon Sep 17 00:00:00 2001
From: marcos ashton <[email protected]>
Date: Sat, 18 Apr 2026 14:18:28 +0100
Subject: [PATCH 3/4] tests/fate/libavutil: add FATE test for
 hdr_dynamic_metadata

Test av_dynamic_hdr_plus_alloc, av_dynamic_hdr_plus_create_side_data,
av_dynamic_hdr_plus_to_t35 in all three documented modes (size
query, caller-allocated, existing caller-owned buffer) including
the AVERROR_BUFFER_TOO_SMALL path, av_dynamic_hdr_plus_from_t35
as a round-trip inverse for minimal and two-window rich payloads
exercising peak luminance matrices, percentile distribution, tone
mapping and color saturation branches, NULL and oversized error
paths, and av_dynamic_hdr_smpte2094_app5_alloc,
av_dynamic_hdr_smpte2094_app5_create_side_data, the App5 to_t35
and from_t35 round trip with adaptive tone map enabled and
num_alternate_images=2. OOM paths via av_max_alloc.

Coverage for libavutil/hdr_dynamic_metadata.c: 31.02% -> 91.56%

The baseline comes from the HDR10+ sample tests on a full FATE
run (fate-hevc-hdr10-plus-metadata, fate-png-mdcv and the
Matroska/WebM HDR10+ remux tests).

Signed-off-by: marcos ashton <[email protected]>
---
 .forgejo/CODEOWNERS                    |   2 +
 libavutil/Makefile                     |   1 +
 libavutil/tests/hdr_dynamic_metadata.c | 409 +++++++++++++++++++++++++
 tests/fate/libavutil.mak               |   4 +
 tests/ref/fate/hdr_dynamic_metadata    |  57 ++++
 5 files changed, 473 insertions(+)
 create mode 100644 libavutil/tests/hdr_dynamic_metadata.c
 create mode 100644 tests/ref/fate/hdr_dynamic_metadata

diff --git a/.forgejo/CODEOWNERS b/.forgejo/CODEOWNERS
index 87ab1abf62..b2e85a9f66 100644
--- a/.forgejo/CODEOWNERS
+++ b/.forgejo/CODEOWNERS
@@ -236,6 +236,7 @@ libavutil/tests/ambient_viewing_environment.* @MarcosAsh
 libavutil/tests/buffer.* @MarcosAsh
 libavutil/tests/csp.* @MarcosAsh
 libavutil/tests/downmix_info.* @MarcosAsh
+libavutil/tests/hdr_dynamic_metadata.* @MarcosAsh
 libavutil/tests/hdr_dynamic_vivid_metadata.* @MarcosAsh
 libavutil/tests/pixdesc.* @MarcosAsh
 libavutil/tests/tdrdi.* @MarcosAsh
@@ -245,6 +246,7 @@ tests/ref/fate/ambient_viewing_environment @MarcosAsh
 tests/ref/fate/buffer @MarcosAsh
 tests/ref/fate/csp @MarcosAsh
 tests/ref/fate/downmix_info @MarcosAsh
+tests/ref/fate/hdr_dynamic_metadata @MarcosAsh
 tests/ref/fate/hdr_dynamic_vivid_metadata @MarcosAsh
 tests/ref/fate/pixdesc @MarcosAsh
 tests/ref/fate/sub-mcc.* @programmerjake
diff --git a/libavutil/Makefile b/libavutil/Makefile
index 57470bc6d4..5ea2b39ed2 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -286,6 +286,7 @@ TESTPROGS = adler32                                         
            \
             fifo                                                        \
             film_grain_params                                           \
             hash                                                        \
+            hdr_dynamic_metadata                                        \
             hdr_dynamic_vivid_metadata                                  \
             hmac                                                        \
             hwdevice                                                    \
diff --git a/libavutil/tests/hdr_dynamic_metadata.c 
b/libavutil/tests/hdr_dynamic_metadata.c
new file mode 100644
index 0000000000..fa16238f7e
--- /dev/null
+++ b/libavutil/tests/hdr_dynamic_metadata.c
@@ -0,0 +1,409 @@
+/*
+ * 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 <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libavutil/error.h"
+#include "libavutil/frame.h"
+#include "libavutil/hdr_dynamic_metadata.h"
+#include "libavutil/mem.h"
+#include "libavutil/rational.h"
+
+static void fill_hdr_plus_minimal(AVDynamicHDRPlus *s)
+{
+    /* Minimal single-window payload. to_t35 hard-codes application_mode=1, so
+     * use application_version=1 for a clean round-trip check. */
+    s->application_version = 1;
+    s->num_windows         = 1;
+    s->targeted_system_display_maximum_luminance = (AVRational){ 10000, 1 };
+    s->targeted_system_display_actual_peak_luminance_flag = 0;
+
+    s->params[0].maxscl[0]      = (AVRational){ 50000,  100000 };
+    s->params[0].maxscl[1]      = (AVRational){ 60000,  100000 };
+    s->params[0].maxscl[2]      = (AVRational){ 70000,  100000 };
+    s->params[0].average_maxrgb = (AVRational){ 40000,  100000 };
+    s->params[0].num_distribution_maxrgb_percentiles = 0;
+    s->params[0].fraction_bright_pixels = (AVRational){ 250, 1000 };
+
+    s->mastering_display_actual_peak_luminance_flag = 0;
+    s->params[0].tone_mapping_flag             = 0;
+    s->params[0].color_saturation_mapping_flag = 0;
+}
+
+static void test_hdr_plus(void)
+{
+    AVDynamicHDRPlus *hdr, hdr_rt;
+    AVFrame *frame;
+    size_t size = 0, required = 0;
+    uint8_t *buf = NULL;
+    int ret;
+
+    /* av_dynamic_hdr_plus_alloc */
+    printf("Testing av_dynamic_hdr_plus_alloc()\n");
+    hdr = av_dynamic_hdr_plus_alloc(&size);
+    printf("alloc: %s size>0=%s\n", hdr ? "OK" : "FAIL",
+           size > 0 ? "yes" : "no");
+    av_freep(&hdr);
+
+    hdr = av_dynamic_hdr_plus_alloc(NULL);
+    printf("alloc (no size): %s\n", hdr ? "OK" : "FAIL");
+    av_freep(&hdr);
+
+    /* av_dynamic_hdr_plus_create_side_data */
+    printf("\nTesting av_dynamic_hdr_plus_create_side_data()\n");
+    frame = av_frame_alloc();
+    if (frame) {
+        hdr = av_dynamic_hdr_plus_create_side_data(frame);
+        printf("create_side_data: %s\n", hdr ? "OK" : "FAIL");
+        av_frame_free(&frame);
+    }
+
+    /* Build a minimal payload for round-trip tests. */
+    hdr = av_dynamic_hdr_plus_alloc(NULL);
+    if (!hdr)
+        return;
+    fill_hdr_plus_minimal(hdr);
+
+    /* Size-query mode: data=NULL, size receives required byte count. */
+    printf("\nTesting av_dynamic_hdr_plus_to_t35() size query\n");
+    required = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &required);
+    printf("size query: ret=%d required>0=%s\n", ret,
+           required > 0 ? "yes" : "no");
+
+    /* Allocation mode: *data=NULL, function mallocs the buffer. */
+    printf("\nTesting av_dynamic_hdr_plus_to_t35() allocation\n");
+    buf = NULL;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, &buf, &size);
+    printf("alloc mode: ret=%d size_match=%s\n", ret,
+           size == required ? "yes" : "no");
+    av_freep(&buf);
+
+    /* Existing-buffer mode: caller-owned buffer is filled, pointer unchanged. 
*/
+    printf("\nTesting av_dynamic_hdr_plus_to_t35() existing buffer\n");
+    buf = av_malloc(required);
+    if (buf) {
+        uint8_t *orig = buf;
+        size = required;
+        ret = av_dynamic_hdr_plus_to_t35(hdr, &buf, &size);
+        printf("existing buf: ret=%d same_pointer=%s size=%zu\n", ret,
+               buf == orig ? "yes" : "no", size);
+        av_freep(&buf);
+    }
+
+    /* Buffer-too-small: caller-owned buffer shorter than required. */
+    printf("\nTesting av_dynamic_hdr_plus_to_t35() buffer too small\n");
+    if (required > 1) {
+        buf = av_malloc(required - 1);
+        if (buf) {
+            size = required - 1;
+            ret = av_dynamic_hdr_plus_to_t35(hdr, &buf, &size);
+            printf("too small: ret==AVERROR_BUFFER_TOO_SMALL=%s\n",
+                   ret == AVERROR_BUFFER_TOO_SMALL ? "yes" : "no");
+            av_freep(&buf);
+        }
+    }
+
+    /* Round trip: from_t35(to_t35(hdr)) should reproduce the fields. */
+    printf("\nTesting av_dynamic_hdr_plus_from_t35() round trip\n");
+    buf = NULL;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, &buf, &size);
+    if (ret >= 0 && buf) {
+        memset(&hdr_rt, 0, sizeof(hdr_rt));
+        ret = av_dynamic_hdr_plus_from_t35(&hdr_rt, buf, size);
+        printf("from_t35: ret=%d\n", ret);
+        printf("round trip: app_ver=%u num_windows=%u max_lum=%d/%d"
+               " maxscl0=%d/%d avg=%d/%d frac_bright=%d/%d\n",
+               hdr_rt.application_version, hdr_rt.num_windows,
+               hdr_rt.targeted_system_display_maximum_luminance.num,
+               hdr_rt.targeted_system_display_maximum_luminance.den,
+               hdr_rt.params[0].maxscl[0].num,
+               hdr_rt.params[0].maxscl[0].den,
+               hdr_rt.params[0].average_maxrgb.num,
+               hdr_rt.params[0].average_maxrgb.den,
+               hdr_rt.params[0].fraction_bright_pixels.num,
+               hdr_rt.params[0].fraction_bright_pixels.den);
+    }
+    av_freep(&buf);
+
+    /* Error paths. */
+    printf("\nTesting error paths\n");
+    ret = av_dynamic_hdr_plus_to_t35(NULL, &buf, &size);
+    printf("to_t35 NULL s: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, NULL);
+    printf("to_t35 no data no size: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+
+    ret = av_dynamic_hdr_plus_from_t35(NULL, (const uint8_t *)"", 0);
+    printf("from_t35 NULL s: ret==AVERROR(ENOMEM)=%s\n",
+           ret == AVERROR(ENOMEM) ? "yes" : "no");
+
+    {
+        /* Oversized input is rejected before parsing. */
+        size_t big = AV_HDR_PLUS_MAX_PAYLOAD_SIZE + 1;
+        uint8_t *oversized = av_mallocz(big);
+        if (oversized) {
+            ret = av_dynamic_hdr_plus_from_t35(&hdr_rt, oversized, big);
+            printf("from_t35 oversized: ret==AVERROR(EINVAL)=%s\n",
+                   ret == AVERROR(EINVAL) ? "yes" : "no");
+            av_free(oversized);
+        }
+    }
+
+    /* Rich payload: flip every optional flag so all branches get exercised. */
+    printf("\nTesting rich round trip (all flags set)\n");
+    hdr->num_windows = 2;
+    hdr->params[1].window_upper_left_corner_x  = (AVRational){ 100, 1 };
+    hdr->params[1].window_upper_left_corner_y  = (AVRational){ 100, 1 };
+    hdr->params[1].window_lower_right_corner_x = (AVRational){ 500, 1 };
+    hdr->params[1].window_lower_right_corner_y = (AVRational){ 500, 1 };
+    hdr->params[1].center_of_ellipse_x               = 300;
+    hdr->params[1].center_of_ellipse_y               = 300;
+    hdr->params[1].rotation_angle                    = 45;
+    hdr->params[1].semimajor_axis_internal_ellipse   = 50;
+    hdr->params[1].semimajor_axis_external_ellipse   = 100;
+    hdr->params[1].semiminor_axis_external_ellipse   = 80;
+    hdr->params[1].overlap_process_option            = 1;
+    hdr->params[1].maxscl[0]      = (AVRational){ 20000, 100000 };
+    hdr->params[1].maxscl[1]      = (AVRational){ 30000, 100000 };
+    hdr->params[1].maxscl[2]      = (AVRational){ 40000, 100000 };
+    hdr->params[1].average_maxrgb = (AVRational){ 25000, 100000 };
+    hdr->params[1].num_distribution_maxrgb_percentiles = 0;
+    hdr->params[1].fraction_bright_pixels = (AVRational){ 100, 1000 };
+
+    hdr->params[0].num_distribution_maxrgb_percentiles = 2;
+    hdr->params[0].distribution_maxrgb[0].percentage = 50;
+    hdr->params[0].distribution_maxrgb[0].percentile = (AVRational){ 30000, 
100000 };
+    hdr->params[0].distribution_maxrgb[1].percentage = 99;
+    hdr->params[0].distribution_maxrgb[1].percentile = (AVRational){ 90000, 
100000 };
+
+    hdr->targeted_system_display_actual_peak_luminance_flag = 1;
+    hdr->num_rows_targeted_system_display_actual_peak_luminance = 2;
+    hdr->num_cols_targeted_system_display_actual_peak_luminance = 2;
+    for (int i = 0; i < 2; i++)
+        for (int j = 0; j < 2; j++)
+            hdr->targeted_system_display_actual_peak_luminance[i][j] =
+                (AVRational){ i + j, 15 };
+
+    hdr->mastering_display_actual_peak_luminance_flag = 1;
+    hdr->num_rows_mastering_display_actual_peak_luminance = 2;
+    hdr->num_cols_mastering_display_actual_peak_luminance = 2;
+    for (int i = 0; i < 2; i++)
+        for (int j = 0; j < 2; j++)
+            hdr->mastering_display_actual_peak_luminance[i][j] =
+                (AVRational){ 1, 15 };
+
+    for (int w = 0; w < 2; w++) {
+        hdr->params[w].tone_mapping_flag = 1;
+        hdr->params[w].knee_point_x = (AVRational){ 500, 4095 };
+        hdr->params[w].knee_point_y = (AVRational){ 800, 4095 };
+        hdr->params[w].num_bezier_curve_anchors = 3;
+        hdr->params[w].bezier_curve_anchors[0] = (AVRational){ 100, 1023 };
+        hdr->params[w].bezier_curve_anchors[1] = (AVRational){ 500, 1023 };
+        hdr->params[w].bezier_curve_anchors[2] = (AVRational){ 900, 1023 };
+        hdr->params[w].color_saturation_mapping_flag = 1;
+        hdr->params[w].color_saturation_weight = (AVRational){ 8, 8 };
+    }
+
+    buf = NULL;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, &buf, &size);
+    if (ret >= 0 && buf) {
+        memset(&hdr_rt, 0, sizeof(hdr_rt));
+        ret = av_dynamic_hdr_plus_from_t35(&hdr_rt, buf, size);
+        printf("rich: ret=%d num_windows=%u target_peak_flag=%u"
+               " master_peak_flag=%u tone_w0=%u tone_w1=%u"
+               " num_pct_w0=%u sat_w0=%u\n", ret,
+               hdr_rt.num_windows,
+               hdr_rt.targeted_system_display_actual_peak_luminance_flag,
+               hdr_rt.mastering_display_actual_peak_luminance_flag,
+               hdr_rt.params[0].tone_mapping_flag,
+               hdr_rt.params[1].tone_mapping_flag,
+               hdr_rt.params[0].num_distribution_maxrgb_percentiles,
+               hdr_rt.params[0].color_saturation_mapping_flag);
+    }
+    av_freep(&buf);
+
+    av_freep(&hdr);
+
+    /* OOM path for alloc. */
+    printf("\nTesting OOM path\n");
+    av_max_alloc(1);
+    hdr = av_dynamic_hdr_plus_alloc(&size);
+    printf("alloc OOM: %s\n", hdr ? "FAIL" : "OK");
+    av_max_alloc(INT_MAX);
+    av_freep(&hdr);
+}
+
+static void fill_app5_minimal(AVDynamicHDRSmpte2094App5 *s)
+{
+    s->application_version         = 1;
+    s->minimum_application_version = 0;
+    s->has_custom_hdr_reference_white_flag = 1;
+    s->hdr_reference_white         = 0x0203;
+    s->has_adaptive_tone_map_flag  = 0;
+}
+
+static void test_app5(void)
+{
+    AVDynamicHDRSmpte2094App5 *app5, app5_rt;
+    AVFrame *frame;
+    size_t size = 0, required = 0;
+    uint8_t *buf = NULL;
+    int ret;
+
+    /* alloc / create_side_data */
+    printf("\n=== AVDynamicHDRSmpte2094App5 ===\n");
+    printf("Testing av_dynamic_hdr_smpte2094_app5_alloc()\n");
+    app5 = av_dynamic_hdr_smpte2094_app5_alloc(&size);
+    printf("alloc: %s size>0=%s\n", app5 ? "OK" : "FAIL",
+           size > 0 ? "yes" : "no");
+    av_freep(&app5);
+
+    printf("\nTesting av_dynamic_hdr_smpte2094_app5_create_side_data()\n");
+    frame = av_frame_alloc();
+    if (frame) {
+        app5 = av_dynamic_hdr_smpte2094_app5_create_side_data(frame);
+        printf("create_side_data: %s\n", app5 ? "OK" : "FAIL");
+        av_frame_free(&frame);
+    }
+
+    app5 = av_dynamic_hdr_smpte2094_app5_alloc(NULL);
+    if (!app5)
+        return;
+    fill_app5_minimal(app5);
+
+    /* Size query. */
+    required = 0;
+    ret = av_dynamic_hdr_smpte2094_app5_to_t35(app5, NULL, &required);
+    printf("\nTesting av_dynamic_hdr_smpte2094_app5_to_t35()\n");
+    printf("size query: ret=%d required>0=%s\n", ret,
+           required > 0 ? "yes" : "no");
+
+    /* Allocation + round trip. */
+    buf = NULL;
+    size = 0;
+    ret = av_dynamic_hdr_smpte2094_app5_to_t35(app5, &buf, &size);
+    printf("alloc mode: ret=%d size_match=%s\n", ret,
+           size == required ? "yes" : "no");
+
+    if (ret >= 0 && buf) {
+        memset(&app5_rt, 0, sizeof(app5_rt));
+        ret = av_dynamic_hdr_smpte2094_app5_from_t35(&app5_rt, buf, size);
+        printf("round trip: ret=%d app_ver=%u min_ver=%u"
+               " custom_white_flag=%u ref_white=0x%04x adapt_flag=%u\n",
+               ret, app5_rt.application_version,
+               app5_rt.minimum_application_version,
+               app5_rt.has_custom_hdr_reference_white_flag,
+               app5_rt.hdr_reference_white,
+               app5_rt.has_adaptive_tone_map_flag);
+    }
+    av_freep(&buf);
+
+    /* Error paths. */
+    printf("\nTesting error paths\n");
+    ret = av_dynamic_hdr_smpte2094_app5_to_t35(NULL, &buf, &size);
+    printf("to_t35 NULL s: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+
+    ret = av_dynamic_hdr_smpte2094_app5_from_t35(NULL, (const uint8_t *)"", 0);
+    printf("from_t35 NULL s: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+
+    /* application_version >= 8 is rejected by to_t35. */
+    app5->application_version = 8;
+    ret = av_dynamic_hdr_smpte2094_app5_to_t35(app5, NULL, &size);
+    printf("to_t35 invalid ver: ret==AVERROR_INVALIDDATA=%s\n",
+           ret == AVERROR_INVALIDDATA ? "yes" : "no");
+    app5->application_version = 1;
+
+    /* Rich App5: adaptive tone map with alternate images to exercise every
+     * gain-curve branch. */
+    printf("\nTesting App5 rich round trip\n");
+    memset(app5, 0, sizeof(*app5));
+    app5->application_version         = 1;
+    app5->minimum_application_version = 0;
+    app5->has_custom_hdr_reference_white_flag = 0;
+    app5->has_adaptive_tone_map_flag  = 1;
+    app5->baseline_hdr_headroom       = 0x0abc;
+    app5->use_reference_white_tone_mapping_flag = 0;
+    app5->num_alternate_images        = 2;
+    app5->gain_application_space_chromaticities_flag = 3;
+    for (int r = 0; r < 8; r++)
+        app5->gain_application_space_chromaticities[r] = 100 * (r + 1);
+    app5->has_common_component_mix_params_flag = 0;
+    app5->has_common_curve_params_flag         = 0;
+    for (int a = 0; a < 2; a++) {
+        app5->alternate_hdr_headrooms[a] = 0x0100 + a;
+        app5->component_mixing_type[a]   = 3;
+        for (int k = 0; k < 6; k++) {
+            app5->has_component_mixing_coefficient_flag[a][k] = k & 1;
+            app5->component_mixing_coefficient[a][k] = 0x1000 + k;
+        }
+        app5->gain_curve_num_control_points_minus_1[a] = 1;
+        app5->gain_curve_use_pchip_slope_flag[a]       = 0;
+        for (int c = 0; c < 2; c++) {
+            app5->gain_curve_control_points_x[a][c]     = 0x2000 + c;
+            app5->gain_curve_control_points_y[a][c]     = 0x3000 + c;
+            app5->gain_curve_control_points_theta[a][c] = 0x4000 + c;
+        }
+    }
+
+    buf = NULL;
+    size = 0;
+    ret = av_dynamic_hdr_smpte2094_app5_to_t35(app5, &buf, &size);
+    if (ret >= 0 && buf) {
+        memset(&app5_rt, 0, sizeof(app5_rt));
+        ret = av_dynamic_hdr_smpte2094_app5_from_t35(&app5_rt, buf, size);
+        printf("App5 rich: ret=%d adapt=%u num_alt=%u chroma_flag=%u"
+               " common_mix=%u common_curve=%u mix_type[0]=%u"
+               " ctrl_x[0][0]=0x%04x theta[1][1]=0x%04x\n", ret,
+               app5_rt.has_adaptive_tone_map_flag,
+               app5_rt.num_alternate_images,
+               app5_rt.gain_application_space_chromaticities_flag,
+               app5_rt.has_common_component_mix_params_flag,
+               app5_rt.has_common_curve_params_flag,
+               app5_rt.component_mixing_type[0],
+               app5_rt.gain_curve_control_points_x[0][0],
+               app5_rt.gain_curve_control_points_theta[1][1]);
+    }
+    av_freep(&buf);
+
+    av_freep(&app5);
+
+    /* OOM. */
+    printf("\nTesting OOM path\n");
+    av_max_alloc(1);
+    app5 = av_dynamic_hdr_smpte2094_app5_alloc(&size);
+    printf("alloc OOM: %s\n", app5 ? "FAIL" : "OK");
+    av_max_alloc(INT_MAX);
+    av_freep(&app5);
+}
+
+int main(void)
+{
+    test_hdr_plus();
+    test_app5();
+    return 0;
+}
diff --git a/tests/fate/libavutil.mak b/tests/fate/libavutil.mak
index 6cec293faf..c5bdf7b763 100644
--- a/tests/fate/libavutil.mak
+++ b/tests/fate/libavutil.mak
@@ -115,6 +115,10 @@ FATE_LIBAVUTIL += fate-hash
 fate-hash: libavutil/tests/hash$(EXESUF)
 fate-hash: CMD = run libavutil/tests/hash$(EXESUF)
 
+FATE_LIBAVUTIL += fate-hdr_dynamic_metadata
+fate-hdr_dynamic_metadata: libavutil/tests/hdr_dynamic_metadata$(EXESUF)
+fate-hdr_dynamic_metadata: CMD = run 
libavutil/tests/hdr_dynamic_metadata$(EXESUF)
+
 FATE_LIBAVUTIL += fate-hdr_dynamic_vivid_metadata
 fate-hdr_dynamic_vivid_metadata: 
libavutil/tests/hdr_dynamic_vivid_metadata$(EXESUF)
 fate-hdr_dynamic_vivid_metadata: CMD = run 
libavutil/tests/hdr_dynamic_vivid_metadata$(EXESUF)
diff --git a/tests/ref/fate/hdr_dynamic_metadata 
b/tests/ref/fate/hdr_dynamic_metadata
new file mode 100644
index 0000000000..4d419b115d
--- /dev/null
+++ b/tests/ref/fate/hdr_dynamic_metadata
@@ -0,0 +1,57 @@
+Testing av_dynamic_hdr_plus_alloc()
+alloc: OK size>0=yes
+alloc (no size): OK
+
+Testing av_dynamic_hdr_plus_create_side_data()
+create_side_data: OK
+
+Testing av_dynamic_hdr_plus_to_t35() size query
+size query: ret=0 required>0=yes
+
+Testing av_dynamic_hdr_plus_to_t35() allocation
+alloc mode: ret=0 size_match=yes
+
+Testing av_dynamic_hdr_plus_to_t35() existing buffer
+existing buf: ret=0 same_pointer=yes size=16
+
+Testing av_dynamic_hdr_plus_to_t35() buffer too small
+too small: ret==AVERROR_BUFFER_TOO_SMALL=yes
+
+Testing av_dynamic_hdr_plus_from_t35() round trip
+from_t35: ret=0
+round trip: app_ver=1 num_windows=1 max_lum=10000/1 maxscl0=50000/100000 
avg=40000/100000 frac_bright=250/1000
+
+Testing error paths
+to_t35 NULL s: ret==AVERROR(EINVAL)=yes
+to_t35 no data no size: ret==AVERROR(EINVAL)=yes
+from_t35 NULL s: ret==AVERROR(ENOMEM)=yes
+from_t35 oversized: ret==AVERROR(EINVAL)=yes
+
+Testing rich round trip (all flags set)
+rich: ret=0 num_windows=2 target_peak_flag=1 master_peak_flag=1 tone_w0=1 
tone_w1=1 num_pct_w0=2 sat_w0=1
+
+Testing OOM path
+alloc OOM: OK
+
+=== AVDynamicHDRSmpte2094App5 ===
+Testing av_dynamic_hdr_smpte2094_app5_alloc()
+alloc: OK size>0=yes
+
+Testing av_dynamic_hdr_smpte2094_app5_create_side_data()
+create_side_data: OK
+
+Testing av_dynamic_hdr_smpte2094_app5_to_t35()
+size query: ret=0 required>0=yes
+alloc mode: ret=0 size_match=yes
+round trip: ret=0 app_ver=1 min_ver=0 custom_white_flag=1 ref_white=0x0203 
adapt_flag=0
+
+Testing error paths
+to_t35 NULL s: ret==AVERROR(EINVAL)=yes
+from_t35 NULL s: ret==AVERROR(EINVAL)=yes
+to_t35 invalid ver: ret==AVERROR_INVALIDDATA=yes
+
+Testing App5 rich round trip
+App5 rich: ret=0 adapt=1 num_alt=2 chroma_flag=3 common_mix=0 common_curve=0 
mix_type[0]=3 ctrl_x[0][0]=0x2000 theta[1][1]=0x4001
+
+Testing OOM path
+alloc OOM: OK
-- 
2.52.0


>From 9e0ef873484e44b6471510f3e9ff281ca5381520 Mon Sep 17 00:00:00 2001
From: marcos ashton <[email protected]>
Date: Sat, 18 Apr 2026 14:30:39 +0100
Subject: [PATCH 4/4] tests/fate/libavutil: add FATE test for side_data

Test av_frame_side_data_desc and av_frame_side_data_name on
valid and invalid types, av_frame_side_data_get and its _c
variant for present and absent types,
av_frame_side_data_remove (including MULTI types with
duplicates), av_frame_side_data_remove_by_props filtering by
AV_SIDE_DATA_PROP_*, av_frame_side_data_clone with and without
AV_FRAME_SIDE_DATA_FLAG_REPLACE (including the AVERROR(EEXIST)
and AVERROR(EINVAL) paths) and with AV_FRAME_SIDE_DATA_FLAG_UNIQUE,
av_frame_side_data_add in move mode, with
AV_FRAME_SIDE_DATA_FLAG_NEW_REF and with
AV_FRAME_SIDE_DATA_FLAG_REPLACE over an existing entry, and
av_frame_side_data_free.

Coverage for libavutil/side_data.c: 72.14% -> 86.43%

Numbers measured against a full FATE run. Remaining uncovered
lines are internal allocation-failure error paths requiring
injected failures mid-operation.

Signed-off-by: marcos ashton <[email protected]>
---
 .forgejo/CODEOWNERS         |   2 +
 libavutil/Makefile          |   1 +
 libavutil/tests/side_data.c | 196 ++++++++++++++++++++++++++++++++++++
 tests/fate/libavutil.mak    |   4 +
 tests/ref/fate/side_data    |  49 +++++++++
 5 files changed, 252 insertions(+)
 create mode 100644 libavutil/tests/side_data.c
 create mode 100644 tests/ref/fate/side_data

diff --git a/.forgejo/CODEOWNERS b/.forgejo/CODEOWNERS
index b2e85a9f66..bf03b2c5cc 100644
--- a/.forgejo/CODEOWNERS
+++ b/.forgejo/CODEOWNERS
@@ -239,6 +239,7 @@ libavutil/tests/downmix_info.* @MarcosAsh
 libavutil/tests/hdr_dynamic_metadata.* @MarcosAsh
 libavutil/tests/hdr_dynamic_vivid_metadata.* @MarcosAsh
 libavutil/tests/pixdesc.* @MarcosAsh
+libavutil/tests/side_data.* @MarcosAsh
 libavutil/tests/tdrdi.* @MarcosAsh
 libavutil/tests/timestamp.* @MarcosAsh
 tests/ref/.*drawvg.* @ayosec
@@ -249,6 +250,7 @@ tests/ref/fate/downmix_info @MarcosAsh
 tests/ref/fate/hdr_dynamic_metadata @MarcosAsh
 tests/ref/fate/hdr_dynamic_vivid_metadata @MarcosAsh
 tests/ref/fate/pixdesc @MarcosAsh
+tests/ref/fate/side_data @MarcosAsh
 tests/ref/fate/sub-mcc.* @programmerjake
 tests/ref/fate/tdrdi @MarcosAsh
 tests/ref/fate/timestamp @MarcosAsh
diff --git a/libavutil/Makefile b/libavutil/Makefile
index 5ea2b39ed2..25c04b7e97 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -311,6 +311,7 @@ TESTPROGS = adler32                                         
            \
             sha                                                         \
             sha512                                                      \
             samplefmt                                                   \
+            side_data                                                   \
             side_data_array                                             \
             softfloat                                                   \
             spherical                                                   \
diff --git a/libavutil/tests/side_data.c b/libavutil/tests/side_data.c
new file mode 100644
index 0000000000..401a10d759
--- /dev/null
+++ b/libavutil/tests/side_data.c
@@ -0,0 +1,196 @@
+/*
+ * 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 <stdio.h>
+
+#include "libavutil/avassert.h"
+#include "libavutil/buffer.h"
+#include "libavutil/error.h"
+#include "libavutil/frame.h"
+#include "libavutil/macros.h"
+
+typedef struct Set {
+    AVFrameSideData **sd;
+    int            nb_sd;
+} Set;
+
+static AVFrameSideData *new_entry(Set *s, enum AVFrameSideDataType type,
+                                  int32_t value)
+{
+    AVFrameSideData *sd = av_frame_side_data_new(&s->sd, &s->nb_sd, type,
+                                                 sizeof(int32_t), 0);
+    av_assert0(sd);
+    *(int32_t *)sd->data = value;
+    return sd;
+}
+
+static void print_set(const char *label, const Set *s)
+{
+    printf("%s: n=%d\n", label, s->nb_sd);
+    for (int i = 0; i < s->nb_sd; i++)
+        printf("  [%d] %s val=%d\n", i,
+               av_frame_side_data_name(s->sd[i]->type),
+               *(int32_t *)s->sd[i]->data);
+}
+
+int main(void)
+{
+    /* desc / name accessors. */
+    printf("Testing av_frame_side_data_desc() / av_frame_side_data_name()\n");
+    static const enum AVFrameSideDataType known[] = {
+        AV_FRAME_DATA_STEREO3D,                    /* PROP_GLOBAL */
+        AV_FRAME_DATA_DYNAMIC_HDR_PLUS,            /* PROP_COLOR_DEPENDENT */
+        AV_FRAME_DATA_SPHERICAL,                   /* 
PROP_GLOBAL|SIZE_DEPENDENT */
+        AV_FRAME_DATA_DOWNMIX_INFO,                /* PROP_CHANNEL_DEPENDENT */
+        AV_FRAME_DATA_SEI_UNREGISTERED,            /* PROP_MULTI */
+    };
+    for (int i = 0; i < FF_ARRAY_ELEMS(known); i++) {
+        const AVSideDataDescriptor *d = av_frame_side_data_desc(known[i]);
+        const char *n = av_frame_side_data_name(known[i]);
+        printf("  type=%d name_match=%s props=0x%x\n", known[i],
+               d && n && d->name == n ? "yes" : "no",
+               d ? d->props : 0);
+    }
+
+    /* Invalid / unmapped types must return NULL (sd_props gap). */
+    {
+        enum AVFrameSideDataType bad = (enum AVFrameSideDataType)-1;
+        printf("  invalid type desc=%s name=%s\n",
+               av_frame_side_data_desc(bad) ? "non-null" : "null",
+               av_frame_side_data_name(bad) ? "non-null" : "null");
+    }
+
+    /* Populate a set with several types, one of them MULTI. */
+    Set set = { 0 };
+    new_entry(&set, AV_FRAME_DATA_STEREO3D,           100);
+    new_entry(&set, AV_FRAME_DATA_DYNAMIC_HDR_PLUS,   200);
+    new_entry(&set, AV_FRAME_DATA_SEI_UNREGISTERED,   1);
+    new_entry(&set, AV_FRAME_DATA_SEI_UNREGISTERED,   2);
+    new_entry(&set, AV_FRAME_DATA_SPHERICAL,          300);
+    print_set("\nInitial set", &set);
+
+    /* get / get_c: present and missing types. */
+    printf("\nTesting av_frame_side_data_get()\n");
+    {
+        const AVFrameSideData *got =
+            av_frame_side_data_get(set.sd, set.nb_sd, AV_FRAME_DATA_STEREO3D);
+        printf("  stereo3d: %s val=%d\n", got ? "found" : "missing",
+               got ? *(int32_t *)got->data : -1);
+        got = av_frame_side_data_get(set.sd, set.nb_sd,
+                                     AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+        printf("  mastering (absent): %s\n", got ? "FAIL" : "null");
+    }
+
+    /* remove by type clears all matching entries (including duplicates). */
+    printf("\nTesting av_frame_side_data_remove()\n");
+    av_frame_side_data_remove(&set.sd, &set.nb_sd,
+                              AV_FRAME_DATA_SEI_UNREGISTERED);
+    print_set("  after remove SEI_UNREGISTERED", &set);
+
+    /* remove_by_props: PROP_GLOBAL drops STEREO3D and SPHERICAL. */
+    printf("\nTesting av_frame_side_data_remove_by_props()\n");
+    av_frame_side_data_remove_by_props(&set.sd, &set.nb_sd,
+                                       AV_SIDE_DATA_PROP_GLOBAL);
+    print_set("  after remove_by_props(GLOBAL)", &set);
+
+    /* Clone: copy remaining entry into a second set. */
+    printf("\nTesting av_frame_side_data_clone()\n");
+    Set dst = { 0 };
+    if (set.nb_sd > 0) {
+        int ret = av_frame_side_data_clone(&dst.sd, &dst.nb_sd,
+                                           set.sd[0], 0);
+        printf("  clone into empty: ret=%d nb=%d val=%d\n", ret, dst.nb_sd,
+               dst.nb_sd ? *(int32_t *)dst.sd[0]->data : -1);
+
+        /* Same type again without REPLACE must fail with EEXIST. */
+        ret = av_frame_side_data_clone(&dst.sd, &dst.nb_sd, set.sd[0], 0);
+        printf("  clone duplicate no REPLACE: ret==AVERROR(EEXIST)=%s\n",
+               ret == AVERROR(EEXIST) ? "yes" : "no");
+
+        /* With REPLACE, clone replaces the existing entry in place. */
+        ret = av_frame_side_data_clone(&dst.sd, &dst.nb_sd, set.sd[0],
+                                       AV_FRAME_SIDE_DATA_FLAG_REPLACE);
+        printf("  clone REPLACE: ret=%d nb=%d\n", ret, dst.nb_sd);
+
+        /* Invalid args. */
+        ret = av_frame_side_data_clone(NULL, &dst.nb_sd, set.sd[0], 0);
+        printf("  clone NULL sd: ret==AVERROR(EINVAL)=%s\n",
+               ret == AVERROR(EINVAL) ? "yes" : "no");
+    }
+
+    /* add: buffer ownership transfer and NEW_REF. */
+    printf("\nTesting av_frame_side_data_add()\n");
+    {
+        AVBufferRef *buf = av_buffer_allocz(sizeof(int32_t));
+        av_assert0(buf);
+        *(int32_t *)buf->data = 999;
+
+        AVFrameSideData *sd_added =
+            av_frame_side_data_add(&dst.sd, &dst.nb_sd,
+                                   AV_FRAME_DATA_DISPLAYMATRIX,
+                                   &buf, AV_FRAME_SIDE_DATA_FLAG_UNIQUE);
+        printf("  add (move): added=%s pbuf_nulled=%s\n",
+               sd_added ? "yes" : "no", buf == NULL ? "yes" : "no");
+
+        /* NEW_REF: the caller retains its reference. */
+        AVBufferRef *keep = av_buffer_allocz(sizeof(int32_t));
+        av_assert0(keep);
+        *(int32_t *)keep->data = 42;
+        sd_added = av_frame_side_data_add(&dst.sd, &dst.nb_sd,
+                                          AV_FRAME_DATA_MOTION_VECTORS, &keep,
+                                          AV_FRAME_SIDE_DATA_FLAG_NEW_REF);
+        printf("  add (NEW_REF): added=%s caller_ref_kept=%s\n",
+               sd_added ? "yes" : "no", keep ? "yes" : "no");
+        av_buffer_unref(&keep);
+
+        /* REPLACE on existing entry: same type, value swaps in place. */
+        AVBufferRef *repl = av_buffer_allocz(sizeof(int32_t));
+        av_assert0(repl);
+        *(int32_t *)repl->data = 1234;
+        sd_added = av_frame_side_data_add(&dst.sd, &dst.nb_sd,
+                                          AV_FRAME_DATA_DISPLAYMATRIX, &repl,
+                                          AV_FRAME_SIDE_DATA_FLAG_REPLACE);
+        {
+            const AVFrameSideData *after =
+                av_frame_side_data_get(dst.sd, dst.nb_sd,
+                                       AV_FRAME_DATA_DISPLAYMATRIX);
+            printf("  add (REPLACE existing): added=%s new_val=%d\n",
+                   sd_added ? "yes" : "no",
+                   after ? *(int32_t *)after->data : -1);
+        }
+    }
+
+    /* clone with UNIQUE: existing same-type entry is removed first. dst
+     * already has a set.sd[0]->type entry from the REPLACE clone above. */
+    printf("\nTesting av_frame_side_data_clone() UNIQUE\n");
+    if (set.nb_sd > 0) {
+        int before = dst.nb_sd;
+        int ret = av_frame_side_data_clone(&dst.sd, &dst.nb_sd, set.sd[0],
+                                           AV_FRAME_SIDE_DATA_FLAG_UNIQUE);
+        printf("  clone UNIQUE: ret=%d before=%d after=%d\n",
+               ret, before, dst.nb_sd);
+    }
+
+    print_set("\nFinal dst", &dst);
+
+    av_frame_side_data_free(&dst.sd, &dst.nb_sd);
+    av_frame_side_data_free(&set.sd, &set.nb_sd);
+    printf("\nfree: set_nb=%d dst_nb=%d\n", set.nb_sd, dst.nb_sd);
+
+    return 0;
+}
diff --git a/tests/fate/libavutil.mak b/tests/fate/libavutil.mak
index c5bdf7b763..73c0ad830f 100644
--- a/tests/fate/libavutil.mak
+++ b/tests/fate/libavutil.mak
@@ -196,6 +196,10 @@ FATE_LIBAVUTIL += fate-samplefmt
 fate-samplefmt: libavutil/tests/samplefmt$(EXESUF)
 fate-samplefmt: CMD = run libavutil/tests/samplefmt$(EXESUF)
 
+FATE_LIBAVUTIL += fate-side_data
+fate-side_data: libavutil/tests/side_data$(EXESUF)
+fate-side_data: CMD = run libavutil/tests/side_data$(EXESUF)
+
 FATE_LIBAVUTIL += fate-side_data_array
 fate-side_data_array: libavutil/tests/side_data_array$(EXESUF)
 fate-side_data_array: CMD = run libavutil/tests/side_data_array$(EXESUF)
diff --git a/tests/ref/fate/side_data b/tests/ref/fate/side_data
new file mode 100644
index 0000000000..dcf9654bc3
--- /dev/null
+++ b/tests/ref/fate/side_data
@@ -0,0 +1,49 @@
+Testing av_frame_side_data_desc() / av_frame_side_data_name()
+  type=2 name_match=yes props=0x1
+  type=17 name_match=yes props=0x8
+  type=13 name_match=yes props=0x5
+  type=4 name_match=yes props=0x10
+  type=20 name_match=yes props=0x2
+  invalid type desc=null name=null
+
+Initial set: n=5
+  [0] Stereo 3D val=100
+  [1] HDR Dynamic Metadata SMPTE2094-40 (HDR10+) val=200
+  [2] H.26[45] User Data Unregistered SEI message val=1
+  [3] H.26[45] User Data Unregistered SEI message val=2
+  [4] Spherical Mapping val=300
+
+Testing av_frame_side_data_get()
+  stereo3d: found val=100
+  mastering (absent): null
+
+Testing av_frame_side_data_remove()
+  after remove SEI_UNREGISTERED: n=3
+  [0] Stereo 3D val=100
+  [1] HDR Dynamic Metadata SMPTE2094-40 (HDR10+) val=200
+  [2] Spherical Mapping val=300
+
+Testing av_frame_side_data_remove_by_props()
+  after remove_by_props(GLOBAL): n=1
+  [0] HDR Dynamic Metadata SMPTE2094-40 (HDR10+) val=200
+
+Testing av_frame_side_data_clone()
+  clone into empty: ret=0 nb=1 val=200
+  clone duplicate no REPLACE: ret==AVERROR(EEXIST)=yes
+  clone REPLACE: ret=0 nb=1
+  clone NULL sd: ret==AVERROR(EINVAL)=yes
+
+Testing av_frame_side_data_add()
+  add (move): added=yes pbuf_nulled=yes
+  add (NEW_REF): added=yes caller_ref_kept=yes
+  add (REPLACE existing): added=yes new_val=1234
+
+Testing av_frame_side_data_clone() UNIQUE
+  clone UNIQUE: ret=0 before=3 after=3
+
+Final dst: n=3
+  [0] Motion vectors val=42
+  [1] 3x3 displaymatrix val=1234
+  [2] HDR Dynamic Metadata SMPTE2094-40 (HDR10+) val=200
+
+free: set_nb=0 dst_nb=0
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to