FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/jpegmpfenc.c
Date: 2026-10-01 08:33:32
Exec Total Coverage
Lines: 226 286 79.0%
Functions: 15 15 100.0%
Branches: 105 163 64.4%

Line Branch Exec Source
1 /*
2 * JPEG Multi-Picture Format muxer
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /**
22 * @file
23 * Muxes several JPEG images into a single JPEG Multi-Picture Format file.
24 */
25
26 #include <stdio.h>
27 #include <string.h>
28 #include <stddef.h>
29
30 #include "libavutil/avassert.h"
31 #include "libavutil/avstring.h"
32 #include "libavutil/gain_map.h"
33 #include "libavutil/internal.h"
34 #include "libavutil/mem.h"
35
36 #include "libavcodec/bytestream.h"
37 #include "libavcodec/exif.h"
38 #include "libavcodec/gain_map.h"
39 #include "libavcodec/mjpeg.h"
40
41 #include "avformat.h"
42 #include "avio_internal.h"
43 #include "mux.h"
44
45 /* Namespace URIs of the GContainer XMP extension */
46 #define XMP_CONTAINER_NS "http://ns.google.com/photos/1.0/container/"
47 #define XMP_ITEM_NS "http://ns.google.com/photos/1.0/container/item/"
48
49 /* Individual Image Attribute bits, from CIPA DC-007 Figure 8 */
50 enum MPFAttribute {
51 MPF_DEPENDENT_PARENT = 0x80000000,
52 MPF_DEPENDENT_CHILD = 0x40000000,
53 MPF_FORMAT_JPEG = 0x00000000,
54 };
55
56 /* MP Index IFD tags, from CIPA DC-007 Table 3 */
57 enum MPFTag {
58 MPF_TAG_MPFVersion = 0xB000,
59 MPF_TAG_NumberOfImages = 0xB001,
60 MPF_TAG_MPEntry = 0xB002,
61 };
62
63 typedef enum MPFRole {
64 MPF_ROLE_UNKNOWN = 0,
65 MPF_ROLE_PRIMARY,
66 MPF_ROLE_GAIN_MAP,
67 MPF_ROLE_NB,
68 } MPFRole;
69
70 /* APP2 extension segments */
71 typedef enum MPFExt {
72 MPF_EXT_XMP, /* GContainer directory, First Individual Image only */
73 MPF_EXT_INDEX, /* MP Index IFD, First Individual Image only */
74 MPF_EXT_ISO21496, /* ISO 21496-1 version fields (in the base image) */
75 MPF_EXT_NB,
76 } MPFExt;
77
78 static const struct {
79 const char *name;
80 uint32_t type; /* from CIPA DC-007 Table 4 */
81 } mpf_roles[MPF_ROLE_NB] = {
82 [MPF_ROLE_PRIMARY] = { "primary image", 0x030000 },
83 [MPF_ROLE_GAIN_MAP] = { "gain map image", 0x050000 },
84 };
85
86 typedef struct MPFImage MPFImage;
87 struct MPFImage {
88 AVPacket *packet;
89 int index;
90 MPFRole role;
91
92 /* Relative to the packet data (before splicing) */
93 int app2; /* offset to role-specific APP2 header */
94 int has_xmp; /* image already carries an XMP packet */
95 int has_hdrgm; /* .. containing Ultra HDR gain map metadata */
96 int splice_pos; /* where in `packet->data` to splice our segments */
97
98 /* Relative to the output file (after splicing) */
99 int64_t start, end; /* start and end of this image in the output stream */
100 int64_t ext_size[MPF_EXT_NB]; /* APP2 payload sizes */
101
102 /* Image dependency metadata */
103 MPFImage *dep;
104 MPFImage *rev_deps[2]; /* maximum 2 permissible by the spec */
105 int nb_rev_deps;
106 };
107
108 typedef struct MPFMuxContext {
109 MPFImage *images;
110
111 /* Holds the XMP GContainer; if needed */
112 #define XMP_GCONTAINER_MAX_LEN 1024 /* see XMP_GCONTAINER_TEMPLATE */
113 char xmp_gcontainer[1024];
114 int xmp_len; /* or 0 */
115 } MPFMuxContext;
116
117 1 static int register_dep(AVFormatContext *ctx, MPFImage *img, MPFImage *base)
118 {
119
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (base->nb_rev_deps == FF_ARRAY_ELEMS(base->rev_deps)) {
120 ✗ av_log(ctx, AV_LOG_ERROR, "Stream %d cannot depend on %d, maximum "
121 "number of dependents reached (%d).\n", img->index, base->index,
122 (int) FF_ARRAY_ELEMS(base->rev_deps));
123 ✗ return AVERROR(EINVAL);
124 }
125
126 1 base->rev_deps[base->nb_rev_deps++] = img;
127 1 img->dep = base;
128 1 return 0;
129 }
130
131 1 static int init_gain_map(AVFormatContext *ctx, const AVStreamGroup *sg)
132 {
133 1 MPFMuxContext *s = ctx->priv_data;
134 1 const AVStreamGroupLayeredVideo *lv = sg->params.layered_video;
135
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (sg->nb_streams != 2 || lv->el_index >= sg->nb_streams)
136 ✗ return AVERROR(EINVAL);
137
138
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 const AVStream *bl = sg->streams[!lv->el_index];
139 1 const AVStream *el = sg->streams[lv->el_index];
140 1 MPFImage *base = &s->images[bl->index];
141 1 MPFImage *gain = &s->images[el->index];
142
143
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (base->index != 0) {
144 ✗ av_log(ctx, AV_LOG_ERROR, "Stream group %u: the gain map base layer must "
145 "be the primary image (stream 0), got stream %d\n",
146 ✗ sg->index, base->index);
147 ✗ return AVERROR(EINVAL);
148 }
149
150
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (gain->role != MPF_ROLE_UNKNOWN) {
151 ✗ av_log(ctx, AV_LOG_ERROR, "Stream group %u: stream %d already used "
152 ✗ "as %s for a different stream group.\n", sg->index, gain->index,
153 ✗ mpf_roles[gain->role].name);
154 ✗ return AVERROR(EINVAL);
155 }
156
157
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 for (int i = 0; i < base->nb_rev_deps; i++) {
158 ✗ const MPFImage *other = base->rev_deps[i];
159 ✗ if (other->role == MPF_ROLE_GAIN_MAP) {
160 ✗ av_log(ctx, AV_LOG_ERROR, "Stream group %u: stream %d already "
161 ✗ "specified as a gain map for stream %d.\n", sg->index,
162 ✗ other->index, base->index);
163 ✗ return AVERROR(EINVAL);
164 }
165 }
166
167 1 gain->role = MPF_ROLE_GAIN_MAP;
168 1 return register_dep(ctx, gain, base);
169 }
170
171 1 static int mpf_init(AVFormatContext *ctx)
172 {
173 int ret;
174
175 1 MPFMuxContext *s = ctx->priv_data;
176
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ctx->nb_streams < 2)
177 ✗ av_log(ctx, AV_LOG_WARNING, "Specified only a single stream (no-op)\n");
178
179 1 s->images = av_calloc(ctx->nb_streams, sizeof(*s->images));
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!s->images)
181 ✗ return AVERROR(ENOMEM);
182
183
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (int i = 0; i < ctx->nb_streams; i++) {
184 2 MPFImage *img = &s->images[i];
185 2 img->index = i;
186 2 img->start = img->end = -1;
187
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 for (int n = 0; n < MPF_EXT_NB; n++)
188 6 img->ext_size[n] = -1;
189 2 img->packet = av_packet_alloc();
190
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!img->packet)
191 ✗ return AVERROR(ENOMEM);
192 }
193
194 /* Classify streams by their role */
195 1 s->images[0].role = MPF_ROLE_PRIMARY;
196
197
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (unsigned i = 0; i < ctx->nb_stream_groups; i++) {
198 1 const AVStreamGroup *sg = ctx->stream_groups[i];
199
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 switch (sg->type) {
200 1 case AV_STREAM_GROUP_PARAMS_GAIN_MAP:
201
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if ((ret = init_gain_map(ctx, sg)) < 0)
202 ✗ return ret;
203 1 break;
204
205 ✗ default:
206 ✗ av_log(ctx, AV_LOG_ERROR, "Stream group %u: unknown type '%s'\n", i,
207 ✗ avformat_stream_group_name(sg->type));
208 ✗ return AVERROR(EINVAL);
209 }
210 }
211
212
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (int i = 0; i < ctx->nb_streams; i++) {
213
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (s->images[i].role == MPF_ROLE_UNKNOWN) {
214 ✗ av_log(ctx, AV_LOG_ERROR, "Missing stream group for stream %d.\n", i);
215 ✗ return AVERROR(EINVAL);
216 }
217 }
218
219 1 return 0;
220 }
221
222 1 static void parse_app1(MPFImage *img, const GetByteContext *gb, int len)
223 {
224
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (len < sizeof(FF_GAIN_MAP_XMP_IDENT) ||
225
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 memcmp(gb->buffer, FF_GAIN_MAP_XMP_IDENT, sizeof(FF_GAIN_MAP_XMP_IDENT)))
226 ✗ return; /* not an XMP packet; e.g. Exif */
227
228 1 img->has_xmp = 1;
229 1 img->has_hdrgm = !!av_strnstr(gb->buffer, FF_GAIN_MAP_XMP_NAMESPACE, len);
230 }
231
232 1 static int parse_app2(AVFormatContext *ctx, MPFImage *img,
233 const GetByteContext *gb, int len)
234 {
235 #define CHECK_IDENT(strc) (len >= sizeof(strc) && !memcmp(gb->buffer, strc, sizeof(strc)))
236
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 switch (img->role) {
237 1 case MPF_ROLE_GAIN_MAP:
238
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (CHECK_IDENT(AV_ISO21496_IDENTIFIER)) {
239
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (img->app2) {
240 ✗ av_log(ctx, AV_LOG_ERROR, "Stream %d: multiple ISO 21496-1 "
241 "APP2 segments\n", img->index);
242 ✗ return AVERROR_INVALIDDATA;
243 }
244
245
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (len < sizeof(AV_ISO21496_IDENTIFIER) + sizeof(uint16_t[2])) {
246 ✗ av_log(ctx, AV_LOG_ERROR, "Stream %d: ISO 21496-1 payload too "
247 "short\n", img->index);
248 ✗ return AVERROR_INVALIDDATA;
249 }
250 1 img->app2 = bytestream2_tell(gb);
251 }
252 1 break;
253 }
254 #undef CHECK_IDENT
255
256 1 return 0;
257 }
258
259 2 static int mpf_write_packet(AVFormatContext *ctx, AVPacket *pkt)
260 {
261 int ret;
262 2 MPFMuxContext *s = ctx->priv_data;
263 2 MPFImage *img = &s->images[pkt->stream_index];
264
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (img->packet->size) {
265 ✗ av_log(ctx, AV_LOG_ERROR, "Stream %d: expected a single JPEG image\n", img->index);
266 ✗ return AVERROR(EINVAL);
267 }
268
269 GetByteContext gb;
270 2 bytestream2_init(&gb, pkt->data, pkt->size);
271
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
4 if (bytestream2_get_bytes_left(&gb) < 2 ||
272 2 bytestream2_get_be16(&gb) != (0xFF00 | SOI))
273 {
274 ✗ av_log(ctx, AV_LOG_ERROR, "Stream %d: not a JPEG image (no SOI)\n", img->index);
275 ✗ return AVERROR_INVALIDDATA;
276 }
277
278 /* Walk the JPEG marker sections up until the start of entropy coded data */
279
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 while (bytestream2_get_bytes_left(&gb) >= 4) {
280 12 const int pos = bytestream2_tell(&gb);
281
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 if (bytestream2_get_byte(&gb) != 0xFF) {
282 ✗ av_log(ctx, AV_LOG_ERROR, "Stream %d: malformed JPEG marker at 0x%x\n",
283 img->index, pos);
284 ✗ return AVERROR_INVALIDDATA;
285 }
286
287 12 int marker = bytestream2_get_byte(&gb);
288
6/8
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 6 times.
12 if (marker == SOS || marker == TEM || (marker >= RST0 && marker <= EOI)) {
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!img->splice_pos)
290 ✗ img->splice_pos = pos;
291 2 break; /* start of entropy coded data */
292 }
293
294 10 int len = bytestream2_get_be16(&gb) - 2;
295
2/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
10 if (len < 0 || len > bytestream2_get_bytes_left(&gb)) {
296 ✗ av_log(ctx, AV_LOG_ERROR, "Stream %d: truncated marker segment at "
297 "0x%x\n", img->index, pos);
298 ✗ return AVERROR_INVALIDDATA;
299 }
300
301
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 6 times.
10 switch (marker) {
302 2 case APP0:
303 2 break; /* ignored */
304 1 case APP1:
305 1 parse_app1(img, &gb, len);
306 1 break; /* not spliced past */
307 1 case APP2:
308 1 ret = parse_app2(ctx, img, &gb, len);
309
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0)
310 ✗ return ret;
311 av_fallthrough;
312 default:
313 /**
314 * Splice position for the MPF metadata shall be the first segment
315 * position after any SOI, APP0 or APP1 (cf. CIPA DC-007)
316 */
317
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
7 if (!img->splice_pos)
318 2 img->splice_pos = pos;
319 7 break;
320 }
321
322 10 bytestream2_skip(&gb, len);
323 }
324
325 /* Verify completeness of all needed information */
326
2/3
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 switch (img->role) {
327 1 case MPF_ROLE_PRIMARY:
328
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!img->splice_pos) {
329 ✗ av_log(ctx, AV_LOG_ERROR, "Stream %d: truncated JPEG data\n", img->index);
330 ✗ return AVERROR_INVALIDDATA;
331 }
332 1 break;
333 1 case MPF_ROLE_GAIN_MAP:
334
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!img->app2) {
335 ✗ av_log(ctx, AV_LOG_ERROR, "Stream %d was declared as a gain map "
336 "but is missing ISO 21496-1 metadata\n", img->index);
337 ✗ return AVERROR_INVALIDDATA;
338 }
339 1 break;
340 }
341
342 2 return av_packet_ref(img->packet, pkt);
343 }
344
345 14 static void update_offset(AVIOContext *pb, int64_t base, int64_t *offset)
346 {
347 14 int64_t val = avio_tell(pb) - base;
348
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 av_assert0(val >= 0);
349
3/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
14 av_assert0(*offset < 0 || *offset == val);
350 14 *offset = val;
351 14 }
352
353 #define XMP_GCONTAINER_TEMPLATE \
354 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"Adobe XMP Core 5.1.2\">\n" \
355 " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" \
356 " <rdf:Description\n" \
357 " xmlns:Container=\"" XMP_CONTAINER_NS "\"\n" \
358 " xmlns:Item=\"" XMP_ITEM_NS "\"\n" \
359 " xmlns:hdrgm=\"" FF_GAIN_MAP_XMP_NAMESPACE "\"\n" \
360 " hdrgm:Version=\"1.0\">\n" \
361 " <Container:Directory>\n" \
362 " <rdf:Seq>\n" \
363 " <rdf:li rdf:parseType=\"Resource\">\n" \
364 " <Container:Item\n" \
365 " Item:Semantic=\"Primary\"\n" \
366 " Item:Mime=\"image/jpeg\"/>\n" \
367 " </rdf:li>\n" \
368 " <rdf:li rdf:parseType=\"Resource\">\n" \
369 " <Container:Item\n" \
370 " Item:Semantic=\"GainMap\"\n" \
371 " Item:Mime=\"image/jpeg\"\n" \
372 " Item:Length=\"%d\"/>\n" \
373 " </rdf:li>\n" \
374 " </rdf:Seq>\n" \
375 " </Container:Directory>\n" \
376 " </rdf:Description>\n" \
377 " </rdf:RDF>\n" \
378 "</x:xmpmeta>"
379
380 static_assert(XMP_GCONTAINER_MAX_LEN > sizeof(XMP_GCONTAINER_TEMPLATE) + 10,
381 "XMP buffer too small for template and values");
382
383 1 static int mpf_build_gcontainer_xmp(AVFormatContext *ctx)
384 {
385 1 MPFMuxContext *s = ctx->priv_data;
386 1 const MPFImage *base = &s->images[0];
387 1 const MPFImage *gain = NULL;
388
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < base->nb_rev_deps; i++) {
389
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (base->rev_deps[i]->role == MPF_ROLE_GAIN_MAP)
390 1 gain = base->rev_deps[i];
391 }
392
393
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!gain || !gain->has_hdrgm)
394 ✗ return 0;
395
396
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (base->has_xmp) {
397 ✗ av_log(ctx, AV_LOG_WARNING, "The primary image already carries an XMP "
398 "packet; omitting the GContainer directory, as a JPEG file may "
399 "only contain one.\n");
400 ✗ return 0;
401 }
402
403 1 int ret = snprintf(s->xmp_gcontainer, sizeof(s->xmp_gcontainer),
404 1 XMP_GCONTAINER_TEMPLATE, gain->packet->size);
405
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 >= sizeof(s->xmp_gcontainer))
406 ✗ return AVERROR_BUG;
407
408 1 s->xmp_len = ret;
409 1 return 0;
410 }
411
412 2 static void mpf_write_xmp(AVFormatContext *ctx, AVIOContext *pb, MPFImage *img)
413 {
414 2 const MPFMuxContext *s = ctx->priv_data;
415
416 2 avio_w8(pb, 0xFF);
417 2 avio_w8(pb, APP1);
418
419 2 const int64_t start = avio_tell(pb);
420 2 avio_wb16(pb, img->ext_size[MPF_EXT_XMP]);
421 2 avio_write(pb, FF_GAIN_MAP_XMP_IDENT, sizeof(FF_GAIN_MAP_XMP_IDENT));
422 2 avio_write(pb, s->xmp_gcontainer, s->xmp_len);
423
424 2 update_offset(pb, start, &img->ext_size[MPF_EXT_XMP]);
425 2 }
426
427 2 static int mpf_write_index(AVFormatContext *ctx, AVIOContext *pb, int64_t base,
428 MPFImage *img)
429 {
430 2 const MPFMuxContext *s = ctx->priv_data;
431
432 2 avio_w8(pb, 0xFF);
433 2 avio_w8(pb, APP2);
434 2 const int64_t app2_start = avio_tell(pb) - base;
435 2 avio_wb16(pb, img->ext_size[MPF_EXT_INDEX]);
436 2 avio_write(pb, "MPF\0", 4);
437
438 /* every offset below is relative to this byte */
439 2 int64_t tiff_base = avio_tell(pb) - base;
440 #define RELPOS(x) (avio_tell(pb) - base - tiff_base + (x))
441
442 2 avio_write(pb, "MM\0*", 4); /* big endian */
443 2 avio_wb32(pb, RELPOS(4)); /* offset to first IFD */
444
445 /* First (and only) IFD */
446 2 const int nb_tags = 3; /* MPFVersion, NumberOfImages, MPEntry */
447 2 avio_wb16(pb, nb_tags); /* number of IFD tags */
448
449 /* MPFVersion tag */
450 2 avio_wb16(pb, MPF_TAG_MPFVersion);
451 2 avio_wb16(pb, AV_TIFF_UNDEFINED);
452 2 avio_wb32(pb, 4); /* count */
453 2 avio_write(pb, "0100", 4);
454
455 /* NumberOfImages tag */
456 2 avio_wb16(pb, MPF_TAG_NumberOfImages);
457 2 avio_wb16(pb, AV_TIFF_LONG);
458 2 avio_wb32(pb, 1);
459 2 avio_wb32(pb, ctx->nb_streams);
460
461 /* MPEntry tag */
462 2 const int64_t entry_size = sizeof(int32_t[3]) + sizeof(int16_t[2]);
463 2 avio_wb16(pb, MPF_TAG_MPEntry);
464 2 avio_wb16(pb, AV_TIFF_UNDEFINED);
465 2 avio_wb32(pb, ctx->nb_streams * entry_size);
466 2 avio_wb32(pb, RELPOS(4 + 4)); /* offset to MPEntry data */
467 2 avio_wb32(pb, 0); /* no MP Attribute IFD, per DC-007 6.1.1.1 */
468
469
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (int i = 0; i < ctx->nb_streams; i++) {
470 4 const MPFImage *entry = &s->images[i];
471 4 int64_t size = entry->end - entry->start;
472
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 av_assert0(size <= UINT32_MAX); /* would overflow null buffer anyways */
473 4 uint32_t attr = MPF_FORMAT_JPEG | mpf_roles[entry->role].type;
474
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (entry->nb_rev_deps)
475 2 attr |= MPF_DEPENDENT_PARENT;
476
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (entry->dep)
477 2 attr |= MPF_DEPENDENT_CHILD;
478
479 4 avio_wb32(pb, attr);
480 4 avio_wb32(pb, size);
481
482 /* The data offset of the First Individual Image is always zero */
483
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 int64_t offset = i ? entry->start - tiff_base : 0;
484
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 av_assert0(offset <= UINT32_MAX);
485 4 avio_wb32(pb, offset);
486
487 /* Indices are 1-based; 0 means "no dependent image" */
488
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 for (int j = 0; j < FF_ARRAY_ELEMS(entry->rev_deps); j++) {
489 8 const MPFImage *rdep = entry->rev_deps[j];
490
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 avio_wb16(pb, rdep ? rdep->index + 1 : 0);
491 }
492 }
493 #undef RELPOS
494
495 2 update_offset(pb, base + app2_start, &img->ext_size[MPF_EXT_INDEX]);
496 2 return 0;
497 }
498
499 2 static void mpf_write_iso21496(AVIOContext *pb, MPFImage *src)
500 {
501 2 avio_w8(pb, 0xFF);
502 2 avio_w8(pb, APP2);
503
504 2 const int64_t start = avio_tell(pb);
505 2 avio_wb16(pb, src->ext_size[MPF_EXT_ISO21496]);
506
507 /* The primary image contains a subset of the ISO 21496-1 struct, carrying
508 * just the version header (two 16-bit integers), but no payload */
509 2 avio_write(pb, AV_ISO21496_IDENTIFIER, sizeof(AV_ISO21496_IDENTIFIER));
510 2 avio_write(pb, src->packet->data + src->app2 + sizeof(AV_ISO21496_IDENTIFIER),
511 sizeof(uint16_t[2]));
512
513 2 update_offset(pb, start, &src->ext_size[MPF_EXT_ISO21496]);
514 2 }
515
516 4 static int mpf_write_extensions(AVFormatContext *ctx, AVIOContext *pb,
517 int64_t base, MPFImage *img)
518 {
519 4 MPFMuxContext *s = ctx->priv_data;
520 int ret;
521
522
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 switch (img->role) {
523 2 case MPF_ROLE_PRIMARY:
524 /* The GContainer directory goes ahead of the MP Extensions, so that
525 * the latter still directly follow the APP1 segments (DC-007 Fig. 1) */
526
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (s->xmp_len)
527 2 mpf_write_xmp(ctx, pb, img);
528
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (ctx->nb_streams > 1) {
529 2 ret = mpf_write_index(ctx, pb, base, img);
530
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret < 0)
531 ✗ return ret;
532 }
533 2 break;
534 }
535
536
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 for (int i = 0; i < img->nb_rev_deps; i++) {
537 2 MPFImage *rdep = img->rev_deps[i];
538
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 switch (rdep->role) {
539 2 case MPF_ROLE_GAIN_MAP:
540 2 mpf_write_iso21496(pb, rdep);
541 2 break;
542 }
543 }
544
545 4 return 0;
546 }
547
548 2 static int mpf_write(AVFormatContext *ctx, AVIOContext *pb)
549 {
550 2 MPFMuxContext *s = ctx->priv_data;
551 2 const int64_t base = avio_tell(pb);
552 int ret;
553
554
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (int i = 0; i < ctx->nb_streams; i++) {
555 4 MPFImage *img = &s->images[i];
556 4 const AVPacket *pkt = img->packet;
557
558 4 update_offset(pb, base, &img->start);
559 4 avio_write(pb, pkt->data, img->splice_pos);
560
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if ((ret = mpf_write_extensions(ctx, pb, base, img)) < 0)
561 ✗ return ret;
562 4 avio_write(pb, pkt->data + img->splice_pos, pkt->size - img->splice_pos);
563 4 update_offset(pb, base, &img->end);
564 }
565
566 2 return 0;
567 }
568
569 1 static int mpf_write_trailer(AVFormatContext *ctx)
570 {
571 1 MPFMuxContext *s = ctx->priv_data;
572 int ret;
573
574
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (int i = 0; i < ctx->nb_streams; i++) {
575
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!s->images[i].packet->size) {
576 ✗ av_log(ctx, AV_LOG_ERROR, "No image was supplied on stream %d\n", i);
577 ✗ return AVERROR(EINVAL);
578 }
579 }
580
581
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if ((ret = mpf_build_gcontainer_xmp(ctx)) < 0)
582 ✗ return ret;
583
584 /* Write the whole file once to a null buffer to settle offsets/sizes */
585 AVIOContext *null;
586
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if ((ret = ffio_open_null_buf(&null)) < 0)
587 ✗ return ret;
588 1 ret = mpf_write(ctx, null);
589 1 int size = ffio_close_null_buf(null);
590
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0)
591 ✗ return ret;
592
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 else if (size < 0)
593 ✗ return size;
594
595
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (int i = 0; i < ctx->nb_streams; i++) {
596 2 MPFImage *img = &s->images[i];
597
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 for (int j = 0; j < MPF_EXT_NB; j++) {
598
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (img->ext_size[j] > UINT16_MAX) {
599 ✗ av_log(ctx, AV_LOG_ERROR, "Stream %d: APP2 segment %d is too "
600 "large (%"PRId64" bytes)\n", img->index, j, img->ext_size[j]);
601 ✗ return AVERROR(EINVAL);
602 }
603 }
604 }
605
606 1 return mpf_write(ctx, ctx->pb);
607 }
608
609 1 static void mpf_uninit(AVFormatContext *ctx)
610 {
611 1 MPFMuxContext *s = ctx->priv_data;
612
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!s->images)
613 ✗ return;
614
615
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (int i = 0; i < ctx->nb_streams; i++)
616 2 av_packet_free(&s->images[i].packet);
617
618 1 av_freep(&s->images);
619 }
620
621 const FFOutputFormat ff_jpeg_mpf_muxer = {
622 .p.name = "jpeg_mpf",
623 .p.long_name = NULL_IF_CONFIG_SMALL("JPEG Multi-Picture Format"),
624 .p.mime_type = "image/jpeg",
625 .p.extensions = "jpg,jpeg",
626 .priv_data_size = sizeof(MPFMuxContext),
627 .p.flags = AVFMT_NOTIMESTAMPS | AVFMT_NODIMENSIONS,
628 .p.video_codec = AV_CODEC_ID_MJPEG,
629 .flags_internal = FF_OFMT_FLAG_ONLY_DEFAULT_CODECS,
630 .init = mpf_init,
631 .deinit = mpf_uninit,
632 .write_packet = mpf_write_packet,
633 .write_trailer = mpf_write_trailer,
634 .interleave_packet = ff_interleave_packet_passthrough,
635 };
636