PR #25226 opened by rajeshgk
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25226
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25226.patch

Add six RTP packet-size boundary cases covering MPEG video, MP2, and PCM S16BE.

Verify header-time rejection for MPEG payloads and packet-time rejection for 
PCM samples. Check that accepted media packets respect the configured 
packet-size limit.

Provide a header-only mode for safe baseline comparison with older RTP muxers.

Depends on the RTP packet-size security fix in FFmpeg PR #25222. The full PCM 
regression requires that fix to avoid a legacy nonterminating packetization 
loop.

# Summary of changes

Briefly describe what this PR does and why.

<!--
If this PR requires new FATE test samples, attach them to the PR and
list their target paths below (relative to the fate-suite root).

Attached filenames must match the sample's filename:

```fate-samples
# e.g. vorbis/new-sample.ogg
```
-->



>From c4f1113a3e68d8515d76460f9beccff7ef95b66c Mon Sep 17 00:00:00 2001
From: Rajesh Gopinath Kumar <[email protected]>
Date: Mon, 19 Oct 2026 15:52:54 -0500
Subject: [PATCH] fate: add RTP packet-size boundary regression tests

Add six RTP packet-size boundary cases covering MPEG video, MP2, and PCM S16BE.

Verify header-time rejection for MPEG payloads and packet-time rejection for 
PCM samples. Check that accepted media packets respect the configured 
packet-size limit.

Provide a header-only mode for safe baseline comparison with older RTP muxers.

Depends on the RTP packet-size security fix in FFmpeg PR #25222. The full PCM 
regression requires that fix to avoid a legacy nonterminating packetization 
loop.
---
 libavformat/Makefile                  |   1 +
 libavformat/tests/rtpenc_packetsize.c | 209 ++++++++++++++++++++++++++
 tests/fate/libavformat.mak            |   4 +
 tests/ref/fate/rtpenc-packetsize      |   7 +
 4 files changed, 221 insertions(+)
 create mode 100644 libavformat/tests/rtpenc_packetsize.c
 create mode 100644 tests/ref/fate/rtpenc-packetsize

diff --git a/libavformat/Makefile b/libavformat/Makefile
index 7c17892c22f..8c357d5d885 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -792,6 +792,7 @@ TESTPROGS = id3v2                                           
            \
             url                                                         \
             seek_utils
 TESTPROGS-$(CONFIG_RTPDEC)               += rtpdec
+TESTPROGS-$(CONFIG_RTP_MUXER)             += rtpenc_packetsize
 #           async                                                       \
 
 FIFO-MUXER-TESTPROGS-$(CONFIG_NETWORK)   += fifo_muxer
diff --git a/libavformat/tests/rtpenc_packetsize.c 
b/libavformat/tests/rtpenc_packetsize.c
new file mode 100644
index 00000000000..f7e122c7b7e
--- /dev/null
+++ b/libavformat/tests/rtpenc_packetsize.c
@@ -0,0 +1,209 @@
+/*
+ * RTP packet-size boundary regression tests.
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libavformat/avformat.h"
+#include "libavutil/error.h"
+#include "libavutil/mem.h"
+
+typedef struct Output {
+    int max_packet_size;
+    int packets;
+    int rtcp_packets;
+    int oversized;
+} Output;
+
+static int write_packet(void *opaque, const uint8_t *buf, int size)
+{
+    Output *out = opaque;
+
+    /*
+     * RTCP sender reports are independent control packets.
+     * The RTP media packet-size limit does not apply to them.
+     */
+    if (size >= 8 &&
+        (buf[0] >> 6) == 2 &&
+        buf[1] == 200 &&
+        ((buf[2] << 8) | buf[3]) == 6) {
+        out->rtcp_packets++;
+        return size;
+    }
+
+    if (size > out->max_packet_size)
+        out->oversized++;
+
+    out->packets++;
+    return size;
+}
+
+static int run_case(enum AVCodecID codec_id, int packet_size,
+                    int should_accept)
+{
+    AVFormatContext *fmt = NULL;
+    AVIOContext *avio = NULL;
+    AVStream *st;
+    Output output = { 0 };
+    uint8_t *buffer = NULL;
+    AVPacket *pkt = NULL;
+    int ret;
+    int result = 1;
+    int header_written = 0;
+    int header_ret;
+    int packet_written = 0;
+
+    output.max_packet_size = packet_size;
+
+    ret = avformat_alloc_output_context2(&fmt, NULL, "rtp", NULL);
+    if (ret < 0 || !fmt)
+        goto end;
+
+    st = avformat_new_stream(fmt, NULL);
+    if (!st)
+        goto end;
+
+    st->codecpar->codec_id = codec_id;
+
+    if (codec_id == AV_CODEC_ID_PCM_S16BE ||
+        codec_id == AV_CODEC_ID_MP2) {
+        st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
+        st->codecpar->sample_rate = 44100;
+        st->codecpar->ch_layout.nb_channels = 1;
+    } else {
+        st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+        st->codecpar->width = 64;
+        st->codecpar->height = 64;
+    }
+
+    buffer = av_malloc(4096);
+    if (!buffer)
+        goto end;
+
+    avio = avio_alloc_context(buffer, 4096, 1, &output,
+                              NULL, write_packet, NULL);
+    if (!avio)
+        goto end;
+    buffer = NULL;
+
+    avio->max_packet_size = packet_size;
+    fmt->pb = avio;
+    fmt->flags |= AVFMT_FLAG_CUSTOM_IO;
+    fmt->packet_size = packet_size;
+
+    ret = avformat_write_header(fmt, NULL);
+    header_ret = ret;
+    if (ret >= 0)
+        header_written = 1;
+
+    /*
+     * MPEG video and audio must reject undersized RTP packets at header
+     * initialization. Avoid writing those invalid packets because older
+     * implementations can enter nonterminating packetization loops.
+     *
+     * PCM sample alignment is checked during packet writing, so PCM
+     * rejection cases must still exercise av_write_frame().
+     */
+    if (ret >= 0 &&
+        (should_accept || codec_id == AV_CODEC_ID_PCM_S16BE)) {
+        pkt = av_packet_alloc();
+        if (!pkt)
+            goto end;
+
+        ret = av_new_packet(pkt, 128);
+        if (ret < 0)
+            goto end;
+
+        memset(pkt->data, 0x55, pkt->size);
+        pkt->stream_index = 0;
+        pkt->pts = 0;
+        pkt->dts = 0;
+        pkt->duration = 1;
+
+        packet_written = 1;
+        ret = av_write_frame(fmt, pkt);
+    }
+
+    if (avio)
+        avio_flush(avio);
+
+    if (should_accept) {
+        result = ret < 0 ||
+                 !packet_written ||
+                 output.oversized != 0 ||
+                 output.packets == 0;
+    } else if (codec_id == AV_CODEC_ID_PCM_S16BE) {
+        result = header_ret < 0 ||
+                 !packet_written ||
+                 ret >= 0 ||
+                 output.oversized != 0;
+    } else {
+        result = header_ret >= 0 ||
+                 packet_written ||
+                 output.oversized != 0;
+    }
+
+    printf("%-12s size=%-3d expected=%s result=%s\n",
+           avcodec_get_name(codec_id),
+           packet_size,
+           should_accept ? "accept" : "reject",
+           result ? "FAIL" : "PASS");
+
+end:
+    if (header_written && ret >= 0)
+        av_write_trailer(fmt);
+
+    av_packet_free(&pkt);
+
+    if (fmt)
+        fmt->pb = NULL;
+
+    if (avio)
+        avio_context_free(&avio);
+
+    av_free(buffer);
+    avformat_free_context(fmt);
+
+    return result;
+}
+
+int main(int argc, char **argv)
+{
+    int failures = 0;
+    int header_only = argc == 2 &&
+                      !strcmp(argv[1], "--header-only");
+
+    if (argc > 1 && !header_only) {
+        fprintf(stderr, "Usage: %s [--header-only]\n", argv[0]);
+        return 2;
+    }
+
+    failures += run_case(AV_CODEC_ID_MPEG1VIDEO, 16, 0);
+    failures += run_case(AV_CODEC_ID_MPEG1VIDEO, 17, 1);
+    failures += run_case(AV_CODEC_ID_MP2, 16, 0);
+    failures += run_case(AV_CODEC_ID_MP2, 17, 1);
+
+    /*
+     * PCM rejection occurs during packet writing. Older RTP muxers
+     * can enter a nonterminating loop when the payload cannot fit
+     * a complete sample. Skip PCM in header-only baseline mode.
+     */
+    if (!header_only) {
+        failures += run_case(AV_CODEC_ID_PCM_S16BE, 13, 0);
+        failures += run_case(AV_CODEC_ID_PCM_S16BE, 14, 1);
+    }
+
+    printf("RTP packet-size regression: %s\n",
+           failures ? "FAIL" : "PASS");
+
+    return failures ? 1 : 0;
+}
diff --git a/tests/fate/libavformat.mak b/tests/fate/libavformat.mak
index 45878891fd9..0fa8332187d 100644
--- a/tests/fate/libavformat.mak
+++ b/tests/fate/libavformat.mak
@@ -52,6 +52,10 @@ FATE_LIBAVFORMAT-$(CONFIG_FFRTMPCRYPT_PROTOCOL) += 
fate-rtmpdh
 fate-rtmpdh: libavformat/tests/rtmpdh$(EXESUF)
 fate-rtmpdh: CMD = run libavformat/tests/rtmpdh$(EXESUF)
 
+FATE_LIBAVFORMAT-$(CONFIG_RTP_MUXER) += fate-rtpenc-packetsize
+fate-rtpenc-packetsize: libavformat/tests/rtpenc_packetsize$(EXESUF)
+fate-rtpenc-packetsize: CMD = run libavformat/tests/rtpenc_packetsize$(EXESUF)
+
 FATE_LIBAVFORMAT-$(CONFIG_RTPDEC) += fate-rtpdec
 fate-rtpdec: libavformat/tests/rtpdec$(EXESUF)
 fate-rtpdec: CMD = run libavformat/tests/rtpdec$(EXESUF)
diff --git a/tests/ref/fate/rtpenc-packetsize b/tests/ref/fate/rtpenc-packetsize
new file mode 100644
index 00000000000..d6a9160b24c
--- /dev/null
+++ b/tests/ref/fate/rtpenc-packetsize
@@ -0,0 +1,7 @@
+mpeg1video   size=16  expected=reject result=PASS
+mpeg1video   size=17  expected=accept result=PASS
+mp2          size=16  expected=reject result=PASS
+mp2          size=17  expected=accept result=PASS
+pcm_s16be    size=13  expected=reject result=PASS
+pcm_s16be    size=14  expected=accept result=PASS
+RTP packet-size regression: PASS
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to