PR #23839 opened by Romain Beauxis (toots)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23839
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23839.patch

This PR adds a new `trim` bitstream filter.

The specific goal that triggered the neeed for the filter is the ability to dro 
the first priming frame of a `ADTS` output when needed. With more generic 
options, I'm sure it could be useful in other contexts.

Its functionalities cover some of the `noise` filter's functionalities but the 
purpose is totally different and `noise` would be quite a confusing filter to 
use for that.


>From 3c66c0c7ced731663586e1d02a5af5007ae53373 Mon Sep 17 00:00:00 2001
From: Romain Beauxis <[email protected]>
Date: Fri, 17 Jul 2026 11:24:47 -0500
Subject: [PATCH] bsf: add trim filter.

---
 doc/bitstream_filters.texi         |  44 ++++++++++++
 libavcodec/bitstream_filters.c     |   1 +
 libavcodec/bsf/Makefile            |   1 +
 libavcodec/bsf/trim.c              | 104 +++++++++++++++++++++++++++++
 tests/fate/ffmpeg.mak              |   7 ++
 tests/ref/fate/ffmpeg-trim-bsf-all |  39 +++++++++++
 tests/ref/fate/ffmpeg-trim-bsf-dts |   9 +++
 tests/ref/fate/ffmpeg-trim-bsf-pkt |  15 +++++
 tests/ref/fate/ffmpeg-trim-bsf-pts |  14 ++++
 9 files changed, 234 insertions(+)
 create mode 100644 libavcodec/bsf/trim.c
 create mode 100644 tests/ref/fate/ffmpeg-trim-bsf-all
 create mode 100644 tests/ref/fate/ffmpeg-trim-bsf-dts
 create mode 100644 tests/ref/fate/ffmpeg-trim-bsf-pkt
 create mode 100644 tests/ref/fate/ffmpeg-trim-bsf-pts

diff --git a/doc/bitstream_filters.texi b/doc/bitstream_filters.texi
index c6705bbd39..f2c936b5cf 100644
--- a/doc/bitstream_filters.texi
+++ b/doc/bitstream_filters.texi
@@ -1045,6 +1045,50 @@ This can be useful for debugging low-level stream issues.
 Supports AV1, H.264, H.265, (M)JPEG, MPEG-2 and VP9, but depending
 on the build only a subset of these may be available.
 
+@section trim
+
+Drop packets outside a given range, similar to the trim and atrim
+filters, but operating on packets instead of decoded frames.
+
+Packets are dropped until the start of the range is reached, then passed
+through until the end of the range, after which end of stream is signaled.
+
+Note that packets are passed or dropped regardless of the underlying codec
+logic, so make sure you understand the consequences for your stream: cutting
+inside a GOP will produce undecodable video packets, while formats made of
+independent frames such as ADTS can be cut anywhere. Timestamps are not
+shifted; use the @option{setts} filter for that.
+
+It accepts the following parameters:
+@table @option
+@item start_pts
+Presentation timestamp of the first packet that should be passed, in stream
+time base. Packets with a lower pts are dropped.
+
+@item end_pts
+Presentation timestamp of the first packet that should be dropped again, in
+stream time base. Signals end of stream when reached.
+
+@item start_dts
+Same as @option{start_pts}, matched against packet dts.
+
+@item end_dts
+Same as @option{end_pts}, matched against packet dts.
+
+@item start_pkt
+Index of the first packet that should be passed, counting from zero.
+Packets with a lower index are dropped.
+
+@item end_pkt
+Index of the first packet that should be dropped again, counting from zero.
+Signals end of stream when reached.
+@end table
+
+For example, to keep only the 10 first packets of an ADTS stream:
+@example
+ffmpeg -i INPUT.aac -c copy -bsf:a trim=end_pkt=10 OUTPUT.aac
+@end example
+
 @section truehd_core
 
 Extract the core from a TrueHD stream, dropping ATMOS data.
diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c
index a9f3218f0f..f57c2632a0 100644
--- a/libavcodec/bitstream_filters.c
+++ b/libavcodec/bitstream_filters.c
@@ -69,6 +69,7 @@ extern const FFBitStreamFilter ff_showinfo_bsf;
 extern const FFBitStreamFilter ff_smpte436m_to_eia608_bsf;
 extern const FFBitStreamFilter ff_text2movsub_bsf;
 extern const FFBitStreamFilter ff_trace_headers_bsf;
+extern const FFBitStreamFilter ff_trim_bsf;
 extern const FFBitStreamFilter ff_truehd_core_bsf;
 extern const FFBitStreamFilter ff_vp9_metadata_bsf;
 extern const FFBitStreamFilter ff_vp9_raw_reorder_bsf;
diff --git a/libavcodec/bsf/Makefile b/libavcodec/bsf/Makefile
index abbd4be49e..ec224e8774 100644
--- a/libavcodec/bsf/Makefile
+++ b/libavcodec/bsf/Makefile
@@ -50,6 +50,7 @@ OBJS-$(CONFIG_SHOWINFO_BSF)               += bsf/showinfo.o
 OBJS-$(CONFIG_SMPTE436M_TO_EIA608_BSF)    += bsf/smpte436m_to_eia608.o
 OBJS-$(CONFIG_TEXT2MOVSUB_BSF)            += bsf/movsub.o
 OBJS-$(CONFIG_TRACE_HEADERS_BSF)          += bsf/trace_headers.o
+OBJS-$(CONFIG_TRIM_BSF)                   += bsf/trim.o
 OBJS-$(CONFIG_TRUEHD_CORE_BSF)            += bsf/truehd_core.o
 OBJS-$(CONFIG_VP9_METADATA_BSF)           += bsf/vp9_metadata.o
 OBJS-$(CONFIG_VP9_RAW_REORDER_BSF)        += bsf/vp9_raw_reorder.o
diff --git a/libavcodec/bsf/trim.c b/libavcodec/bsf/trim.c
new file mode 100644
index 0000000000..bb260b57b8
--- /dev/null
+++ b/libavcodec/bsf/trim.c
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/opt.h"
+
+#include "libavcodec/bsf.h"
+#include "libavcodec/bsf_internal.h"
+
+typedef struct TrimContext {
+    const AVClass *class;
+
+    int64_t start_pts;
+    int64_t end_pts;
+    int64_t start_dts;
+    int64_t end_dts;
+    int64_t start_pkt;
+    int64_t end_pkt;
+
+    int64_t pkt_idx;
+} TrimContext;
+
+static int trim_filter(AVBSFContext *ctx, AVPacket *pkt)
+{
+    TrimContext *s = ctx->priv_data;
+    int64_t idx;
+    int ret;
+
+    ret = ff_bsf_get_packet_ref(ctx, pkt);
+    if (ret < 0)
+        return ret;
+
+    idx = s->pkt_idx++;
+
+    if (idx >= s->end_pkt) {
+        av_packet_unref(pkt);
+        return AVERROR_EOF;
+    }
+
+    if (idx < s->start_pkt) {
+        av_packet_unref(pkt);
+        return AVERROR(EAGAIN);
+    }
+
+    if ((pkt->pts != AV_NOPTS_VALUE && pkt->pts >= s->end_pts) ||
+        (pkt->dts != AV_NOPTS_VALUE && pkt->dts >= s->end_dts)) {
+        av_packet_unref(pkt);
+        return AVERROR_EOF;
+    }
+
+    if ((pkt->pts != AV_NOPTS_VALUE && pkt->pts < s->start_pts) ||
+        (pkt->dts != AV_NOPTS_VALUE && pkt->dts < s->start_dts)) {
+        av_packet_unref(pkt);
+        return AVERROR(EAGAIN);
+    }
+
+    return 0;
+}
+
+#define OFFSET(x) offsetof(TrimContext, x)
+#define FLAGS 
(AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_BSF_PARAM)
+static const AVOption trim_options[] = {
+    { "start_pts", "pts of the first packet that should be passed, in stream 
time base", OFFSET(start_pts),
+        AV_OPT_TYPE_INT64, { .i64 = INT64_MIN }, INT64_MIN, INT64_MAX, FLAGS },
+    { "end_pts", "pts of the first packet that should be dropped again, in 
stream time base", OFFSET(end_pts),
+        AV_OPT_TYPE_INT64, { .i64 = INT64_MAX }, INT64_MIN, INT64_MAX, FLAGS },
+    { "start_dts", "dts of the first packet that should be passed, in stream 
time base", OFFSET(start_dts),
+        AV_OPT_TYPE_INT64, { .i64 = INT64_MIN }, INT64_MIN, INT64_MAX, FLAGS },
+    { "end_dts", "dts of the first packet that should be dropped again, in 
stream time base", OFFSET(end_dts),
+        AV_OPT_TYPE_INT64, { .i64 = INT64_MAX }, INT64_MIN, INT64_MAX, FLAGS },
+    { "start_pkt", "index of the first packet that should be passed", 
OFFSET(start_pkt),
+        AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, FLAGS },
+    { "end_pkt", "index of the first packet that should be dropped again", 
OFFSET(end_pkt),
+        AV_OPT_TYPE_INT64, { .i64 = INT64_MAX }, 0, INT64_MAX, FLAGS },
+    { NULL },
+};
+
+static const AVClass trim_class = {
+    .class_name = "trim",
+    .item_name  = av_default_item_name,
+    .option     = trim_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const FFBitStreamFilter ff_trim_bsf = {
+    .p.name         = "trim",
+    .p.priv_class   = &trim_class,
+    .priv_data_size = sizeof(TrimContext),
+    .filter         = trim_filter,
+};
diff --git a/tests/fate/ffmpeg.mak b/tests/fate/ffmpeg.mak
index db6a171898..3c8ab0bdc4 100644
--- a/tests/fate/ffmpeg.mak
+++ b/tests/fate/ffmpeg.mak
@@ -229,6 +229,13 @@ fate-ffmpeg-bsf-remove-e: CMD = transcode "mpeg" 
$(TARGET_SAMPLES)/mpeg2/matrixb
 FATE_SAMPLES_FFMPEG-$(call DEMMUX, APNG, FRAMECRC, SETTS_BSF PIPE_PROTOCOL) += 
fate-ffmpeg-setts-bsf
 fate-ffmpeg-setts-bsf: CMD = framecrc -i $(TARGET_SAMPLES)/apng/clock.png -c:v 
copy -bsf:v 
"setts=duration=if(eq(NEXT_PTS\,NOPTS)\,PREV_OUTDURATION\,(NEXT_PTS-PTS)/2):ts=PTS/2"
 -fflags +bitexact
 
+FATE_TRIM_BSF-$(call DEMMUX, AAC, FRAMECRC, TRIM_BSF PIPE_PROTOCOL) += 
fate-ffmpeg-trim-bsf-all fate-ffmpeg-trim-bsf-pts fate-ffmpeg-trim-bsf-dts 
fate-ffmpeg-trim-bsf-pkt
+fate-ffmpeg-trim-bsf-all: CMD = framecrc -i 
$(TARGET_SAMPLES)/audiomatch/tones_afconvert_16000_mono_aac_lc.adts -c:a copy 
-bsf:a trim
+fate-ffmpeg-trim-bsf-pts: CMD = framecrc -i 
$(TARGET_SAMPLES)/audiomatch/tones_afconvert_16000_mono_aac_lc.adts -c:a copy 
-bsf:a trim=start_pts=1806336:end_pts=18063360
+fate-ffmpeg-trim-bsf-dts: CMD = framecrc -i 
$(TARGET_SAMPLES)/audiomatch/tones_afconvert_16000_mono_aac_lc.adts -c:a copy 
-bsf:a trim=start_dts=3612672:end_dts=10838016
+fate-ffmpeg-trim-bsf-pkt: CMD = framecrc -i 
$(TARGET_SAMPLES)/audiomatch/tones_afconvert_16000_mono_aac_lc.adts -c:a copy 
-bsf:a trim=start_pkt=5:end_pkt=15
+FATE_SAMPLES_FFMPEG-yes += $(FATE_TRIM_BSF-yes)
+
 FATE_TIME_BASE-$(call PARSERDEMDEC, MPEGVIDEO, MPEGPS, MPEG2VIDEO, 
MPEGVIDEO_DEMUXER MXF_MUXER) += fate-time_base
 fate-time_base: CMD = md5 -i $(TARGET_SAMPLES)/mpeg2/dvd_single_frame.vob -an 
-sn -c:v copy -r 25 -fflags +bitexact -f mxf
 
diff --git a/tests/ref/fate/ffmpeg-trim-bsf-all 
b/tests/ref/fate/ffmpeg-trim-bsf-all
new file mode 100644
index 0000000000..60edaa1d0f
--- /dev/null
+++ b/tests/ref/fate/ffmpeg-trim-bsf-all
@@ -0,0 +1,39 @@
+#tb 0: 1/28224000
+#media_type 0: audio
+#codec_id 0: aac
+#sample_rate 0: 16000
+#channel_layout_name 0: mono
+0,          0,          0,  1806336,       11, 0x204d0430
+0,    1806336,    1806336,  1806336,      170, 0xd84b5314
+0,    3612672,    3612672,  1806336,      134, 0x38bd43ed
+0,    5419008,    5419008,  1806336,      101, 0xa57c2f3a
+0,    7225344,    7225344,  1806336,      321, 0xfb92953a
+0,    9031680,    9031680,  1806336,      214, 0x143460ad
+0,   10838016,   10838016,  1806336,      126, 0x685c3988
+0,   12644352,   12644352,  1806336,      191, 0x83256440
+0,   14450688,   14450688,  1806336,      139, 0x90373f34
+0,   16257024,   16257024,  1806336,      159, 0xc36153dc
+0,   18063360,   18063360,  1806336,      234, 0xff6b6ed9
+0,   19869696,   19869696,  1806336,      129, 0x439b3859
+0,   21676032,   21676032,  1806336,      170, 0x5a275023
+0,   23482368,   23482368,  1806336,      154, 0xf2a846e2
+0,   25288704,   25288704,  1806336,      126, 0x40943574
+0,   27095040,   27095040,  1806336,      160, 0x77e345fb
+0,   28901376,   28901376,  1806336,      192, 0xb90452c3
+0,   30707712,   30707712,  1806336,      133, 0x443d404e
+0,   32514048,   32514048,  1806336,      253, 0xcb7f7ed0
+0,   34320384,   34320384,  1806336,      169, 0xb4b6508d
+0,   36126720,   36126720,  1806336,      142, 0x61393f69
+0,   37933056,   37933056,  1806336,      253, 0xe0c27aff
+0,   39739392,   39739392,  1806336,      269, 0x72ed7afa
+0,   41545728,   41545728,  1806336,       67, 0xd6281c15
+0,   43352064,   43352064,  1806336,      262, 0x61687cd4
+0,   45158400,   45158400,  1806336,      160, 0x27df47c7
+0,   46964736,   46964736,  1806336,      167, 0xb2d748dc
+0,   48771072,   48771072,  1806336,      222, 0x928864d7
+0,   50577408,   50577408,  1806336,      151, 0xef554327
+0,   52383744,   52383744,  1806336,      194, 0x77d05f2b
+0,   54190080,   54190080,  1806336,      184, 0xba7458a4
+0,   55996416,   55996416,  1806336,      163, 0x86965266
+0,   57802752,   57802752,  1806336,      208, 0xa9f162cb
+0,   59609088,   59609088,  1806336,      144, 0x1f633caa
diff --git a/tests/ref/fate/ffmpeg-trim-bsf-dts 
b/tests/ref/fate/ffmpeg-trim-bsf-dts
new file mode 100644
index 0000000000..2cae1141bd
--- /dev/null
+++ b/tests/ref/fate/ffmpeg-trim-bsf-dts
@@ -0,0 +1,9 @@
+#tb 0: 1/28224000
+#media_type 0: audio
+#codec_id 0: aac
+#sample_rate 0: 16000
+#channel_layout_name 0: mono
+0,    3612672,    3612672,  1806336,      134, 0x38bd43ed
+0,    5419008,    5419008,  1806336,      101, 0xa57c2f3a
+0,    7225344,    7225344,  1806336,      321, 0xfb92953a
+0,    9031680,    9031680,  1806336,      214, 0x143460ad
diff --git a/tests/ref/fate/ffmpeg-trim-bsf-pkt 
b/tests/ref/fate/ffmpeg-trim-bsf-pkt
new file mode 100644
index 0000000000..60fe07751f
--- /dev/null
+++ b/tests/ref/fate/ffmpeg-trim-bsf-pkt
@@ -0,0 +1,15 @@
+#tb 0: 1/28224000
+#media_type 0: audio
+#codec_id 0: aac
+#sample_rate 0: 16000
+#channel_layout_name 0: mono
+0,    9031680,    9031680,  1806336,      214, 0x143460ad
+0,   10838016,   10838016,  1806336,      126, 0x685c3988
+0,   12644352,   12644352,  1806336,      191, 0x83256440
+0,   14450688,   14450688,  1806336,      139, 0x90373f34
+0,   16257024,   16257024,  1806336,      159, 0xc36153dc
+0,   18063360,   18063360,  1806336,      234, 0xff6b6ed9
+0,   19869696,   19869696,  1806336,      129, 0x439b3859
+0,   21676032,   21676032,  1806336,      170, 0x5a275023
+0,   23482368,   23482368,  1806336,      154, 0xf2a846e2
+0,   25288704,   25288704,  1806336,      126, 0x40943574
diff --git a/tests/ref/fate/ffmpeg-trim-bsf-pts 
b/tests/ref/fate/ffmpeg-trim-bsf-pts
new file mode 100644
index 0000000000..298cbdabde
--- /dev/null
+++ b/tests/ref/fate/ffmpeg-trim-bsf-pts
@@ -0,0 +1,14 @@
+#tb 0: 1/28224000
+#media_type 0: audio
+#codec_id 0: aac
+#sample_rate 0: 16000
+#channel_layout_name 0: mono
+0,    1806336,    1806336,  1806336,      170, 0xd84b5314
+0,    3612672,    3612672,  1806336,      134, 0x38bd43ed
+0,    5419008,    5419008,  1806336,      101, 0xa57c2f3a
+0,    7225344,    7225344,  1806336,      321, 0xfb92953a
+0,    9031680,    9031680,  1806336,      214, 0x143460ad
+0,   10838016,   10838016,  1806336,      126, 0x685c3988
+0,   12644352,   12644352,  1806336,      191, 0x83256440
+0,   14450688,   14450688,  1806336,      139, 0x90373f34
+0,   16257024,   16257024,  1806336,      159, 0xc36153dc
-- 
2.52.0

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

Reply via email to