Commit 42344c7cbb for ffmpeg

commit 42344c7cbb7d03a8ba64bcb0aa6439702037442b
Author: Niklas Haas <git@haasn.dev>
Date:   Fri Oct 2 17:20:38 2026 +0200

    avcodec: implement ISO 21496 gain map parsing

    The parsing logic for this already existed in libavcodec, but was unused
    until now. This commit adds the APP2 extraction logic and the necessary
    scaffolding to make this metadata survive the decoder.

    Since the base layer of a JPEG Ultra HDR image also contains a stub
    copy of the same gain map params struct, we ignore that one here. (It's
    only useful for the demuxer, not the decoder)

    Signed-off-by: Niklas Haas <git@haasn.dev>

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 53c638c878..2ea4f9417e 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -535,7 +535,7 @@ OBJS-$(CONFIG_METASOUND_DECODER)       += metasound.o twinvq.o
 OBJS-$(CONFIG_MICRODVD_DECODER)        += microdvddec.o ass.o
 OBJS-$(CONFIG_MIMIC_DECODER)           += mimic.o
 OBJS-$(CONFIG_MISC4_DECODER)           += misc4.o
-OBJS-$(CONFIG_MJPEG_DECODER)           += mjpegdec.o mjpegdec_common.o
+OBJS-$(CONFIG_MJPEG_DECODER)           += mjpegdec.o mjpegdec_common.o gain_map.o
 OBJS-$(CONFIG_MJPEG_QSV_DECODER)       += qsvdec.o
 OBJS-$(CONFIG_MJPEG_ENCODER)           += mjpegenc.o mjpegenc_common.o \
                                           mjpegenc_huffman.o gain_map.o
diff --git a/libavcodec/mjpegdec.c b/libavcodec/mjpegdec.c
index cbb7268ed9..3716bbf753 100644
--- a/libavcodec/mjpegdec.c
+++ b/libavcodec/mjpegdec.c
@@ -44,6 +44,7 @@
 #include "decode.h"
 #include "exif.h"
 #include "exif_internal.h"
+#include "gain_map.h"
 #include "hwaccel_internal.h"
 #include "hwconfig.h"
 #include "idctdsp.h"
@@ -2126,6 +2127,38 @@ static int mjpeg_decode_app(MJpegDecodeContext *s, int start_code)
             av_log(s->avctx, AV_LOG_WARNING, "Read more ICC markers than are supposed to be coded\n");
     }

+    if (start_code == APP2 && id == AV_RB32("urn:")) {
+        bytestream2_seek(&s->gB, -4, SEEK_CUR); /* include `urn:` in the string */
+        len += 4;
+
+        if (len >= sizeof(AV_ISO21496_IDENTIFIER) &&
+            !memcmp(s->gB.buffer, AV_ISO21496_IDENTIFIER, sizeof(AV_ISO21496_IDENTIFIER)))
+        {
+            bytestream2_skipu(&s->gB, sizeof(AV_ISO21496_IDENTIFIER));
+            len -= sizeof(AV_ISO21496_IDENTIFIER);
+
+            /* Discard gain map version stubs from base layer */
+            if (len <= sizeof(uint16_t[2]))
+                goto out;
+            else if (s->gain_map) {
+                av_log(s->avctx, AV_LOG_WARNING, "Multiple ISO 21496 gain map headers\n");
+                goto out;
+            }
+
+            s->gain_map = av_gain_map_params_alloc(&s->gain_map_size);
+            if (!s->gain_map)
+                return AVERROR(ENOMEM);
+
+            ret = av_gain_map_params_from_iso21496(s->gain_map, s->gB.buffer, len);
+            if (ret < 0) {
+                av_log(s->avctx, AV_LOG_WARNING, "Failed to parse ISO 21496 gain map header\n");
+                av_freep(&s->gain_map);
+                s->gain_map_size = 0;
+                goto out;
+            }
+        }
+    }
+
 out:
     /* slow but needed for extreme adobe jpegs */
     if (len < 0)
@@ -2399,6 +2432,7 @@ int ff_mjpeg_decode_frame_from_buf(AVCodecContext *avctx, AVFrame *frame,

     av_exif_free(&s->exif_metadata);
     av_freep(&s->stereo3d);
+    av_freep(&s->gain_map);
     s->adobe_transform = -1;

     if (s->iccnum != 0)
@@ -2831,6 +2865,18 @@ the_end:
         av_freep(&s->stereo3d);
     }

+    if (s->gain_map) {
+        AVFrameSideData *sd;
+        ret = ff_frame_new_side_data(avctx, frame, AV_FRAME_DATA_GAIN_MAP_PARAMS, s->gain_map_size, &sd);
+        if (ret < 0)
+            return ret;
+
+        if (sd)
+            memcpy(sd->data, s->gain_map, s->gain_map_size);
+        av_freep(&s->gain_map);
+        s->gain_map_size = 0;
+    }
+
     if (s->iccnum != 0 && s->iccnum == s->iccread) {
         AVFrameSideData *sd;
         size_t offset = 0;
@@ -2904,6 +2950,8 @@ av_cold int ff_mjpeg_decode_end(AVCodecContext *avctx)
     av_freep(&s->stereo3d);
     av_freep(&s->ljpeg_buffer);
     s->ljpeg_buffer_size = 0;
+    av_freep(&s->gain_map);
+    s->gain_map_size = 0;

     for (i = 0; i < 3; i++) {
         for (j = 0; j < 4; j++)
diff --git a/libavcodec/mjpegdec.h b/libavcodec/mjpegdec.h
index 9162de851f..30f6947eaa 100644
--- a/libavcodec/mjpegdec.h
+++ b/libavcodec/mjpegdec.h
@@ -29,6 +29,7 @@
 #ifndef AVCODEC_MJPEGDEC_H
 #define AVCODEC_MJPEGDEC_H

+#include "libavutil/gain_map.h"
 #include "libavutil/log.h"
 #include "libavutil/mem_internal.h"
 #include "libavutil/pixdesc.h"
@@ -153,6 +154,9 @@ typedef struct MJpegDecodeContext {

     AVStereo3D *stereo3d; ///!< stereoscopic information (cached, since it is read before frame allocation)

+    AVGainMapParams *gain_map;
+    size_t gain_map_size;
+
     const AVPixFmtDescriptor *pix_desc;

     ICCEntry *iccentries;