This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

commit 58c47ca583f23a5ab1329f5e026f8f2c33775464
Author:     marcos ashton <[email protected]>
AuthorDate: Sat Apr 18 14:18:28 2026 +0100
Commit:     michaelni <[email protected]>
CommitDate: Thu Aug 27 10:54:38 2026 +0000

    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, and av_dynamic_hdr_plus_from_t35
    as a round-trip inverse.
    
    Two HDR10+ fixtures are used. The first is a conforming
    ST 2094-40:2020 ApplicationVersion 1 payload: one processing
    window, the nine distribution maxrgb percentiles required by
    section 8.5.4, and neither actual-peak-luminance item nor
    ColorSaturationWeight, both excluded for that version by section
    9.4. The second is marked in the source as deliberately
    non-conforming and exists only to reach the optional
    serializer/parser branches: a second processing window with its
    geometry, both actual-peak-luminance matrices, per-window tone
    mapping and color saturation. Every field either fixture
    populates is decoded and printed, so a serializer or parser that
    drops or corrupts a value changes the reference rather than
    passing a flag-only check.
    
    Also test that av_dynamic_hdr_plus_to_t35 ignores
    application_version and always writes the CTA-861-H value, the
    NULL and oversized error paths, and
    av_dynamic_hdr_smpte2094_app5_alloc,
    av_dynamic_hdr_smpte2094_app5_create_side_data and the App5
    to_t35 / from_t35 round trip over four fixtures covering both
    early-out paths (no adaptive tone map, reference-white tone
    mapping) and both sides of the component mixing and gain curve
    selectors: per-image parameters with component mixing type 3 and
    explicit control point slopes, and common parameters shared from
    alternate 0 with a mixing type other than 3 and the pchip slope.
    The App5 to_t35 validation failures and the OOM paths via
    av_max_alloc are covered as well.
    
    Coverage for libavutil/hdr_dynamic_metadata.c: 31.02% -> 95.53%
    
    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/.gitignore             |   1 +
 libavutil/tests/hdr_dynamic_metadata.c | 723 +++++++++++++++++++++++++++++++++
 tests/fate/libavutil.mak               |   4 +
 tests/ref/fate/hdr_dynamic_metadata    | 146 +++++++
 6 files changed, 877 insertions(+)

diff --git a/.forgejo/CODEOWNERS b/.forgejo/CODEOWNERS
index 82c9707553..3e55f000d7 100644
--- a/.forgejo/CODEOWNERS
+++ b/.forgejo/CODEOWNERS
@@ -238,6 +238,7 @@ libavutil/tests/buffer.* @MarcosAsh
 libavutil/tests/csp.* @MarcosAsh
 libavutil/tests/dovi_meta.* @MarcosAsh
 libavutil/tests/downmix_info.* @MarcosAsh
+libavutil/tests/hdr_dynamic_metadata.* @MarcosAsh
 libavutil/tests/hdr_dynamic_vivid_metadata.* @MarcosAsh
 libavutil/tests/mastering_display_metadata.* @MarcosAsh
 libavutil/tests/pixdesc.* @MarcosAsh
@@ -250,6 +251,7 @@ tests/ref/fate/buffer @MarcosAsh
 tests/ref/fate/csp @MarcosAsh
 tests/ref/fate/dovi_meta @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/mastering_display_metadata @MarcosAsh
 tests/ref/fate/pixdesc @MarcosAsh
diff --git a/libavutil/Makefile b/libavutil/Makefile
index fb93ed0cf5..a1170af6eb 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -285,6 +285,7 @@ TESTPROGS = adler32                                         
            \
             fifo                                                        \
             film_grain_params                                           \
             hash                                                        \
+            hdr_dynamic_metadata                                        \
             hdr_dynamic_vivid_metadata                                  \
             hmac                                                        \
             hwdevice                                                    \
diff --git a/libavutil/tests/.gitignore b/libavutil/tests/.gitignore
index 6fa7268abf..6e69431bca 100644
--- a/libavutil/tests/.gitignore
+++ b/libavutil/tests/.gitignore
@@ -30,6 +30,7 @@
 /file
 /film_grain_params
 /hash
+/hdr_dynamic_metadata
 /hdr_dynamic_vivid_metadata
 /hmac
 /hwcontext_cuda
diff --git a/libavutil/tests/hdr_dynamic_metadata.c 
b/libavutil/tests/hdr_dynamic_metadata.c
new file mode 100644
index 0000000000..c2126fb45d
--- /dev/null
+++ b/libavutil/tests/hdr_dynamic_metadata.c
@@ -0,0 +1,723 @@
+/*
+ * 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"
+
+/* Denominators av_dynamic_hdr_plus_to_t35() quantises to. The fixtures below
+ * express every rational in these terms, so a correct round trip reproduces
+ * the input exactly and the decoded values printed here can be compared
+ * against the fixture directly. */
+#define LUMINANCE_DEN          1
+#define PEAK_LUMINANCE_DEN    15
+#define RGB_DEN           100000
+#define FRACTION_PIXEL_DEN  1000
+#define KNEE_POINT_DEN      4095
+#define BEZIER_ANCHOR_DEN   1023
+#define SATURATION_DEN         8
+
+/*
+ * Printers. Every field a fixture populates is printed, so that a serializer
+ * or parser which drops or corrupts a value changes the FATE reference
+ * instead of silently passing a flag-only check.
+ */
+
+static void print_peak_matrix(const char *label, int rows, int cols,
+                              const AVRational m[25][25])
+{
+    printf("    %s %dx%d:", label, rows, cols);
+    for (int i = 0; i < rows; i++)
+        for (int j = 0; j < cols; j++)
+            printf(" %d/%d", m[i][j].num, m[i][j].den);
+    printf("\n");
+}
+
+static void print_window(int w, const AVHDRPlusColorTransformParams *p,
+                         int geometry)
+{
+    printf("  window %d:\n", w);
+    /* Window 0 covers the whole frame; its geometry is not serialized. */
+    if (geometry) {
+        printf("    corners: ul=%d/%d,%d/%d lr=%d/%d,%d/%d\n",
+               p->window_upper_left_corner_x.num,
+               p->window_upper_left_corner_x.den,
+               p->window_upper_left_corner_y.num,
+               p->window_upper_left_corner_y.den,
+               p->window_lower_right_corner_x.num,
+               p->window_lower_right_corner_x.den,
+               p->window_lower_right_corner_y.num,
+               p->window_lower_right_corner_y.den);
+        printf("    ellipse: center=%u,%u rotation=%u semimajor_int=%u"
+               " semimajor_ext=%u semiminor_ext=%u overlap=%u\n",
+               p->center_of_ellipse_x, p->center_of_ellipse_y,
+               p->rotation_angle, p->semimajor_axis_internal_ellipse,
+               p->semimajor_axis_external_ellipse,
+               p->semiminor_axis_external_ellipse,
+               p->overlap_process_option);
+    }
+    printf("    maxscl: %d/%d %d/%d %d/%d average_maxrgb=%d/%d\n",
+           p->maxscl[0].num, p->maxscl[0].den,
+           p->maxscl[1].num, p->maxscl[1].den,
+           p->maxscl[2].num, p->maxscl[2].den,
+           p->average_maxrgb.num, p->average_maxrgb.den);
+    printf("    distribution_maxrgb: n=%u",
+           p->num_distribution_maxrgb_percentiles);
+    for (int i = 0; i < p->num_distribution_maxrgb_percentiles; i++)
+        printf(" %u%%=%d/%d", p->distribution_maxrgb[i].percentage,
+               p->distribution_maxrgb[i].percentile.num,
+               p->distribution_maxrgb[i].percentile.den);
+    printf("\n    fraction_bright_pixels=%d/%d\n",
+           p->fraction_bright_pixels.num, p->fraction_bright_pixels.den);
+    printf("    tone_mapping_flag=%u", p->tone_mapping_flag);
+    if (p->tone_mapping_flag) {
+        printf(" knee=%d/%d,%d/%d anchors=%u:",
+               p->knee_point_x.num, p->knee_point_x.den,
+               p->knee_point_y.num, p->knee_point_y.den,
+               p->num_bezier_curve_anchors);
+        for (int i = 0; i < p->num_bezier_curve_anchors; i++)
+            printf(" %d/%d", p->bezier_curve_anchors[i].num,
+                   p->bezier_curve_anchors[i].den);
+    }
+    printf("\n    color_saturation_mapping_flag=%u",
+           p->color_saturation_mapping_flag);
+    if (p->color_saturation_mapping_flag)
+        printf(" weight=%d/%d", p->color_saturation_weight.num,
+               p->color_saturation_weight.den);
+    printf("\n");
+}
+
+static void print_hdr_plus(const char *label, const AVDynamicHDRPlus *s)
+{
+    printf("%s:\n", label);
+    printf("  application_version=%u num_windows=%u"
+           " targeted_system_display_maximum_luminance=%d/%d\n",
+           s->application_version, s->num_windows,
+           s->targeted_system_display_maximum_luminance.num,
+           s->targeted_system_display_maximum_luminance.den);
+    printf("  targeted_system_display_actual_peak_luminance_flag=%u\n",
+           s->targeted_system_display_actual_peak_luminance_flag);
+    if (s->targeted_system_display_actual_peak_luminance_flag)
+        print_peak_matrix("targeted_peak",
+                          
s->num_rows_targeted_system_display_actual_peak_luminance,
+                          
s->num_cols_targeted_system_display_actual_peak_luminance,
+                          s->targeted_system_display_actual_peak_luminance);
+    printf("  mastering_display_actual_peak_luminance_flag=%u\n",
+           s->mastering_display_actual_peak_luminance_flag);
+    if (s->mastering_display_actual_peak_luminance_flag)
+        print_peak_matrix("mastering_peak",
+                          s->num_rows_mastering_display_actual_peak_luminance,
+                          s->num_cols_mastering_display_actual_peak_luminance,
+                          s->mastering_display_actual_peak_luminance);
+    for (int w = 0; w < s->num_windows; w++)
+        print_window(w, &s->params[w], w > 0);
+}
+
+static void print_app5(const char *label, const AVDynamicHDRSmpte2094App5 *s)
+{
+    printf("%s:\n", label);
+    printf("  application_version=%u minimum_application_version=%u\n",
+           s->application_version, s->minimum_application_version);
+    printf("  has_custom_hdr_reference_white_flag=%u",
+           s->has_custom_hdr_reference_white_flag);
+    if (s->has_custom_hdr_reference_white_flag)
+        printf(" hdr_reference_white=0x%04x", s->hdr_reference_white);
+    printf("\n  has_adaptive_tone_map_flag=%u\n",
+           s->has_adaptive_tone_map_flag);
+    if (!s->has_adaptive_tone_map_flag)
+        return;
+
+    printf("  baseline_hdr_headroom=0x%04x"
+           " use_reference_white_tone_mapping_flag=%u\n",
+           s->baseline_hdr_headroom, s->use_reference_white_tone_mapping_flag);
+    if (s->use_reference_white_tone_mapping_flag)
+        return;
+
+    printf("  num_alternate_images=%u chromaticities_flag=%u"
+           " has_common_component_mix_params_flag=%u"
+           " has_common_curve_params_flag=%u\n",
+           s->num_alternate_images,
+           s->gain_application_space_chromaticities_flag,
+           s->has_common_component_mix_params_flag,
+           s->has_common_curve_params_flag);
+    if (s->gain_application_space_chromaticities_flag == 3) {
+        printf("  chromaticities:");
+        for (int r = 0; r < 8; r++)
+            printf(" 0x%04x", s->gain_application_space_chromaticities[r]);
+        printf("\n");
+    }
+
+    for (int a = 0; a < s->num_alternate_images; a++) {
+        int n = s->gain_curve_num_control_points_minus_1[a] + 1;
+        printf("  alternate %d: hdr_headroom=0x%04x 
component_mixing_type=%u\n",
+               a, s->alternate_hdr_headrooms[a], s->component_mixing_type[a]);
+        if (s->component_mixing_type[a] == 3) {
+            printf("    mixing flag:coefficient:");
+            for (int k = 0; k < 6; k++)
+                printf(" %u:0x%04x",
+                       s->has_component_mixing_coefficient_flag[a][k],
+                       s->component_mixing_coefficient[a][k]);
+            printf("\n");
+        }
+        printf("    curve: num_control_points=%d use_pchip_slope=%u\n", n,
+               s->gain_curve_use_pchip_slope_flag[a]);
+        printf("    x:");
+        for (int c = 0; c < n; c++)
+            printf(" 0x%04x", s->gain_curve_control_points_x[a][c]);
+        printf("\n    y:");
+        for (int c = 0; c < n; c++)
+            printf(" 0x%04x", s->gain_curve_control_points_y[a][c]);
+        printf("\n");
+        /* theta is only serialized when the pchip slope is not used. */
+        if (!s->gain_curve_use_pchip_slope_flag[a]) {
+            printf("    theta:");
+            for (int c = 0; c < n; c++)
+                printf(" 0x%04x", s->gain_curve_control_points_theta[a][c]);
+            printf("\n");
+        }
+    }
+}
+
+/*
+ * Round-trip helpers: serialize, then parse back into a zeroed struct.
+ */
+
+static int hdr_plus_round_trip(const AVDynamicHDRPlus *in, AVDynamicHDRPlus 
*out)
+{
+    uint8_t *buf = NULL;
+    size_t size = 0;
+    int ret;
+
+    ret = av_dynamic_hdr_plus_to_t35(in, &buf, &size);
+    if (ret < 0)
+        return ret;
+
+    memset(out, 0, sizeof(*out));
+    ret = av_dynamic_hdr_plus_from_t35(out, buf, size);
+    av_free(buf);
+    return ret;
+}
+
+static int app5_round_trip(const AVDynamicHDRSmpte2094App5 *in,
+                           AVDynamicHDRSmpte2094App5 *out)
+{
+    uint8_t *buf = NULL;
+    size_t size = 0;
+    int ret;
+
+    ret = av_dynamic_hdr_smpte2094_app5_to_t35(in, &buf, &size);
+    if (ret < 0)
+        return ret;
+
+    memset(out, 0, sizeof(*out));
+    ret = av_dynamic_hdr_smpte2094_app5_from_t35(out, buf, size);
+    av_free(buf);
+    return ret;
+}
+
+/*
+ * Fixtures.
+ */
+
+/* A conforming ST 2094-40:2020 ApplicationVersion 1 payload: exactly one
+ * processing window, neither actual-peak-luminance item and no
+ * ColorSaturationWeight (§9.4 excludes all three for Version 1), and the nine
+ * distribution maxrgb percentiles §8.5.4 requires for that version.
+ * CTA-861-H fixes application_version at 1 for HDR10+ carried over T.35.
+ *
+ * The entries for 5% and 10% are not CFD percentile samples: §8.5.4 reserves
+ * every value other than 0.00000 and 0.00255 for that pair. No other entry may
+ * exceed the largest MaxSCL component either, since §8.3 takes MaxSCL over 
each
+ * RGB component separately while §8.5.2 draws the distribution from the
+ * per-pixel maximum component. */
+static void fill_hdr_plus_conforming(AVDynamicHDRPlus *s)
+{
+    static const uint8_t percentages[9] = { 1, 5, 10, 25, 50, 75, 90, 95, 99 };
+    static const int percentiles[9] = {
+        1000, 0, 255, 25000, 50000, 75000, 90000, 95000, 99000,
+    };
+
+    memset(s, 0, sizeof(*s));
+
+    s->application_version = 1;
+    s->num_windows         = 1;
+    s->targeted_system_display_maximum_luminance =
+        (AVRational){ 400, LUMINANCE_DEN };
+    s->targeted_system_display_actual_peak_luminance_flag = 0;
+    s->mastering_display_actual_peak_luminance_flag       = 0;
+
+    s->params[0].maxscl[0]      = (AVRational){ 50000, RGB_DEN };
+    s->params[0].maxscl[1]      = (AVRational){ 60000, RGB_DEN };
+    s->params[0].maxscl[2]      = (AVRational){ 99000, RGB_DEN };
+    s->params[0].average_maxrgb = (AVRational){ 40000, RGB_DEN };
+
+    s->params[0].num_distribution_maxrgb_percentiles = 9;
+    for (int i = 0; i < 9; i++) {
+        s->params[0].distribution_maxrgb[i].percentage = percentages[i];
+        s->params[0].distribution_maxrgb[i].percentile =
+            (AVRational){ percentiles[i], RGB_DEN };
+    }
+    s->params[0].fraction_bright_pixels =
+        (AVRational){ 250, FRACTION_PIXEL_DEN };
+
+    s->params[0].tone_mapping_flag = 1;
+    s->params[0].knee_point_x = (AVRational){  512, KNEE_POINT_DEN };
+    s->params[0].knee_point_y = (AVRational){ 1024, KNEE_POINT_DEN };
+    s->params[0].num_bezier_curve_anchors = 9;
+    for (int i = 0; i < 9; i++)
+        s->params[0].bezier_curve_anchors[i] =
+            (AVRational){ 100 * (i + 1), BEZIER_ANCHOR_DEN };
+
+    s->params[0].color_saturation_mapping_flag = 0;
+}
+
+/* Deliberately NOT a conforming Version 1 payload: it combines a second
+ * processing window, both actual-peak-luminance matrices and
+ * ColorSaturationWeight, all of which §9.4 excludes for Version 1. It exists
+ * only to drive the optional serializer/parser branches those fields guard.
+ *
+ * tone_mapping_flag stays set for every window because the serializer writes
+ * color_saturation_mapping_flag inside the tone-mapping block while the parser
+ * reads it outside; only tone_mapping_flag=1 round-trips today. */
+static void fill_hdr_plus_synthetic(AVDynamicHDRPlus *s)
+{
+    memset(s, 0, sizeof(*s));
+
+    s->application_version = 1;
+    s->num_windows         = 2;
+    s->targeted_system_display_maximum_luminance =
+        (AVRational){ 10000, LUMINANCE_DEN };
+
+    s->params[1].window_upper_left_corner_x  = (AVRational){ 100, 1 };
+    s->params[1].window_upper_left_corner_y  = (AVRational){ 120, 1 };
+    s->params[1].window_lower_right_corner_x = (AVRational){ 500, 1 };
+    s->params[1].window_lower_right_corner_y = (AVRational){ 520, 1 };
+    s->params[1].center_of_ellipse_x             = 300;
+    s->params[1].center_of_ellipse_y             = 320;
+    s->params[1].rotation_angle                  = 45;
+    s->params[1].semimajor_axis_internal_ellipse = 50;
+    s->params[1].semimajor_axis_external_ellipse = 100;
+    s->params[1].semiminor_axis_external_ellipse = 80;
+    s->params[1].overlap_process_option          = 1;
+
+    s->params[0].maxscl[0]      = (AVRational){ 50000, RGB_DEN };
+    s->params[0].maxscl[1]      = (AVRational){ 60000, RGB_DEN };
+    s->params[0].maxscl[2]      = (AVRational){ 70000, RGB_DEN };
+    s->params[0].average_maxrgb = (AVRational){ 40000, RGB_DEN };
+    s->params[0].num_distribution_maxrgb_percentiles = 2;
+    s->params[0].distribution_maxrgb[0].percentage = 50;
+    s->params[0].distribution_maxrgb[0].percentile =
+        (AVRational){ 30000, RGB_DEN };
+    s->params[0].distribution_maxrgb[1].percentage = 99;
+    s->params[0].distribution_maxrgb[1].percentile =
+        (AVRational){ 90000, RGB_DEN };
+    s->params[0].fraction_bright_pixels =
+        (AVRational){ 250, FRACTION_PIXEL_DEN };
+
+    s->params[1].maxscl[0]      = (AVRational){ 20000, RGB_DEN };
+    s->params[1].maxscl[1]      = (AVRational){ 30000, RGB_DEN };
+    s->params[1].maxscl[2]      = (AVRational){ 40000, RGB_DEN };
+    s->params[1].average_maxrgb = (AVRational){ 25000, RGB_DEN };
+    s->params[1].num_distribution_maxrgb_percentiles = 1;
+    s->params[1].distribution_maxrgb[0].percentage = 25;
+    s->params[1].distribution_maxrgb[0].percentile =
+        (AVRational){ 12500, RGB_DEN };
+    s->params[1].fraction_bright_pixels =
+        (AVRational){ 100, FRACTION_PIXEL_DEN };
+
+    s->targeted_system_display_actual_peak_luminance_flag = 1;
+    s->num_rows_targeted_system_display_actual_peak_luminance = 2;
+    s->num_cols_targeted_system_display_actual_peak_luminance = 3;
+    for (int i = 0; i < 2; i++)
+        for (int j = 0; j < 3; j++)
+            s->targeted_system_display_actual_peak_luminance[i][j] =
+                (AVRational){ i * 3 + j, PEAK_LUMINANCE_DEN };
+
+    s->mastering_display_actual_peak_luminance_flag = 1;
+    s->num_rows_mastering_display_actual_peak_luminance = 3;
+    s->num_cols_mastering_display_actual_peak_luminance = 2;
+    for (int i = 0; i < 3; i++)
+        for (int j = 0; j < 2; j++)
+            s->mastering_display_actual_peak_luminance[i][j] =
+                (AVRational){ 15 - (i * 2 + j), PEAK_LUMINANCE_DEN };
+
+    for (int w = 0; w < 2; w++) {
+        s->params[w].tone_mapping_flag = 1;
+        s->params[w].knee_point_x = (AVRational){ 500 + w, KNEE_POINT_DEN };
+        s->params[w].knee_point_y = (AVRational){ 800 + w, KNEE_POINT_DEN };
+        s->params[w].num_bezier_curve_anchors = 3;
+        s->params[w].bezier_curve_anchors[0] =
+            (AVRational){ 100 + w, BEZIER_ANCHOR_DEN };
+        s->params[w].bezier_curve_anchors[1] =
+            (AVRational){ 500 + w, BEZIER_ANCHOR_DEN };
+        s->params[w].bezier_curve_anchors[2] =
+            (AVRational){ 900 + w, BEZIER_ANCHOR_DEN };
+        s->params[w].color_saturation_mapping_flag = 1;
+        s->params[w].color_saturation_weight =
+            (AVRational){ 8 + w, SATURATION_DEN };
+    }
+}
+
+static void test_hdr_plus(void)
+{
+    AVDynamicHDRPlus *hdr, *hdr_rt;
+    AVFrame *frame;
+    size_t size = 0, required = 0;
+    uint8_t *buf = NULL;
+    int ret;
+
+    printf("=== AVDynamicHDRPlus ===\n");
+
+    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);
+
+    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);
+    }
+
+    hdr    = av_dynamic_hdr_plus_alloc(NULL);
+    hdr_rt = av_dynamic_hdr_plus_alloc(NULL);
+    if (!hdr || !hdr_rt) {
+        av_freep(&hdr);
+        av_freep(&hdr_rt);
+        return;
+    }
+    fill_hdr_plus_conforming(hdr);
+
+    /* Size-query mode: data=NULL, size receives the 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, the function allocates 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: a caller-owned buffer is filled in place. */
+    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_match=%s\n", ret,
+               buf == orig ? "yes" : "no",
+               size == required ? "yes" : "no");
+        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);
+        }
+    }
+
+    printf("\nTesting round trip, conforming ST 2094-40 Version 1 payload\n");
+    ret = hdr_plus_round_trip(hdr, hdr_rt);
+    printf("round trip: ret=%d\n", ret);
+    if (ret >= 0)
+        print_hdr_plus("decoded", hdr_rt);
+
+    /* to_t35() ignores application_version and always writes the CTA-861-H
+     * value, so the parsed field does not echo an out-of-range input. */
+    printf("\nTesting that to_t35() overrides application_version\n");
+    hdr->application_version = 7;
+    ret = hdr_plus_round_trip(hdr, hdr_rt);
+    printf("input=7 ret=%d decoded application_version=%u\n", ret,
+           hdr_rt->application_version);
+    hdr->application_version = 1;
+
+    printf("\nTesting round trip, synthetic payload (branch coverage only)\n");
+    fill_hdr_plus_synthetic(hdr);
+    ret = hdr_plus_round_trip(hdr, hdr_rt);
+    printf("round trip: ret=%d\n", ret);
+    if (ret >= 0)
+        print_hdr_plus("decoded", hdr_rt);
+
+    printf("\nTesting error paths\n");
+    buf  = NULL;
+    size = 0;
+    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(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "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);
+        }
+    }
+
+    av_freep(&hdr);
+    av_freep(&hdr_rt);
+
+    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);
+}
+
+/* Table C.2 early-out: no adaptive tone map, custom reference white only. */
+static void fill_app5_minimal(AVDynamicHDRSmpte2094App5 *s)
+{
+    memset(s, 0, sizeof(*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;
+}
+
+/* Table C.3 early-out: reference-white tone mapping, no alternate images. */
+static void fill_app5_reference_white(AVDynamicHDRSmpte2094App5 *s)
+{
+    memset(s, 0, sizeof(*s));
+    s->application_version        = 2;
+    s->has_adaptive_tone_map_flag = 1;
+    s->baseline_hdr_headroom      = 0x0abc;
+    s->use_reference_white_tone_mapping_flag = 1;
+}
+
+/* Per-image parameters: explicit gain application space chromaticities,
+ * component mixing type 3 with a mix of present and absent coefficients, and
+ * explicit control point slopes (theta). */
+static void fill_app5_per_image(AVDynamicHDRSmpte2094App5 *s)
+{
+    memset(s, 0, sizeof(*s));
+    s->application_version        = 1;
+    s->has_adaptive_tone_map_flag = 1;
+    s->baseline_hdr_headroom      = 0x0abc;
+    s->num_alternate_images       = 2;
+    s->gain_application_space_chromaticities_flag = 3;
+    for (int r = 0; r < 8; r++)
+        s->gain_application_space_chromaticities[r] = 100 * (r + 1);
+    s->has_common_component_mix_params_flag = 0;
+    s->has_common_curve_params_flag         = 0;
+    for (int a = 0; a < 2; a++) {
+        s->alternate_hdr_headrooms[a] = 0x0100 + a;
+        s->component_mixing_type[a]   = 3;
+        for (int k = 0; k < 6; k++) {
+            s->has_component_mixing_coefficient_flag[a][k] = k & 1;
+            s->component_mixing_coefficient[a][k] = 0x1000 + 0x10 * a + k;
+        }
+        s->gain_curve_num_control_points_minus_1[a] = 1;
+        s->gain_curve_use_pchip_slope_flag[a]       = 0;
+        for (int c = 0; c < 2; c++) {
+            s->gain_curve_control_points_x[a][c]     = 0x2000 + 0x10 * a + c;
+            s->gain_curve_control_points_y[a][c]     = 0x3000 + 0x10 * a + c;
+            s->gain_curve_control_points_theta[a][c] = 0x4000 + 0x10 * a + c;
+        }
+    }
+}
+
+/* The complementary selectors: no explicit chromaticities, a component mixing
+ * type other than 3, mixing and curve parameters shared from alternate 0, and
+ * the pchip slope, which omits theta from the payload.
+ *
+ * The common_* flags make the parser copy alternate 0's parameters to the
+ * other alternates, so the fixture sets them identically everywhere; only
+ * alternate_hdr_headrooms and the y control points stay per-image. */
+static void fill_app5_common(AVDynamicHDRSmpte2094App5 *s)
+{
+    memset(s, 0, sizeof(*s));
+    s->application_version        = 1;
+    s->has_adaptive_tone_map_flag = 1;
+    s->baseline_hdr_headroom      = 0x0def;
+    s->num_alternate_images       = 3;
+    s->gain_application_space_chromaticities_flag = 0;
+    s->has_common_component_mix_params_flag = 1;
+    s->has_common_curve_params_flag         = 1;
+    for (int a = 0; a < 3; a++) {
+        s->alternate_hdr_headrooms[a] = 0x0200 + a;
+        s->component_mixing_type[a]   = 1;
+        s->gain_curve_num_control_points_minus_1[a] = 2;
+        s->gain_curve_use_pchip_slope_flag[a]       = 1;
+        for (int c = 0; c < 3; c++) {
+            s->gain_curve_control_points_x[a][c] = 0x5000 + c;
+            s->gain_curve_control_points_y[a][c] = 0x6000 + 0x10 * a + c;
+        }
+    }
+}
+
+static void test_app5(void)
+{
+    AVDynamicHDRSmpte2094App5 *app5, *app5_rt;
+    AVFrame *frame;
+    size_t size = 0, required = 0;
+    uint8_t *buf = NULL;
+    int ret;
+
+    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);
+    app5_rt = av_dynamic_hdr_smpte2094_app5_alloc(NULL);
+    if (!app5 || !app5_rt) {
+        av_freep(&app5);
+        av_freep(&app5_rt);
+        return;
+    }
+    fill_app5_minimal(app5);
+
+    printf("\nTesting av_dynamic_hdr_smpte2094_app5_to_t35()\n");
+    required = 0;
+    ret = av_dynamic_hdr_smpte2094_app5_to_t35(app5, NULL, &required);
+    printf("size query: ret=%d required>0=%s\n", ret,
+           required > 0 ? "yes" : "no");
+
+    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");
+    av_freep(&buf);
+
+    printf("\nTesting round trip, no adaptive tone map\n");
+    ret = app5_round_trip(app5, app5_rt);
+    printf("round trip: ret=%d\n", ret);
+    if (ret >= 0)
+        print_app5("decoded", app5_rt);
+
+    printf("\nTesting round trip, reference-white tone mapping\n");
+    fill_app5_reference_white(app5);
+    ret = app5_round_trip(app5, app5_rt);
+    printf("round trip: ret=%d\n", ret);
+    if (ret >= 0)
+        print_app5("decoded", app5_rt);
+
+    printf("\nTesting round trip, per-image mixing and curve parameters\n");
+    fill_app5_per_image(app5);
+    ret = app5_round_trip(app5, app5_rt);
+    printf("round trip: ret=%d\n", ret);
+    if (ret >= 0)
+        print_app5("decoded", app5_rt);
+
+    printf("\nTesting round trip, common mixing and curve parameters\n");
+    fill_app5_common(app5);
+    ret = app5_round_trip(app5, app5_rt);
+    printf("round trip: ret=%d\n", ret);
+    if (ret >= 0)
+        print_app5("decoded", app5_rt);
+
+    printf("\nTesting error paths\n");
+    buf  = NULL;
+    size = 0;
+    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 does not fit the 3-bit field. */
+    app5->application_version = 8;
+    ret = av_dynamic_hdr_smpte2094_app5_to_t35(app5, NULL, &size);
+    printf("to_t35 invalid application_version: ret==AVERROR_INVALIDDATA=%s\n",
+           ret == AVERROR_INVALIDDATA ? "yes" : "no");
+    app5->application_version = 1;
+
+    /* minimum_application_version above the documented maximum. */
+    app5->minimum_application_version = 3;
+    ret = av_dynamic_hdr_smpte2094_app5_to_t35(app5, NULL, &size);
+    printf("to_t35 invalid minimum_application_version:"
+           " ret==AVERROR_INVALIDDATA=%s\n",
+           ret == AVERROR_INVALIDDATA ? "yes" : "no");
+    app5->minimum_application_version = 0;
+
+    /* More alternate images than the 3-bit count admits. */
+    app5->num_alternate_images = 5;
+    ret = av_dynamic_hdr_smpte2094_app5_to_t35(app5, NULL, &size);
+    printf("to_t35 too many alternate images: ret==AVERROR_INVALIDDATA=%s\n",
+           ret == AVERROR_INVALIDDATA ? "yes" : "no");
+
+    av_freep(&app5);
+    av_freep(&app5_rt);
+
+    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 0bc152dc49..7796c21b7f 100644
--- a/tests/fate/libavutil.mak
+++ b/tests/fate/libavutil.mak
@@ -119,6 +119,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..fbd10e79e9
--- /dev/null
+++ b/tests/ref/fate/hdr_dynamic_metadata
@@ -0,0 +1,146 @@
+=== AVDynamicHDRPlus ===
+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_match=yes
+
+Testing av_dynamic_hdr_plus_to_t35() buffer too small
+too small: ret==AVERROR_BUFFER_TOO_SMALL=yes
+
+Testing round trip, conforming ST 2094-40 Version 1 payload
+round trip: ret=0
+decoded:
+  application_version=1 num_windows=1 
targeted_system_display_maximum_luminance=400/1
+  targeted_system_display_actual_peak_luminance_flag=0
+  mastering_display_actual_peak_luminance_flag=0
+  window 0:
+    maxscl: 50000/100000 60000/100000 99000/100000 average_maxrgb=40000/100000
+    distribution_maxrgb: n=9 1%=1000/100000 5%=0/100000 10%=255/100000 
25%=25000/100000 50%=50000/100000 75%=75000/100000 90%=90000/100000 
95%=95000/100000 99%=99000/100000
+    fraction_bright_pixels=250/1000
+    tone_mapping_flag=1 knee=512/4095,1024/4095 anchors=9: 100/1023 200/1023 
300/1023 400/1023 500/1023 600/1023 700/1023 800/1023 900/1023
+    color_saturation_mapping_flag=0
+
+Testing that to_t35() overrides application_version
+input=7 ret=0 decoded application_version=1
+
+Testing round trip, synthetic payload (branch coverage only)
+round trip: ret=0
+decoded:
+  application_version=1 num_windows=2 
targeted_system_display_maximum_luminance=10000/1
+  targeted_system_display_actual_peak_luminance_flag=1
+    targeted_peak 2x3: 0/15 1/15 2/15 3/15 4/15 5/15
+  mastering_display_actual_peak_luminance_flag=1
+    mastering_peak 3x2: 15/15 14/15 13/15 12/15 11/15 10/15
+  window 0:
+    maxscl: 50000/100000 60000/100000 70000/100000 average_maxrgb=40000/100000
+    distribution_maxrgb: n=2 50%=30000/100000 99%=90000/100000
+    fraction_bright_pixels=250/1000
+    tone_mapping_flag=1 knee=500/4095,800/4095 anchors=3: 100/1023 500/1023 
900/1023
+    color_saturation_mapping_flag=1 weight=8/8
+  window 1:
+    corners: ul=100/1,120/1 lr=500/1,520/1
+    ellipse: center=300,320 rotation=45 semimajor_int=50 semimajor_ext=100 
semiminor_ext=80 overlap=1
+    maxscl: 20000/100000 30000/100000 40000/100000 average_maxrgb=25000/100000
+    distribution_maxrgb: n=1 25%=12500/100000
+    fraction_bright_pixels=100/1000
+    tone_mapping_flag=1 knee=501/4095,801/4095 anchors=3: 101/1023 501/1023 
901/1023
+    color_saturation_mapping_flag=1 weight=9/8
+
+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(EINVAL)=yes
+from_t35 oversized: ret==AVERROR(EINVAL)=yes
+
+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
+
+Testing round trip, no adaptive tone map
+round trip: ret=0
+decoded:
+  application_version=1 minimum_application_version=0
+  has_custom_hdr_reference_white_flag=1 hdr_reference_white=0x0203
+  has_adaptive_tone_map_flag=0
+
+Testing round trip, reference-white tone mapping
+round trip: ret=0
+decoded:
+  application_version=2 minimum_application_version=0
+  has_custom_hdr_reference_white_flag=0
+  has_adaptive_tone_map_flag=1
+  baseline_hdr_headroom=0x0abc use_reference_white_tone_mapping_flag=1
+
+Testing round trip, per-image mixing and curve parameters
+round trip: ret=0
+decoded:
+  application_version=1 minimum_application_version=0
+  has_custom_hdr_reference_white_flag=0
+  has_adaptive_tone_map_flag=1
+  baseline_hdr_headroom=0x0abc use_reference_white_tone_mapping_flag=0
+  num_alternate_images=2 chromaticities_flag=3 
has_common_component_mix_params_flag=0 has_common_curve_params_flag=0
+  chromaticities: 0x0064 0x00c8 0x012c 0x0190 0x01f4 0x0258 0x02bc 0x0320
+  alternate 0: hdr_headroom=0x0100 component_mixing_type=3
+    mixing flag:coefficient: 0:0x0000 1:0x1001 0:0x0000 1:0x1003 0:0x0000 
1:0x1005
+    curve: num_control_points=2 use_pchip_slope=0
+    x: 0x2000 0x2001
+    y: 0x3000 0x3001
+    theta: 0x4000 0x4001
+  alternate 1: hdr_headroom=0x0101 component_mixing_type=3
+    mixing flag:coefficient: 0:0x0000 1:0x1011 0:0x0000 1:0x1013 0:0x0000 
1:0x1015
+    curve: num_control_points=2 use_pchip_slope=0
+    x: 0x2010 0x2011
+    y: 0x3010 0x3011
+    theta: 0x4010 0x4011
+
+Testing round trip, common mixing and curve parameters
+round trip: ret=0
+decoded:
+  application_version=1 minimum_application_version=0
+  has_custom_hdr_reference_white_flag=0
+  has_adaptive_tone_map_flag=1
+  baseline_hdr_headroom=0x0def use_reference_white_tone_mapping_flag=0
+  num_alternate_images=3 chromaticities_flag=0 
has_common_component_mix_params_flag=1 has_common_curve_params_flag=1
+  alternate 0: hdr_headroom=0x0200 component_mixing_type=1
+    curve: num_control_points=3 use_pchip_slope=1
+    x: 0x5000 0x5001 0x5002
+    y: 0x6000 0x6001 0x6002
+  alternate 1: hdr_headroom=0x0201 component_mixing_type=1
+    curve: num_control_points=3 use_pchip_slope=1
+    x: 0x5000 0x5001 0x5002
+    y: 0x6010 0x6011 0x6012
+  alternate 2: hdr_headroom=0x0202 component_mixing_type=1
+    curve: num_control_points=3 use_pchip_slope=1
+    x: 0x5000 0x5001 0x5002
+    y: 0x6020 0x6021 0x6022
+
+Testing error paths
+to_t35 NULL s: ret==AVERROR(EINVAL)=yes
+from_t35 NULL s: ret==AVERROR(EINVAL)=yes
+to_t35 invalid application_version: ret==AVERROR_INVALIDDATA=yes
+to_t35 invalid minimum_application_version: ret==AVERROR_INVALIDDATA=yes
+to_t35 too many alternate images: ret==AVERROR_INVALIDDATA=yes
+
+Testing OOM path
+alloc OOM: OK

-- 
To stop receiving notification emails like this one, please contact
[email protected].
_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to