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]
