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 <marcosashiglesias@gmail.com>
This commit is contained in:
marcos ashton authored and michaelni committed 2026-08-27 10:54:38 +00:00
1 parent b799c7772b
commit 58c47ca583
6 files changed
+877

No files matched your search

+2
View File
@@ -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
+1
View File
@@ -285,6 +285,7 @@ TESTPROGS = adler32 \
fifo \
film_grain_params \
hash \
hdr_dynamic_metadata \
hdr_dynamic_vivid_metadata \
hmac \
hwdevice \
+1
View File
@@ -30,6 +30,7 @@
/file
/film_grain_params
/hash
/hdr_dynamic_metadata
/hdr_dynamic_vivid_metadata
/hmac
/hwcontext_cuda
+723
View File
@@ -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;
}
+4
View File
@@ -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)
+146
View File
@@ -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