This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 9bf999c24ffbbfcde7f6722f4c1081c629a7a30e Author: Soham Kute <[email protected]> AuthorDate: Mon Mar 30 03:43:52 2026 +0530 Commit: michaelni <[email protected]> CommitDate: Sun Mar 29 23:01:39 2026 +0000 avcodec/tests: add encoder-parser API test for H.261 Add tests/api/api-enc-parser-test.c, a generic encoder+parser round-trip test that takes codec_name, width, and height on the command line (defaults: h261 176 144). Three cases are tested: garbage - a single av_parser_parse2() call on 8 bytes with no Picture Start Code; verifies out_size == 0 so the parser emits no spurious data. bulk - encodes 2 frames, concatenates the raw packets, feeds the whole buffer to a fresh parser in one call, then flushes. Verifies that exactly 2 non-empty frames come out and that the parser found the PSC boundary between them. split - the same buffer fed in two halves (chunk boundary falls inside frame 0). Verifies the parser still emits exactly 2 frames when input arrives incrementally, and that the collected bytes are identical to the bulk output (checked with memcmp). Implementation notes: avcodec_get_supported_config() selects the pixel format; chroma height uses AV_CEIL_RSHIFT with log2_chroma_h from AVPixFmtDescriptor; data[1] and data[2] are checked independently so semi-planar formats work; the encoded buffer is given AV_INPUT_BUFFER_PADDING_SIZE zero bytes at the end; parse_stream() skips the fed chunk if consumed==0 to prevent an infinite loop. Two FATE entries in tests/fate/api.mak: QCIF (176x144) and CIF (352x288), both standard H.261 resolutions. Signed-off-by: Soham Kute <[email protected]> --- tests/api/Makefile | 1 + tests/api/api-enc-parser-test.c | 389 ++++++++++++++++++++++++++++++++++++++ tests/fate/api.mak | 6 + tests/ref/fate/api-enc-parser | 5 + tests/ref/fate/api-enc-parser-cif | 5 + 5 files changed, 406 insertions(+) diff --git a/tests/api/Makefile b/tests/api/Makefile index 899aeb1f54..987db1b785 100644 --- a/tests/api/Makefile +++ b/tests/api/Makefile @@ -4,6 +4,7 @@ APITESTPROGS-$(call DEMDEC, H264, H264) += api-h264-slice APITESTPROGS-yes += api-seek api-dump-stream-meta APITESTPROGS-$(call DEMDEC, H263, H263) += api-band APITESTPROGS-$(HAVE_THREADS) += api-threadmessage +APITESTPROGS-$(call ALLYES, H261_ENCODER H261_PARSER) += api-enc-parser APITESTPROGS += $(APITESTPROGS-yes) APITESTOBJS := $(APITESTOBJS:%=$(APITESTSDIR)%) $(APITESTPROGS:%=$(APITESTSDIR)/%-test.o) diff --git a/tests/api/api-enc-parser-test.c b/tests/api/api-enc-parser-test.c new file mode 100644 index 0000000000..0c37bcf52d --- /dev/null +++ b/tests/api/api-enc-parser-test.c @@ -0,0 +1,389 @@ +/* + * 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; +} diff --git a/tests/fate/api.mak b/tests/fate/api.mak index b760de5eb6..ba9789d9f7 100644 --- a/tests/fate/api.mak +++ b/tests/fate/api.mak @@ -3,6 +3,12 @@ fate-api-flac: $(APITESTSDIR)/api-flac-test$(EXESUF) fate-api-flac: CMD = run $(APITESTSDIR)/api-flac-test$(EXESUF) fate-api-flac: CMP = null +FATE_API_LIBAVCODEC-$(call ALLYES, H261_ENCODER H261_PARSER) += fate-api-enc-parser fate-api-enc-parser-cif +fate-api-enc-parser: $(APITESTSDIR)/api-enc-parser-test$(EXESUF) +fate-api-enc-parser: CMD = run $(APITESTSDIR)/api-enc-parser-test$(EXESUF) h261 176 144 +fate-api-enc-parser-cif: $(APITESTSDIR)/api-enc-parser-test$(EXESUF) +fate-api-enc-parser-cif: CMD = run $(APITESTSDIR)/api-enc-parser-test$(EXESUF) h261 352 288 + FATE_API_SAMPLES_LIBAVFORMAT-$(call DEMDEC, FLV, FLV) += fate-api-band fate-api-band: $(APITESTSDIR)/api-band-test$(EXESUF) fate-api-band: CMD = run $(APITESTSDIR)/api-band-test$(EXESUF) $(TARGET_SAMPLES)/mpeg4/resize_down-up.h263 diff --git a/tests/ref/fate/api-enc-parser b/tests/ref/fate/api-enc-parser new file mode 100644 index 0000000000..12799bb064 --- /dev/null +++ b/tests/ref/fate/api-enc-parser @@ -0,0 +1,5 @@ +garbage: out_size=0 +bulk[0]: out_size=819 first=00 01 00 16 last=f2 04 81 00 +bulk[1]: out_size=14 first=00 01 00 86 last=40 00 15 10 +split[0]: out_size=819 first=00 01 00 16 last=f2 04 81 00 +split[1]: out_size=14 first=00 01 00 86 last=40 00 15 10 diff --git a/tests/ref/fate/api-enc-parser-cif b/tests/ref/fate/api-enc-parser-cif new file mode 100644 index 0000000000..8826dcf6aa --- /dev/null +++ b/tests/ref/fate/api-enc-parser-cif @@ -0,0 +1,5 @@ +garbage: out_size=0 +bulk[0]: out_size=3261 first=00 01 00 1e last=fe 40 90 20 +bulk[1]: out_size=43 first=00 01 00 8e last=10 00 07 04 +split[0]: out_size=3261 first=00 01 00 1e last=fe 40 90 20 +split[1]: out_size=43 first=00 01 00 8e last=10 00 07 04 _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
