FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavcodec/gain_map.c
Date: 2026-10-01 14:18:39
Exec Total Coverage
Lines: 48 107 44.9%
Functions: 4 9 44.4%
Branches: 21 82 25.6%

Line Branch Exec Source
1 /*
2 * This file is part of FFmpeg.
3 *
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * FFmpeg is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 #include <stdio.h>
20
21 #include "libavutil/avassert.h"
22 #include "libavutil/common.h"
23 #include "libavutil/error.h"
24 #include "libavutil/gain_map.h"
25 #include "libavutil/mem.h"
26 #include "libavutil/rational.h"
27
28 #include "bytestream.h"
29 #include "gain_map.h"
30
31 #define ISO21496_HEADER_SIZE 5
32
33 #define ISO21496_FLAG_MULTICHANNEL (1 << 7)
34 #define ISO21496_FLAG_USE_BASE_CG (1 << 6)
35 #define ISO21496_FLAG_RESERVED 0x3f
36
37 ✗ static size_t iso21496_payload_size(int nb_channels)
38 {
39 ✗ return 4 * 4 + (size_t) nb_channels * 10 * 4;
40 }
41
42 ✗ static int make_q(int64_t num, uint32_t den, AVRational *out)
43 {
44 ✗ if (!den)
45 ✗ return AVERROR_INVALIDDATA;
46
47 /* Ensure the result fits in AVRational */
48 ✗ av_reduce(&out->num, &out->den, num, den, INT32_MAX);
49 ✗ return 0;
50 }
51
52 ✗ static int get_sq(GetByteContext *gb, AVRational *out)
53 {
54 ✗ int32_t num = (int32_t) bytestream2_get_be32(gb);
55 ✗ uint32_t den = bytestream2_get_be32u(gb);
56 ✗ return make_q(num, den, out);
57 }
58
59 ✗ static int get_uq(GetByteContext *gb, AVRational *out)
60 {
61 ✗ uint32_t num = bytestream2_get_be32u(gb);
62 ✗ uint32_t den = bytestream2_get_be32u(gb);
63 ✗ return make_q(num, den, out);
64 }
65
66 ✗ int ff_gain_map_params_from_iso21496(AVGainMapParams *restrict p,
67 const uint8_t *data, size_t size)
68 {
69 int ret;
70 ✗ if (!p || !data)
71 ✗ return AVERROR(EINVAL);
72
73 ✗ if (size < ISO21496_HEADER_SIZE || size > INT_MAX)
74 ✗ return AVERROR_INVALIDDATA;
75
76 GetByteContext gb;
77 ✗ bytestream2_init(&gb, data, size);
78
79 /* Parse header */
80 ✗ const uint16_t minimum_version = bytestream2_get_be16(&gb);
81 ✗ const uint16_t writer_version = bytestream2_get_be16(&gb);
82 ✗ if (minimum_version > FF_GAIN_MAP_VERSION || writer_version < minimum_version)
83 ✗ return AVERROR_PATCHWELCOME;
84 ✗ const uint8_t flags = bytestream2_get_byte(&gb);
85
86 /**
87 * While the spec does not require reserved bits to be 0, older versions
88 * of the ISO 21496-1 draft specification had extra flags that got removed
89 * from the final publication, so conservatively error out just in case;
90 * unless the writer version is >0 in which case these may be genuine
91 * additions to later revisions of the published spec, the validity of
92 * ignoring which is already guarded by the `minimum_version` check.
93 */
94 ✗ if (writer_version == 0 && (flags & ISO21496_FLAG_RESERVED))
95 ✗ return AVERROR_INVALIDDATA;
96
97 ✗ p->version = FF_GAIN_MAP_VERSION;
98 ✗ p->nb_channels = (flags & ISO21496_FLAG_MULTICHANNEL) ? 3 : 1;
99 ✗ p->use_base_color_space = !!(flags & ISO21496_FLAG_USE_BASE_CG);
100
101 /* Parse payload */
102 ✗ if (bytestream2_get_bytes_left(&gb) < iso21496_payload_size(p->nb_channels))
103 ✗ return AVERROR_INVALIDDATA;
104
105 ✗ if ((ret = get_uq(&gb, &p->base_hdr_headroom)) < 0 ||
106 ✗ (ret = get_uq(&gb, &p->alternate_hdr_headroom)) < 0)
107 ✗ return ret;
108
109 ✗ for (int c = 0; c < p->nb_channels; c++) {
110 ✗ struct AVGainMapChannel *const ch = &p->channels[c];
111 ✗ if ((ret = get_sq(&gb, &ch->gain_map_min)) < 0 ||
112 ✗ (ret = get_sq(&gb, &ch->gain_map_max)) < 0 ||
113 ✗ (ret = get_uq(&gb, &ch->gamma)) < 0 ||
114 ✗ (ret = get_sq(&gb, &ch->base_offset)) < 0 ||
115 ✗ (ret = get_sq(&gb, &ch->alternate_offset)) < 0)
116 ✗ return ret;
117 }
118
119 /* nb_channels == 1 means every channel shares the same parameters */
120 ✗ for (int c = p->nb_channels; c < 3; c++)
121 ✗ p->channels[c] = p->channels[0];
122
123 ✗ return av_gain_map_params_validate(p);
124 }
125
126 7 static void put_q(PutByteContext *pb, AVRational q)
127 {
128 7 bytestream2_put_be32(pb, (unsigned) q.num);
129 7 bytestream2_put_be32(pb, (unsigned) q.den);
130 7 }
131
132 1 int ff_gain_map_params_to_iso21496(const AVGainMapParams *p, uint8_t *buf)
133 {
134
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!p || !buf)
135 ✗ return AVERROR(EINVAL);
136
137 /* Ensures representability in the bitstream */
138 1 int ret = av_gain_map_params_validate(p);
139
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0)
140 ✗ return ret;
141
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (p->version > FF_GAIN_MAP_VERSION)
143 ✗ return AVERROR_PATCHWELCOME; /* e.g. libraries out of sync */
144
145 PutByteContext pb;
146 1 bytestream2_init_writer(&pb, buf, FF_GAIN_MAP_MAX_PAYLOAD_SIZE);
147
148
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 int channels = av_gain_map_channels_identical(p) ? 1 : p->nb_channels;
149 1 uint8_t flags = 0;
150
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (channels == 3)
151 ✗ flags |= ISO21496_FLAG_MULTICHANNEL;
152
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (p->use_base_color_space)
153 1 flags |= ISO21496_FLAG_USE_BASE_CG;
154
155 1 bytestream2_put_be16(&pb, p->version); /* minimum_version */
156 1 bytestream2_put_be16(&pb, p->version); /* writer_version */
157 1 bytestream2_put_byte(&pb, flags);
158 1 put_q(&pb, p->base_hdr_headroom);
159 1 put_q(&pb, p->alternate_hdr_headroom);
160
161
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int c = 0; c < channels; c++) {
162 1 const struct AVGainMapChannel *const ch = &p->channels[c];
163 1 put_q(&pb, ch->gain_map_min);
164 1 put_q(&pb, ch->gain_map_max);
165 1 put_q(&pb, ch->gamma);
166 1 put_q(&pb, ch->base_offset);
167 1 put_q(&pb, ch->alternate_offset);
168 }
169
170 1 return bytestream2_tell_p(&pb);
171 }
172
173 3 int ff_gain_map_params_check_xmp(const AVGainMapParams *p)
174 {
175
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (av_gain_map_params_validate(p) < 0)
176 ✗ return 0;
177
178
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (p->version > FF_GAIN_MAP_VERSION)
179 ✗ return 0; /* e.g. libraries out of sync */
180
181 /* XMP gain maps only store one set of parameters (shared across channels),
182 * have no way to describe a map applied in the alternate rendition's
183 * color space, and explicitly reject no-op parameters */
184 ✗ return (p->nb_channels == 1 || av_gain_map_channels_identical(p)) &&
185
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
6 p->use_base_color_space &&
186 3 av_cmp_q(p->alternate_hdr_headroom, p->base_hdr_headroom) != 0;
187 }
188
189 #define XMP_GAIN_MAP_TEMPLATE \
190 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 5.5.0\">\n" \
191 " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" \
192 " <rdf:Description rdf:about=\"\"\n" \
193 " xmlns:hdrgm=\"" FF_GAIN_MAP_XMP_NAMESPACE "\"\n" \
194 " hdrgm:Version=\"1.0\"\n" \
195 " hdrgm:GainMapMin=\"%g\"\n" \
196 " hdrgm:GainMapMax=\"%g\"\n" \
197 " hdrgm:Gamma=\"%g\"\n" \
198 " hdrgm:OffsetSDR=\"%g\"\n" \
199 " hdrgm:OffsetHDR=\"%g\"\n" \
200 " hdrgm:HDRCapacityMin=\"%g\"\n" \
201 " hdrgm:HDRCapacityMax=\"%g\"\n" \
202 " hdrgm:BaseRenditionIsHDR=\"%s\"/>\n" \
203 " </rdf:RDF>\n" \
204 "</x:xmpmeta>"
205
206 static_assert(FF_GAIN_MAP_XMP_MAX_LEN > sizeof(XMP_GAIN_MAP_TEMPLATE) + 8 * 10,
207 "XMP buffer too small for template and values");
208
209 1 int ff_gain_map_params_to_xmp(const AVGainMapParams *p, char *buf)
210 {
211
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!p || !buf)
212 ✗ return AVERROR(EINVAL);
213
214
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (!ff_gain_map_params_check_xmp(p))
215 ✗ return AVERROR(ENOTSUP);
216
217 1 const struct AVGainMapChannel *const ch = &p->channels[0];
218 1 AVRational offset_sdr = ch->base_offset;
219 1 AVRational offset_hdr = ch->alternate_offset;
220 1 AVRational hdr_cap_min = p->base_hdr_headroom;
221 1 AVRational hdr_cap_max = p->alternate_hdr_headroom;
222
223 1 const int base_is_hdr = av_cmp_q(p->base_hdr_headroom,
224 1 p->alternate_hdr_headroom) > 0;
225
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (base_is_hdr) {
226 ✗ FFSWAP(AVRational, offset_sdr, offset_hdr);
227 ✗ FFSWAP(AVRational, hdr_cap_min, hdr_cap_max);
228 }
229
230 1 const size_t size = FF_GAIN_MAP_XMP_MAX_LEN;
231
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 int ret = snprintf(buf, size, XMP_GAIN_MAP_TEMPLATE,
232 av_q2d(ch->gain_map_min), av_q2d(ch->gain_map_max),
233 av_q2d(ch->gamma),
234 av_q2d(offset_sdr), av_q2d(offset_hdr),
235 av_q2d(hdr_cap_min), av_q2d(hdr_cap_max),
236 base_is_hdr ? "True" : "False");
237
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (ret < 0 || ret >= size)
238 ✗ return AVERROR_BUG;
239
240 1 return ret;
241 }
242