FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavcodec/bsf/jpegapp.c
Date: 2026-10-16 04:22:46
Exec Total Coverage
Lines: 91 124 73.4%
Functions: 6 6 100.0%
Branches: 51 88 58.0%

Line Branch Exec Source
1 /*
2 * Inject per-frame JPEG APP segments from a length-prefixed sidecar.
3 *
4 * Copyright (c) 2026 Reinhard Nißl
5 *
6 * This file is part of FFmpeg.
7 *
8 * FFmpeg is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * FFmpeg is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with FFmpeg; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23 /**
24 * Sidecar format (big-endian), one record per encoded JPEG packet:
25 *
26 * u32be length
27 * u8 payload[length]
28 *
29 * Payload is zero or more complete JPEG APP segments (APP0..APP15),
30 * each ``FF En`` + 16-bit length (length includes those two bytes).
31 * A zero length leaves the packet unchanged.
32 *
33 * After SOI the payload is inserted. If it starts with APP0 and the
34 * two bytes immediately after SOI are an APP0 marker, that segment is
35 * dropped so the sidecar can replace it. An APP0 preceded by fill
36 * bytes or another segment is retained. An APP1-only payload keeps
37 * the encoder APP0: SOI, APP1, APP0, DQT, ...
38 *
39 * A sidecar read or validation error stops further record consumption
40 * for this filter context. Flushing does not clear that error.
41 */
42
43 #include <errno.h>
44 #include <inttypes.h>
45 #include <limits.h>
46 #include <stdint.h>
47 #include <stdio.h>
48 #include <string.h>
49
50 #include "libavutil/error.h"
51 #include "libavutil/file_open.h"
52 #include "libavutil/intreadwrite.h"
53 #include "libavutil/log.h"
54 #include "libavutil/mem.h"
55 #include "libavutil/opt.h"
56
57 #include "libavcodec/bsf.h"
58 #include "libavcodec/bsf_internal.h"
59 #include "libavcodec/mjpeg.h"
60
61 #define JPEGAPP_MAX_RECORD (16 * 1024 * 1024)
62
63 typedef struct JPEGAppContext {
64 const AVClass *class;
65 char *filename;
66 FILE *file;
67 uint8_t *buf;
68 int buf_size;
69 int read_error;
70 int64_t packet_index;
71 } JPEGAppContext;
72
73 3 static int jpeg_app_segment_len(const uint8_t *data, int size)
74 {
75 int len;
76
77
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
3 if (size < 4 || data[0] != 0xff || data[1] < APP0 || data[1] > APP15)
78 ✗ return AVERROR_INVALIDDATA;
79 3 len = AV_RB16(data + 2);
80
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (len < 2 || len > size - 2)
81 ✗ return AVERROR_INVALIDDATA;
82 3 return 2 + len;
83 }
84
85 2 static int jpegapp_validate_payload(const uint8_t *data, int size)
86 {
87 2 int pos = 0, n;
88
89
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 while (pos < size) {
90 2 n = jpeg_app_segment_len(data + pos, size - pos);
91
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (n < 0)
92 ✗ return n;
93 2 pos += n;
94 }
95 2 return 0;
96 }
97
98 4 static int jpegapp_init(AVBSFContext *ctx)
99 {
100 4 JPEGAppContext *s = ctx->priv_data;
101
102
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (!s->filename || !s->filename[0]) {
103 ✗ av_log(ctx, AV_LOG_ERROR, "Option filename is required\n");
104 ✗ return AVERROR(EINVAL);
105 }
106
107 4 s->file = avpriv_fopen_utf8(s->filename, "rb");
108
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!s->file) {
109 ✗ int err = errno ? AVERROR(errno) : AVERROR(EIO);
110 ✗ av_log(ctx, AV_LOG_ERROR, "Could not open '%s': %s\n",
111 ✗ s->filename, av_err2str(err));
112 ✗ return err;
113 }
114 4 return 0;
115 }
116
117 4 static void jpegapp_close(AVBSFContext *ctx)
118 {
119 4 JPEGAppContext *s = ctx->priv_data;
120
121
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (s->file) {
122 4 fclose(s->file);
123 4 s->file = NULL;
124 }
125 4 av_freep(&s->buf);
126 4 s->buf_size = 0;
127 4 }
128
129 8 static int jpegapp_read_record(AVBSFContext *ctx, int *out_size)
130 {
131 8 JPEGAppContext *s = ctx->priv_data;
132 uint8_t hdr[4];
133 uint32_t n;
134 size_t got;
135 int ret;
136
137 8 errno = 0;
138 8 got = fread(hdr, 1, 4, s->file);
139 8 ret = errno;
140
4/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
8 if (got == 0 && feof(s->file) && !ferror(s->file)) {
141 1 av_log(ctx, AV_LOG_ERROR,
142 "jpegapp: no sidecar record for packet %"PRId64"\n",
143 s->packet_index);
144 1 return AVERROR_INVALIDDATA;
145 }
146
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (got != 4) {
147 ✗ if (ferror(s->file)) {
148 ✗ av_log(ctx, AV_LOG_ERROR, "jpegapp: sidecar read failed\n");
149 ✗ return ret ? AVERROR(ret) : AVERROR(EIO);
150 }
151 ✗ av_log(ctx, AV_LOG_ERROR, "jpegapp: truncated length prefix\n");
152 ✗ return AVERROR_INVALIDDATA;
153 }
154 7 n = AV_RB32(hdr);
155
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
7 if (n > JPEGAPP_MAX_RECORD) {
156 2 av_log(ctx, AV_LOG_ERROR, "jpegapp: record length %u too large\n", n);
157 2 return AVERROR_INVALIDDATA;
158 }
159
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (n > (uint32_t)s->buf_size) {
160 1 ret = av_reallocp(&s->buf, n);
161
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0) {
162 ✗ s->buf_size = 0;
163 ✗ return ret;
164 }
165 1 s->buf_size = n;
166 }
167
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 if (n) {
168 2 errno = 0;
169 2 got = fread(s->buf, 1, n, s->file);
170 2 ret = errno;
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (got != n) {
172 ✗ if (ferror(s->file)) {
173 ✗ av_log(ctx, AV_LOG_ERROR, "jpegapp: sidecar read failed\n");
174 ✗ return ret ? AVERROR(ret) : AVERROR(EIO);
175 }
176 ✗ av_log(ctx, AV_LOG_ERROR, "jpegapp: truncated record payload\n");
177 ✗ return AVERROR_INVALIDDATA;
178 }
179 2 ret = jpegapp_validate_payload(s->buf, n);
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret < 0) {
181 ✗ av_log(ctx, AV_LOG_ERROR,
182 "jpegapp: payload is not a sequence of APP0..APP15 segments\n");
183 ✗ return ret;
184 }
185 }
186 5 *out_size = n;
187 5 return 0;
188 }
189
190 156 static int jpegapp_filter(AVBSFContext *ctx, AVPacket *out)
191 {
192 156 JPEGAppContext *s = ctx->priv_data;
193 AVPacket *in;
194 156 int rec_size = 0, src_off, dst_size, ret;
195
196 156 ret = ff_bsf_get_packet(ctx, &in);
197
2/2
✓ Branch 0 taken 77 times.
✓ Branch 1 taken 79 times.
156 if (ret < 0)
198 77 return ret;
199
200 79 ret = s->read_error;
201
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 71 times.
79 if (!ret)
202 8 ret = jpegapp_read_record(ctx, &rec_size);
203 79 s->packet_index++;
204
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 5 times.
79 if (ret < 0) {
205 74 s->read_error = ret;
206 74 goto fail;
207 }
208
209
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if (in->size < 2 || AV_RB16(in->data) != 0xffd8) {
210 ✗ av_log(ctx, AV_LOG_ERROR, "jpegapp: packet is not JPEG (missing SOI)\n");
211 ✗ ret = AVERROR_INVALIDDATA;
212 ✗ goto fail;
213 }
214
215 5 src_off = 2;
216
5/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
5 if (rec_size >= 2 && s->buf[0] == 0xff && s->buf[1] == APP0 &&
217
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 in->size >= 4 && in->data[2] == 0xff && in->data[3] == APP0) {
218 1 int skip = jpeg_app_segment_len(in->data + 2, in->size - 2);
219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (skip < 0) {
220 ✗ av_log(ctx, AV_LOG_ERROR,
221 "jpegapp: existing APP0 is invalid or truncated\n");
222 ✗ ret = skip;
223 ✗ goto fail;
224 }
225 1 src_off = 2 + skip;
226 }
227
228
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (!rec_size) {
229 3 ret = av_packet_ref(out, in);
230 3 goto fail;
231 }
232
233
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (in->size - src_off > INT_MAX - 2 - rec_size) {
234 ✗ ret = AVERROR(ERANGE);
235 ✗ goto fail;
236 }
237 2 dst_size = 2 + rec_size + (in->size - src_off);
238 2 ret = av_new_packet(out, dst_size);
239
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret < 0)
240 ✗ goto fail;
241 2 ret = av_packet_copy_props(out, in);
242
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret < 0)
243 ✗ goto fail;
244
245 2 memcpy(out->data, in->data, 2);
246 2 memcpy(out->data + 2, s->buf, rec_size);
247 2 memcpy(out->data + 2 + rec_size, in->data + src_off, in->size - src_off);
248 2 av_packet_free(&in);
249 2 return 0;
250
251 77 fail:
252
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 3 times.
77 if (ret < 0)
253 74 av_packet_unref(out);
254 77 av_packet_free(&in);
255 77 return ret;
256 }
257
258 #define OFFSET(x) offsetof(JPEGAppContext, x)
259 #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_BSF_PARAM)
260 static const AVOption jpegapp_options[] = {
261 { "filename", "sidecar of u32be-length + APP-segment records", OFFSET(filename),
262 AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
263 { "file", "sidecar of u32be-length + APP-segment records", OFFSET(filename),
264 AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
265 { NULL },
266 };
267
268 static const AVClass jpegapp_class = {
269 .class_name = "jpegapp",
270 .item_name = av_default_item_name,
271 .option = jpegapp_options,
272 .version = LIBAVUTIL_VERSION_INT,
273 };
274
275 static const enum AVCodecID jpegapp_codec_ids[] = {
276 AV_CODEC_ID_MJPEG, AV_CODEC_ID_NONE,
277 };
278
279 const FFBitStreamFilter ff_jpegapp_bsf = {
280 .p.name = "jpegapp",
281 .p.codec_ids = jpegapp_codec_ids,
282 .p.priv_class = &jpegapp_class,
283 .priv_data_size = sizeof(JPEGAppContext),
284 .init = jpegapp_init,
285 .close = jpegapp_close,
286 .filter = jpegapp_filter,
287 };
288