[FFmpeg-devel] [PATCH v2 05/16] swscale/utils: read dynamic HDR10+ metadata from AVFrame

Niklas Haas ffmpeg at haasn.xyz
Fri Dec 6 16:32:04 EET 2024


From: Niklas Haas <git at haasn.dev>

Logic ported from libplacebo's AVFrame helpers. The basic idea is to use the
provided MaxRGB/MaxSCL values to infer what the actual luminance would have
been, which HDR10+ metadata does not provide directly. It's worth pointing out
that this gives us an *upper* bound on the true maximum luminance, so any
error in the estimation cannot result in clipping.
---
 libswscale/utils.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/libswscale/utils.c b/libswscale/utils.c
index 3d3fcadacc..b097ec51b2 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -43,6 +43,7 @@
 #include "libavutil/cpu.h"
 #include "libavutil/csp.h"
 #include "libavutil/emms.h"
+#include "libavutil/hdr_dynamic_metadata.h"
 #include "libavutil/imgutils.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/libm.h"
@@ -2745,6 +2746,55 @@ SwsFormat ff_fmt_from_frame(const AVFrame *frame, int field)
         }
     }
 
+    if ((sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DYNAMIC_HDR_PLUS))) {
+        const AVDynamicHDRPlus *dhp = (const AVDynamicHDRPlus *) sd->data;
+        const AVHDRPlusColorTransformParams *pars = &dhp->params[0];
+        const AVRational nits = av_make_q(10000, 1);
+        AVRational maxrgb = pars->maxscl[0];
+
+        if (!dhp->num_windows || dhp->application_version > 1)
+            goto skip_hdr10;
+
+        /* Maximum of MaxSCL components */
+        if (av_cmp_q(pars->maxscl[1], maxrgb) > 0)
+            maxrgb = pars->maxscl[1];
+        if (av_cmp_q(pars->maxscl[2], maxrgb) > 0)
+            maxrgb = pars->maxscl[2];
+
+        if (maxrgb.num > 0) {
+            /* Estimate true luminance from MaxSCL */
+            const AVLumaCoefficients *luma = av_csp_luma_coeffs_from_avcsp(fmt.csp);
+            if (!luma)
+                goto skip_hdr10;
+            fmt.color.frame_peak = av_add_q(av_mul_q(luma->cr, pars->maxscl[0]),
+                                   av_add_q(av_mul_q(luma->cg, pars->maxscl[1]),
+                                            av_mul_q(luma->cb, pars->maxscl[2])));
+            /* Scale the scene average brightness by the ratio between the
+             * maximum luminance and the MaxRGB values */
+            fmt.color.frame_avg = av_mul_q(pars->average_maxrgb,
+                                           av_div_q(fmt.color.frame_peak, maxrgb));
+        } else {
+            /**
+             * Calculate largest value from histogram to use as fallback for
+             * clips with missing MaxSCL information. Note that this may end
+             * up picking the "reserved" value at the 5% percentile, which in
+             * practice appears to track the brightest pixel in the scene.
+             */
+            for (int i = 0; i < pars->num_distribution_maxrgb_percentiles; i++) {
+                const AVRational pct = pars->distribution_maxrgb[i].percentile;
+                if (av_cmp_q(pct, maxrgb) > 0)
+                    maxrgb = pct;
+                fmt.color.frame_peak = maxrgb;
+                fmt.color.frame_avg  = pars->average_maxrgb;
+            }
+        }
+
+        /* Rescale to nits */
+        fmt.color.frame_peak = av_mul_q(nits, fmt.color.frame_peak);
+        fmt.color.frame_avg  = av_mul_q(nits, fmt.color.frame_avg);
+    }
+skip_hdr10:
+
     /* PQ is always scaled down to absolute zero, so ignore mastering metadata */
     if (fmt.color.trc == AVCOL_TRC_SMPTE2084)
         fmt.color.min_luma = av_make_q(0, 1);
-- 
2.47.0



More information about the ffmpeg-devel mailing list