| /* |
| * Copyright (c) 2026 Soham Kute |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| |
| /* |
| * Encoder + parser API test. |
| * Usage: api-enc-parser-test [codec_name [width height]] |
| * Defaults: h261, 176, 144 |
| * |
| * Encodes two frames with the named encoder, concatenates the packets, |
| * and feeds the result to the matching parser to verify frame boundary |
| * detection. For each non-empty output the size and up to four bytes |
| * at the start and end are printed for comparison against a reference file. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "libavcodec/avcodec.h" |
| #include "libavutil/log.h" |
| #include "libavutil/mem.h" |
| #include "libavutil/pixdesc.h" |
| |
| /* Garbage with no PSC - parser must return out_size == 0 */ |
| static const uint8_t garbage[] = { |
| 0xff, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, |
| }; |
| |
| /* |
| * Encode n_frames of video at width x height using enc. |
| * Returns concatenated raw bitstream; caller must av_free() it. |
| * Returns NULL on error. |
| */ |
| static uint8_t *encode_frames(const AVCodec *enc, int width, int height, |
| int n_frames, size_t *out_size) |
| { |
| AVCodecContext *enc_ctx = NULL; |
| AVFrame *frame = NULL; |
| AVPacket *pkt = NULL; |
| uint8_t *buf = NULL, *tmp; |
| size_t buf_size = 0; |
| const enum AVPixelFormat *pix_fmts; |
| const AVPixFmtDescriptor *desc; |
| int num_pix_fmts; |
| int chroma_h; |
| int ret; |
| |
| *out_size = 0; |
| |
| enc_ctx = avcodec_alloc_context3(enc); |
| if (!enc_ctx) |
| return NULL; |
| |
| /* use first supported pixel format, fall back to yuv420p */ |
| ret = avcodec_get_supported_config(enc_ctx, enc, AV_CODEC_CONFIG_PIX_FORMAT, |
| 0, (const void **)&pix_fmts, &num_pix_fmts); |
| enc_ctx->pix_fmt = (ret >= 0 && num_pix_fmts > 0) ? pix_fmts[0] |
| : AV_PIX_FMT_YUV420P; |
| enc_ctx->width = width; |
| enc_ctx->height = height; |
| enc_ctx->time_base = (AVRational){ 1, 25 }; |
| |
| if (avcodec_open2(enc_ctx, enc, NULL) < 0) |
| goto fail; |
| |
| desc = av_pix_fmt_desc_get(enc_ctx->pix_fmt); |
| if (!desc) |
| goto fail; |
| chroma_h = AV_CEIL_RSHIFT(height, desc->log2_chroma_h); |
| |
| frame = av_frame_alloc(); |
| if (!frame) |
| goto fail; |
| |
| frame->format = enc_ctx->pix_fmt; |
| frame->width = width; |
| frame->height = height; |
| |
| if (av_frame_get_buffer(frame, 0) < 0) |
| goto fail; |
| |
| pkt = av_packet_alloc(); |
| if (!pkt) |
| goto fail; |
| |
| for (int i = 0; i < n_frames; i++) { |
| frame->pts = i; |
| if (av_frame_make_writable(frame) < 0) |
| goto fail; |
| /* fill with flat color so encoder produces deterministic output */ |
| memset(frame->data[0], 128, (size_t)frame->linesize[0] * height); |
| if (frame->data[1]) |
| memset(frame->data[1], 64, (size_t)frame->linesize[1] * chroma_h); |
| if (frame->data[2]) |
| memset(frame->data[2], 64, (size_t)frame->linesize[2] * chroma_h); |
| |
| ret = avcodec_send_frame(enc_ctx, frame); |
| if (ret < 0) |
| goto fail; |
| |
| while (ret >= 0) { |
| ret = avcodec_receive_packet(enc_ctx, pkt); |
| if (ret == AVERROR(EAGAIN)) |
| break; |
| if (ret < 0) |
| goto fail; |
| |
| tmp = av_realloc(buf, buf_size + pkt->size + AV_INPUT_BUFFER_PADDING_SIZE); |
| if (!tmp) { |
| av_packet_unref(pkt); |
| goto fail; |
| } |
| buf = tmp; |
| memcpy(buf + buf_size, pkt->data, pkt->size); |
| buf_size += pkt->size; |
| av_packet_unref(pkt); |
| } |
| } |
| |
| /* flush encoder */ |
| ret = avcodec_send_frame(enc_ctx, NULL); |
| if (ret < 0 && ret != AVERROR_EOF) |
| goto fail; |
| while (1) { |
| ret = avcodec_receive_packet(enc_ctx, pkt); |
| if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) |
| break; |
| if (ret < 0) |
| goto fail; |
| tmp = av_realloc(buf, buf_size + pkt->size + AV_INPUT_BUFFER_PADDING_SIZE); |
| if (!tmp) { |
| av_packet_unref(pkt); |
| goto fail; |
| } |
| buf = tmp; |
| memcpy(buf + buf_size, pkt->data, pkt->size); |
| buf_size += pkt->size; |
| av_packet_unref(pkt); |
| } |
| |
| if (!buf) |
| goto fail; |
| memset(buf + buf_size, 0, AV_INPUT_BUFFER_PADDING_SIZE); |
| *out_size = buf_size; |
| av_frame_free(&frame); |
| av_packet_free(&pkt); |
| avcodec_free_context(&enc_ctx); |
| return buf; |
| |
| fail: |
| av_free(buf); |
| av_frame_free(&frame); |
| av_packet_free(&pkt); |
| avcodec_free_context(&enc_ctx); |
| return NULL; |
| } |
| |
| /* Print label, out_size, and first/last 4 bytes of out when non-empty. */ |
| static void print_parse_result(const char *label, |
| const uint8_t *out, int out_size) |
| { |
| printf("%s: out_size=%d", label, out_size); |
| if (out && out_size > 0) { |
| int n = out_size < 4 ? out_size : 4; |
| int k; |
| printf(" first="); |
| for (k = 0; k < n; k++) |
| printf(k ? " %02x" : "%02x", out[k]); |
| if (out_size > 4) { |
| printf(" last="); |
| for (k = out_size - 4; k < out_size; k++) |
| printf(k > out_size - 4 ? " %02x" : "%02x", out[k]); |
| } |
| } |
| printf("\n"); |
| } |
| |
| /* |
| * Single parse call on buf — prints the result with label. |
| * Returns out_size on success, negative AVERROR on error. |
| * No flush; used to verify the parser does not emit output for a given input. |
| */ |
| static int parse_once(AVCodecContext *avctx, enum AVCodecID codec_id, |
| const char *label, |
| const uint8_t *buf, int buf_size) |
| { |
| AVCodecParserContext *parser = av_parser_init(codec_id); |
| uint8_t *out; |
| int out_size, ret; |
| |
| if (!parser) |
| return AVERROR(ENOSYS); |
| ret = av_parser_parse2(parser, avctx, &out, &out_size, |
| buf, buf_size, |
| AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1); |
| print_parse_result(label, out, ret < 0 ? 0 : out_size); |
| av_parser_close(parser); |
| return ret < 0 ? ret : out_size; |
| } |
| |
| /* |
| * Feed buf through a fresh parser in chunks of chunk_size bytes. |
| * chunk_size=0 feeds all data in one call. |
| * Prints each emitted frame as "tag[N]". |
| * Returns frame count (>=0) or negative AVERROR on error. |
| */ |
| static int parse_stream(AVCodecContext *avctx, enum AVCodecID codec_id, |
| const char *tag, |
| const uint8_t *buf, int buf_size, int chunk_size, |
| uint8_t **all_out, size_t *all_size) |
| { |
| AVCodecParserContext *parser = av_parser_init(codec_id); |
| const uint8_t *p = buf; |
| int remaining = buf_size; |
| int n = 0; |
| uint8_t *out; |
| int out_size, consumed; |
| |
| if (!parser) |
| return AVERROR(ENOSYS); |
| |
| if (chunk_size <= 0) |
| chunk_size = buf_size ? buf_size : 1; |
| |
| while (remaining > 0) { |
| int feed = remaining < chunk_size ? remaining : chunk_size; |
| consumed = av_parser_parse2(parser, avctx, &out, &out_size, |
| p, feed, |
| AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1); |
| if (consumed < 0) { |
| av_parser_close(parser); |
| return consumed; |
| } |
| if (out_size > 0) { |
| char label[64]; |
| snprintf(label, sizeof(label), "%s[%d]", tag, n++); |
| print_parse_result(label, out, out_size); |
| if (all_out) { |
| uint8_t *tmp = av_realloc(*all_out, *all_size + out_size); |
| if (!tmp) { |
| av_parser_close(parser); |
| return AVERROR(ENOMEM); |
| } |
| memcpy(tmp + *all_size, out, out_size); |
| *all_out = tmp; |
| *all_size += out_size; |
| } |
| } |
| /* advance by consumed bytes; if parser consumed nothing, skip the |
| * fed chunk to avoid an infinite loop */ |
| p += consumed > 0 ? consumed : feed; |
| remaining -= consumed > 0 ? consumed : feed; |
| } |
| |
| /* flush any frame the parser held waiting for a next-frame start code */ |
| consumed = av_parser_parse2(parser, avctx, &out, &out_size, |
| NULL, 0, |
| AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1); |
| if (consumed < 0) { |
| av_parser_close(parser); |
| return consumed; |
| } |
| if (out_size > 0) { |
| char label[64]; |
| snprintf(label, sizeof(label), "%s[%d]", tag, n++); |
| print_parse_result(label, out, out_size); |
| if (all_out) { |
| uint8_t *tmp = av_realloc(*all_out, *all_size + out_size); |
| if (!tmp) { |
| av_parser_close(parser); |
| return AVERROR(ENOMEM); |
| } |
| memcpy(tmp + *all_size, out, out_size); |
| *all_out = tmp; |
| *all_size += out_size; |
| } |
| } |
| |
| av_parser_close(parser); |
| return n; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| const char *codec_name = argc > 1 ? argv[1] : "h261"; |
| int width = argc > 2 ? atoi(argv[2]) : 176; |
| int height = argc > 3 ? atoi(argv[3]) : 144; |
| AVCodecContext *avctx = NULL; |
| AVCodecParserContext *parser; |
| uint8_t *encoded = NULL; |
| size_t encoded_size; |
| enum AVCodecID codec_id; |
| const AVCodec *enc; |
| uint8_t *bulk_data = NULL, *split_data = NULL; |
| size_t bulk_sz = 0, split_sz = 0; |
| int n, ret; |
| |
| av_log_set_level(AV_LOG_ERROR); |
| |
| enc = avcodec_find_encoder_by_name(codec_name); |
| if (!enc) { |
| av_log(NULL, AV_LOG_ERROR, "encoder '%s' not found\n", codec_name); |
| return 1; |
| } |
| codec_id = enc->id; |
| |
| /* verify parser is available before running tests */ |
| parser = av_parser_init(codec_id); |
| if (!parser) { |
| av_log(NULL, AV_LOG_ERROR, "parser for '%s' not available\n", codec_name); |
| return 1; |
| } |
| av_parser_close(parser); |
| |
| avctx = avcodec_alloc_context3(NULL); |
| if (!avctx) |
| return 1; |
| avctx->codec_id = codec_id; |
| |
| /* encode two real frames to use as parser input */ |
| encoded = encode_frames(enc, width, height, 2, &encoded_size); |
| if (!encoded || encoded_size == 0) { |
| av_log(NULL, AV_LOG_ERROR, "encoder '%s' failed\n", codec_name); |
| avcodec_free_context(&avctx); |
| return 1; |
| } |
| |
| /* test 1: single parse call on garbage — no PSC means out_size must be 0 */ |
| ret = parse_once(avctx, codec_id, "garbage", garbage, (int)sizeof(garbage)); |
| if (ret != 0) { |
| av_log(NULL, AV_LOG_ERROR, "garbage test failed\n"); |
| goto fail; |
| } |
| |
| /* test 2: two real encoded frames fed all at once — parser must split |
| * them and emit exactly 2 frames */ |
| n = parse_stream(avctx, codec_id, "bulk", encoded, (int)encoded_size, 0, |
| &bulk_data, &bulk_sz); |
| if (n != 2) { |
| av_log(NULL, AV_LOG_ERROR, "bulk test failed: got %d frames\n", n); |
| goto fail; |
| } |
| |
| /* test 3: same two frames split mid-stream — verify the parser handles |
| * partial input and still emits exactly 2 frames, with identical bytes */ |
| n = parse_stream(avctx, codec_id, "split", encoded, (int)encoded_size, |
| (int)encoded_size / 2, &split_data, &split_sz); |
| if (n != 2) { |
| av_log(NULL, AV_LOG_ERROR, "split test failed: got %d frames\n", n); |
| goto fail; |
| } |
| |
| if (bulk_sz != split_sz || memcmp(bulk_data, split_data, bulk_sz) != 0) { |
| av_log(NULL, AV_LOG_ERROR, "bulk and split outputs differ\n"); |
| goto fail; |
| } |
| |
| av_free(bulk_data); |
| av_free(split_data); |
| av_free(encoded); |
| avcodec_free_context(&avctx); |
| return 0; |
| |
| fail: |
| av_free(bulk_data); |
| av_free(split_data); |
| av_free(encoded); |
| avcodec_free_context(&avctx); |
| return 1; |
| } |