FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/tests/api/api-enc-parser-test.c
Date: 2026-05-03 23:58:45
Exec Total Coverage
Lines: 148 209 70.8%
Functions: 5 5 100.0%
Branches: 67 122 54.9%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2026 Soham Kute
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23 /*
24 * Encoder + parser API test.
25 * Usage: api-enc-parser-test [codec_name [width height]]
26 * Defaults: h261, 176, 144
27 *
28 * Encodes two frames with the named encoder, concatenates the packets,
29 * and feeds the result to the matching parser to verify frame boundary
30 * detection. For each non-empty output the size and up to four bytes
31 * at the start and end are printed for comparison against a reference file.
32 */
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 #include "libavcodec/avcodec.h"
39 #include "libavutil/log.h"
40 #include "libavutil/mem.h"
41 #include "libavutil/pixdesc.h"
42
43 /* Garbage with no PSC - parser must return out_size == 0 */
44 static const uint8_t garbage[] = {
45 0xff, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78,
46 };
47
48 /*
49 * Encode n_frames of video at width x height using enc.
50 * Returns concatenated raw bitstream; caller must av_free() it.
51 * Returns NULL on error.
52 */
53 2 static uint8_t *encode_frames(const AVCodec *enc, int width, int height,
54 int n_frames, size_t *out_size)
55 {
56 2 AVCodecContext *enc_ctx = NULL;
57 2 AVFrame *frame = NULL;
58 2 AVPacket *pkt = NULL;
59 2 uint8_t *buf = NULL, *tmp;
60 2 size_t buf_size = 0;
61 const enum AVPixelFormat *pix_fmts;
62 const AVPixFmtDescriptor *desc;
63 int num_pix_fmts;
64 int chroma_h;
65 int ret;
66
67 2 *out_size = 0;
68
69 2 enc_ctx = avcodec_alloc_context3(enc);
70
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!enc_ctx)
71 return NULL;
72
73 /* use first supported pixel format, fall back to yuv420p */
74 2 ret = avcodec_get_supported_config(enc_ctx, enc, AV_CODEC_CONFIG_PIX_FORMAT,
75 0, (const void **)&pix_fmts, &num_pix_fmts);
76
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 enc_ctx->pix_fmt = (ret >= 0 && num_pix_fmts > 0) ? pix_fmts[0]
77
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 : AV_PIX_FMT_YUV420P;
78 2 enc_ctx->width = width;
79 2 enc_ctx->height = height;
80 2 enc_ctx->time_base = (AVRational){ 1, 25 };
81
82
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (avcodec_open2(enc_ctx, enc, NULL) < 0)
83 goto fail;
84
85 2 desc = av_pix_fmt_desc_get(enc_ctx->pix_fmt);
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!desc)
87 goto fail;
88 2 chroma_h = AV_CEIL_RSHIFT(height, desc->log2_chroma_h);
89
90 2 frame = av_frame_alloc();
91
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!frame)
92 goto fail;
93
94 2 frame->format = enc_ctx->pix_fmt;
95 2 frame->width = width;
96 2 frame->height = height;
97
98
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (av_frame_get_buffer(frame, 0) < 0)
99 goto fail;
100
101 2 pkt = av_packet_alloc();
102
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!pkt)
103 goto fail;
104
105
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (int i = 0; i < n_frames; i++) {
106 4 frame->pts = i;
107
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if (av_frame_make_writable(frame) < 0)
108 goto fail;
109 /* fill with flat color so encoder produces deterministic output */
110 4 memset(frame->data[0], 128, (size_t)frame->linesize[0] * height);
111
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (frame->data[1])
112 4 memset(frame->data[1], 64, (size_t)frame->linesize[1] * chroma_h);
113
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (frame->data[2])
114 4 memset(frame->data[2], 64, (size_t)frame->linesize[2] * chroma_h);
115
116 4 ret = avcodec_send_frame(enc_ctx, frame);
117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ret < 0)
118 goto fail;
119
120
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 while (ret >= 0) {
121 8 ret = avcodec_receive_packet(enc_ctx, pkt);
122
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 if (ret == AVERROR(EAGAIN))
123 4 break;
124
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ret < 0)
125 goto fail;
126
127 4 tmp = av_realloc(buf, buf_size + pkt->size + AV_INPUT_BUFFER_PADDING_SIZE);
128
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!tmp) {
129 av_packet_unref(pkt);
130 goto fail;
131 }
132 4 buf = tmp;
133 4 memcpy(buf + buf_size, pkt->data, pkt->size);
134 4 buf_size += pkt->size;
135 4 av_packet_unref(pkt);
136 }
137 }
138
139 /* flush encoder */
140 2 ret = avcodec_send_frame(enc_ctx, NULL);
141
1/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (ret < 0 && ret != AVERROR_EOF)
142 goto fail;
143 while (1) {
144 2 ret = avcodec_receive_packet(enc_ctx, pkt);
145
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
146 break;
147 if (ret < 0)
148 goto fail;
149 tmp = av_realloc(buf, buf_size + pkt->size + AV_INPUT_BUFFER_PADDING_SIZE);
150 if (!tmp) {
151 av_packet_unref(pkt);
152 goto fail;
153 }
154 buf = tmp;
155 memcpy(buf + buf_size, pkt->data, pkt->size);
156 buf_size += pkt->size;
157 av_packet_unref(pkt);
158 }
159
160
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!buf)
161 goto fail;
162 2 memset(buf + buf_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
163 2 *out_size = buf_size;
164 2 av_frame_free(&frame);
165 2 av_packet_free(&pkt);
166 2 avcodec_free_context(&enc_ctx);
167 2 return buf;
168
169 fail:
170 av_free(buf);
171 av_frame_free(&frame);
172 av_packet_free(&pkt);
173 avcodec_free_context(&enc_ctx);
174 return NULL;
175 }
176
177 /* Print label, out_size, and first/last 4 bytes of out when non-empty. */
178 10 static void print_parse_result(const char *label,
179 const uint8_t *out, int out_size)
180 {
181 10 printf("%s: out_size=%d", label, out_size);
182
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
10 if (out && out_size > 0) {
183 8 int n = out_size < 4 ? out_size : 4;
184 int k;
185 8 printf(" first=");
186
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 8 times.
40 for (k = 0; k < n; k++)
187
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 8 times.
32 printf(k ? " %02x" : "%02x", out[k]);
188
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (out_size > 4) {
189 8 printf(" last=");
190
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 8 times.
40 for (k = out_size - 4; k < out_size; k++)
191
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 8 times.
32 printf(k > out_size - 4 ? " %02x" : "%02x", out[k]);
192 }
193 }
194 10 printf("\n");
195 10 }
196
197 /*
198 * Single parse call on buf — prints the result with label.
199 * Returns out_size on success, negative AVERROR on error.
200 * No flush; used to verify the parser does not emit output for a given input.
201 */
202 2 static int parse_once(AVCodecContext *avctx, enum AVCodecID codec_id,
203 const char *label,
204 const uint8_t *buf, int buf_size)
205 {
206 2 AVCodecParserContext *parser = av_parser_init(codec_id);
207 uint8_t *out;
208 int out_size, ret;
209
210
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!parser)
211 return AVERROR(ENOSYS);
212 2 ret = av_parser_parse2(parser, avctx, &out, &out_size,
213 buf, buf_size,
214 AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
215
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 print_parse_result(label, out, ret < 0 ? 0 : out_size);
216 2 av_parser_close(parser);
217
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 return ret < 0 ? ret : out_size;
218 }
219
220 /*
221 * Feed buf through a fresh parser in chunks of chunk_size bytes.
222 * chunk_size=0 feeds all data in one call.
223 * Prints each emitted frame as "tag[N]".
224 * Returns frame count (>=0) or negative AVERROR on error.
225 */
226 4 static int parse_stream(AVCodecContext *avctx, enum AVCodecID codec_id,
227 const char *tag,
228 const uint8_t *buf, int buf_size, int chunk_size,
229 uint8_t **all_out, size_t *all_size)
230 {
231 4 AVCodecParserContext *parser = av_parser_init(codec_id);
232 4 const uint8_t *p = buf;
233 4 int remaining = buf_size;
234 4 int n = 0;
235 uint8_t *out;
236 int out_size, consumed;
237
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!parser)
239 return AVERROR(ENOSYS);
240
241
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (chunk_size <= 0)
242
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 chunk_size = buf_size ? buf_size : 1;
243
244
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
14 while (remaining > 0) {
245 10 int feed = remaining < chunk_size ? remaining : chunk_size;
246 10 consumed = av_parser_parse2(parser, avctx, &out, &out_size,
247 p, feed,
248 AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
249
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (consumed < 0) {
250 av_parser_close(parser);
251 return consumed;
252 }
253
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
10 if (out_size > 0) {
254 char label[64];
255 4 snprintf(label, sizeof(label), "%s[%d]", tag, n++);
256 4 print_parse_result(label, out, out_size);
257
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (all_out) {
258 4 uint8_t *tmp = av_realloc(*all_out, *all_size + out_size);
259
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!tmp) {
260 av_parser_close(parser);
261 return AVERROR(ENOMEM);
262 }
263 4 memcpy(tmp + *all_size, out, out_size);
264 4 *all_out = tmp;
265 4 *all_size += out_size;
266 }
267 }
268 /* advance by consumed bytes; if parser consumed nothing, skip the
269 * fed chunk to avoid an infinite loop */
270
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 p += consumed > 0 ? consumed : feed;
271
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 remaining -= consumed > 0 ? consumed : feed;
272 }
273
274 /* flush any frame the parser held waiting for a next-frame start code */
275 4 consumed = av_parser_parse2(parser, avctx, &out, &out_size,
276 NULL, 0,
277 AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
278
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (consumed < 0) {
279 av_parser_close(parser);
280 return consumed;
281 }
282
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (out_size > 0) {
283 char label[64];
284 4 snprintf(label, sizeof(label), "%s[%d]", tag, n++);
285 4 print_parse_result(label, out, out_size);
286
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (all_out) {
287 4 uint8_t *tmp = av_realloc(*all_out, *all_size + out_size);
288
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!tmp) {
289 av_parser_close(parser);
290 return AVERROR(ENOMEM);
291 }
292 4 memcpy(tmp + *all_size, out, out_size);
293 4 *all_out = tmp;
294 4 *all_size += out_size;
295 }
296 }
297
298 4 av_parser_close(parser);
299 4 return n;
300 }
301
302 2 int main(int argc, char **argv)
303 {
304
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const char *codec_name = argc > 1 ? argv[1] : "h261";
305
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 int width = argc > 2 ? atoi(argv[2]) : 176;
306
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 int height = argc > 3 ? atoi(argv[3]) : 144;
307 2 AVCodecContext *avctx = NULL;
308 AVCodecParserContext *parser;
309 2 uint8_t *encoded = NULL;
310 size_t encoded_size;
311 enum AVCodecID codec_id;
312 const AVCodec *enc;
313 2 uint8_t *bulk_data = NULL, *split_data = NULL;
314 2 size_t bulk_sz = 0, split_sz = 0;
315 int n, ret;
316
317 2 av_log_set_level(AV_LOG_ERROR);
318
319 2 enc = avcodec_find_encoder_by_name(codec_name);
320
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!enc) {
321 av_log(NULL, AV_LOG_ERROR, "encoder '%s' not found\n", codec_name);
322 return 1;
323 }
324 2 codec_id = enc->id;
325
326 /* verify parser is available before running tests */
327 2 parser = av_parser_init(codec_id);
328
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!parser) {
329 av_log(NULL, AV_LOG_ERROR, "parser for '%s' not available\n", codec_name);
330 return 1;
331 }
332 2 av_parser_close(parser);
333
334 2 avctx = avcodec_alloc_context3(NULL);
335
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!avctx)
336 return 1;
337 2 avctx->codec_id = codec_id;
338
339 /* encode two real frames to use as parser input */
340 2 encoded = encode_frames(enc, width, height, 2, &encoded_size);
341
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!encoded || encoded_size == 0) {
342 av_log(NULL, AV_LOG_ERROR, "encoder '%s' failed\n", codec_name);
343 avcodec_free_context(&avctx);
344 return 1;
345 }
346
347 /* test 1: single parse call on garbage — no PSC means out_size must be 0 */
348 2 ret = parse_once(avctx, codec_id, "garbage", garbage, (int)sizeof(garbage));
349
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret != 0) {
350 av_log(NULL, AV_LOG_ERROR, "garbage test failed\n");
351 goto fail;
352 }
353
354 /* test 2: two real encoded frames fed all at once — parser must split
355 * them and emit exactly 2 frames */
356 2 n = parse_stream(avctx, codec_id, "bulk", encoded, (int)encoded_size, 0,
357 &bulk_data, &bulk_sz);
358
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (n != 2) {
359 av_log(NULL, AV_LOG_ERROR, "bulk test failed: got %d frames\n", n);
360 goto fail;
361 }
362
363 /* test 3: same two frames split mid-stream — verify the parser handles
364 * partial input and still emits exactly 2 frames, with identical bytes */
365 2 n = parse_stream(avctx, codec_id, "split", encoded, (int)encoded_size,
366 2 (int)encoded_size / 2, &split_data, &split_sz);
367
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (n != 2) {
368 av_log(NULL, AV_LOG_ERROR, "split test failed: got %d frames\n", n);
369 goto fail;
370 }
371
372
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (bulk_sz != split_sz || memcmp(bulk_data, split_data, bulk_sz) != 0) {
373 av_log(NULL, AV_LOG_ERROR, "bulk and split outputs differ\n");
374 goto fail;
375 }
376
377 2 av_free(bulk_data);
378 2 av_free(split_data);
379 2 av_free(encoded);
380 2 avcodec_free_context(&avctx);
381 2 return 0;
382
383 fail:
384 av_free(bulk_data);
385 av_free(split_data);
386 av_free(encoded);
387 avcodec_free_context(&avctx);
388 return 1;
389 }
390