[FFmpeg-devel] [PATCH 7/8] fftools/ffmpeg_demux: implement -bsf for input
Stefano Sabatini
stefasab at gmail.com
Sat Jan 6 14:12:19 EET 2024
On date Friday 2024-01-05 17:42:50 +0100, Anton Khirnov wrote:
> Previously bitstream filters could only be applied right before muxing,
> this allows to apply them right after demuxing.
> ---
> Changelog | 1 +
> doc/ffmpeg.texi | 22 +++--
> fftools/ffmpeg_demux.c | 139 ++++++++++++++++++++++++++++----
> fftools/ffmpeg_opt.c | 2 +-
> tests/fate/ffmpeg.mak | 5 ++
> tests/ref/fate/ffmpeg-bsf-input | 18 +++++
> 6 files changed, 164 insertions(+), 23 deletions(-)
> create mode 100644 tests/ref/fate/ffmpeg-bsf-input
>
> diff --git a/Changelog b/Changelog
> index 5b2899d05b..f8191d88c7 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -18,6 +18,7 @@ version <next>:
> - lavu/eval: introduce randomi() function in expressions
> - VVC decoder
> - fsync filter
> +- ffmpeg CLI -bsf option may now be used for input as well as output
>
> version 6.1:
> - libaribcaption decoder
> diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
> index d75517b443..59f7badcb6 100644
> --- a/doc/ffmpeg.texi
> +++ b/doc/ffmpeg.texi
> @@ -2093,26 +2093,34 @@ an output mpegts file:
> ffmpeg -i inurl -streamid 0:33 -streamid 1:36 out.ts
> @end example
>
> - at item -bsf[:@var{stream_specifier}] @var{bitstream_filters} (@emph{output,per-stream})
> -Apply bitstream filters to matching streams.
> + at item -bsf[:@var{stream_specifier}] @var{bitstream_filters} (@emph{input/output,per-stream})
> +Apply bitstream filters to matching streams. The filters are applied to each
> +packet as it is received from the demuxer (when used as an input option) or
> +before it is sent to the muxer (when used as an output option).
>
> @var{bitstream_filters} is a comma-separated list of bitstream filter
> -specifications. The specified bitstream filters are applied to coded packets in
> -the order they are written in. Each bitstream filter specification is of the
> -form
> +specifications, each of the form
> @example
> @var{filter}[=@var{optname0}=@var{optval0}:@var{optname1}=@var{optval1}:...]
> @end example
> Any of the ',=:' characters that are to be a part of an option value need to be
> escaped with a backslash.
>
> -Use the @code{-bsfs} option to get the list of bitstream filters.
> +Use the @code{-bsfs} option to get the list of bitstream filters. E.g.
This looks spurious, since this suggests the example is about the
listing, and it's applying a weird order of example/explanation
(rather than the opposite).
What about something as:
--------------------------
[...]
Use the @code{-bsfs} option to get the list of bitstream filters.
Some examples follow.
To apply the @code{h264_mp4toannexb} bitstream filter (which converts
MP4-encapsulated H.264 stream to Annex B) to the @emph{input} video stream:
@example
...
@end example
To apply the @code{mov2textsub} bitstream filter (which extracts text from MOV
subtitles) to the @emph{output} subtitle stream:
@example
...
@end example
Note, however, that since both examples use @code{-c copy}, it matters
little whether the filters are applied on input or output - that would
change if transcoding was happening.
--------------------------
[...]
> +on input or output - that would change if transcoding was hapenning.
hapenning typo
>
> @item -tag[:@var{stream_specifier}] @var{codec_tag} (@emph{input/output,per-stream})
> Force a tag/fourcc for matching streams.
> diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c
> index eae1f0bde5..16d4f67e59 100644
> --- a/fftools/ffmpeg_demux.c
> +++ b/fftools/ffmpeg_demux.c
> @@ -34,6 +34,7 @@
> #include "libavutil/time.h"
> #include "libavutil/timestamp.h"
>
> +#include "libavcodec/bsf.h"
> #include "libavcodec/packet.h"
>
> #include "libavformat/avformat.h"
> @@ -71,6 +72,8 @@ typedef struct DemuxStream {
>
> const AVCodecDescriptor *codec_desc;
>
> + AVBSFContext *bsf;
> +
> /* number of packets successfully read for this stream */
> uint64_t nb_packets;
> // combined size of all the packets read
> @@ -118,6 +121,8 @@ typedef struct Demuxer {
> typedef struct DemuxThreadContext {
> // packet used for reading from the demuxer
> AVPacket *pkt_demux;
> + // packet for reading from BSFs
> + AVPacket *pkt_bsf;
> } DemuxThreadContext;
>
> static DemuxStream *ds_from_ist(InputStream *ist)
> @@ -513,13 +518,17 @@ static int do_send(Demuxer *d, DemuxStream *ds, AVPacket *pkt, unsigned flags,
> return 0;
> }
>
> -static int demux_send(Demuxer *d, DemuxStream *ds, AVPacket *pkt, unsigned flags)
> +static int demux_send(Demuxer *d, DemuxThreadContext *dt, DemuxStream *ds,
> + AVPacket *pkt, unsigned flags)
> {
> InputFile *f = &d->f;
> int ret;
>
> + // pkt can be NULL only when flushing BSFs
> + av_assert0(ds->bsf || pkt);
> +
> // send heartbeat for sub2video streams
> - if (d->pkt_heartbeat && pkt->pts != AV_NOPTS_VALUE) {
> + if (d->pkt_heartbeat && pkt && pkt->pts != AV_NOPTS_VALUE) {
> for (int i = 0; i < f->nb_streams; i++) {
> DemuxStream *ds1 = ds_from_ist(f->streams[i]);
>
> @@ -537,10 +546,69 @@ static int demux_send(Demuxer *d, DemuxStream *ds, AVPacket *pkt, unsigned flags
> }
> }
>
> - ret = do_send(d, ds, pkt, flags, "demuxed");
> - if (ret < 0)
> - return ret;
> + if (ds->bsf) {
> + if (pkt)
> + av_packet_rescale_ts(pkt, pkt->time_base, ds->bsf->time_base_in);
>
> + ret = av_bsf_send_packet(ds->bsf, pkt);
> + if (ret < 0) {
> + if (pkt)
> + av_packet_unref(pkt);
> + av_log(ds, AV_LOG_ERROR, "Error submitting a packet for filtering: %s\n",
might be useful to signal the stream identifier
> + av_err2str(ret));
possibly redundant? (IIRC this is shown anyway in the outer level failure message)
> + return ret;
> + }
> +
> + while (1) {
> + ret = av_bsf_receive_packet(ds->bsf, dt->pkt_bsf);
> + if (ret == AVERROR(EAGAIN))
> + return 0;
> + else if (ret < 0) {
> + if (ret != AVERROR_EOF)
> + av_log(ds, AV_LOG_ERROR,
> + "Error applying bitstream filters to a packet: %s\n",
> + av_err2str(ret));
ditto
> + return ret;
> + }
> +
> + dt->pkt_bsf->time_base = ds->bsf->time_base_out;
> +
> + ret = do_send(d, ds, dt->pkt_bsf, 0, "filtered");
> + if (ret < 0) {
> + av_packet_unref(dt->pkt_bsf);
> + return ret;
> + }
> + }
> + } else {
> + ret = do_send(d, ds, pkt, flags, "demuxed");
> + if (ret < 0)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int demux_bsf_flush(Demuxer *d, DemuxThreadContext *dt)
> +{
> + InputFile *f = &d->f;
> + int ret;
> +
> + for (unsigned i = 0; i < f->nb_streams; i++) {
> + DemuxStream *ds = ds_from_ist(f->streams[i]);
> +
> + if (!ds->bsf)
> + continue;
> +
> + ret = demux_send(d, dt, ds, NULL, 0);
> + ret = (ret == AVERROR_EOF) ? 0 : (ret < 0) ? ret : AVERROR_BUG;
> + if (ret < 0) {
> + av_log(ds, AV_LOG_ERROR, "Error flushing BSFs: %s\n",
> + av_err2str(ret));
ditto
> + return ret;
> + }
> +
> + av_bsf_flush(ds->bsf);
> + }
>
> return 0;
> }
> @@ -573,6 +641,7 @@ static void thread_set_name(InputFile *f)
> static void demux_thread_uninit(DemuxThreadContext *dt)
> {
> av_packet_free(&dt->pkt_demux);
> + av_packet_free(&dt->pkt_bsf);
>
> memset(dt, 0, sizeof(*dt));
> }
> @@ -585,6 +654,10 @@ static int demux_thread_init(DemuxThreadContext *dt)
> if (!dt->pkt_demux)
> return AVERROR(ENOMEM);
>
> + dt->pkt_bsf = av_packet_alloc();
> + if (!dt->pkt_bsf)
> + return AVERROR(ENOMEM);
> +
> return 0;
> }
>
> @@ -619,10 +692,22 @@ static void *input_thread(void *arg)
> continue;
> }
> if (ret < 0) {
> + int ret_bsf;
> +
> + if (ret == AVERROR_EOF)
> + av_log(d, AV_LOG_VERBOSE, "EOF while reading input\n");
> + else {
> + av_log(d, AV_LOG_ERROR, "Error during demuxing: %s\n",
> + av_err2str(ret));
> + ret = exit_on_error ? ret : 0;
> + }
> +
> + ret_bsf = demux_bsf_flush(d, &dt);
> + ret = err_merge(ret == AVERROR_EOF ? 0 : ret, ret_bsf);
> +
> if (d->loop) {
> /* signal looping to our consumers */
> dt.pkt_demux->stream_index = -1;
> -
> ret = sch_demux_send(d->sch, f->index, dt.pkt_demux, 0);
> if (ret >= 0)
> ret = seek_to_start(d, (Timestamp){ .ts = dt.pkt_demux->pts,
> @@ -633,14 +718,6 @@ static void *input_thread(void *arg)
> /* fallthrough to the error path */
> }
>
> - if (ret == AVERROR_EOF)
> - av_log(d, AV_LOG_VERBOSE, "EOF while reading input\n");
> - else {
> - av_log(d, AV_LOG_ERROR, "Error during demuxing: %s\n",
> - av_err2str(ret));
> - ret = exit_on_error ? ret : 0;
> - }
> -
> break;
> }
>
> @@ -677,7 +754,7 @@ static void *input_thread(void *arg)
> if (d->readrate)
> readrate_sleep(d);
>
> - ret = demux_send(d, ds, dt.pkt_demux, send_flags);
> + ret = demux_send(d, &dt, ds, dt.pkt_demux, send_flags);
> if (ret < 0)
> break;
> }
> @@ -735,9 +812,11 @@ static void demux_final_stats(Demuxer *d)
> static void ist_free(InputStream **pist)
> {
> InputStream *ist = *pist;
> + DemuxStream *ds;
>
> if (!ist)
> return;
> + ds = ds_from_ist(ist);
>
> dec_free(&ist->decoder);
>
> @@ -749,6 +828,8 @@ static void ist_free(InputStream **pist)
> avcodec_free_context(&ist->dec_ctx);
> avcodec_parameters_free(&ist->par);
>
> + av_bsf_free(&ds->bsf);
> +
> av_freep(pist);
> }
>
> @@ -1039,6 +1120,7 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st)
> const char *hwaccel = NULL;
> char *hwaccel_output_format = NULL;
> char *codec_tag = NULL;
> + char *bsfs = NULL;
> char *next;
> char *discard_str = NULL;
> int ret;
> @@ -1258,6 +1340,33 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st)
> return ret;
> }
>
> + MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, ic, st);
> + if (bsfs) {
> + ret = av_bsf_list_parse_str(bsfs, &ds->bsf);
> + if (ret < 0) {
> + av_log(ist, AV_LOG_ERROR,
> + "Error parsing bitstream filter sequence '%s': %s\n",
> + bsfs, av_err2str(ret));
> + return ret;
> + }
> +
> + ret = avcodec_parameters_copy(ds->bsf->par_in, ist->par);
> + if (ret < 0)
> + return ret;
> + ds->bsf->time_base_in = ist->st->time_base;
> +
> + ret = av_bsf_init(ds->bsf);
> + if (ret < 0) {
> + av_log(ist, AV_LOG_ERROR, "Error initializing bitstream filters: %s\n",
> + av_err2str(ret));
> + return ret;
> + }
> +
> + ret = avcodec_parameters_copy(ist->par, ds->bsf->par_out);
> + if (ret < 0)
> + return ret;
> + }
> +
> ds->codec_desc = avcodec_descriptor_get(ist->par->codec_id);
>
> return 0;
> diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
> index c189cf373b..76b50c0bad 100644
> --- a/fftools/ffmpeg_opt.c
> +++ b/fftools/ffmpeg_opt.c
> @@ -1919,7 +1919,7 @@ const OptionDef options[] = {
> "0 = use frame rate (video) or sample rate (audio),"
> "-1 = match source time base", "ratio" },
>
> - { "bsf", OPT_TYPE_STRING, OPT_SPEC | OPT_EXPERT | OPT_OUTPUT,
> + { "bsf", OPT_TYPE_STRING, OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_INPUT,
> { .off = OFFSET(bitstream_filters) },
> "A comma-separated list of bitstream filters", "bitstream_filters", },
>
> diff --git a/tests/fate/ffmpeg.mak b/tests/fate/ffmpeg.mak
> index 1bfd5c1b31..df955df4d0 100644
> --- a/tests/fate/ffmpeg.mak
> +++ b/tests/fate/ffmpeg.mak
> @@ -256,3 +256,8 @@ FATE_SAMPLES_FFMPEG-$(call FRAMECRC, MPEGVIDEO, MPEG2VIDEO) += fate-ffmpeg-input
> fate-ffmpeg-error-rate-fail: CMD = ffmpeg -i $(TARGET_SAMPLES)/mkv/h264_tta_undecodable.mkv -c:v copy -f null -; test $$? -eq 69
> fate-ffmpeg-error-rate-pass: CMD = ffmpeg -i $(TARGET_SAMPLES)/mkv/h264_tta_undecodable.mkv -c:v copy -f null - -max_error_rate 1
> FATE_SAMPLES_FFMPEG-$(call ENCDEC, PCM_S16LE TTA, NULL MATROSKA) += fate-ffmpeg-error-rate-fail fate-ffmpeg-error-rate-pass
> +
> +# test input -bsf
> +# use -stream_loop, because it tests flushing bsfs
> +fate-ffmpeg-bsf-input: CMD = framecrc -stream_loop 2 -bsf setts=PTS*2 -i $(TARGET_SAMPLES)/hevc/extradata-reload-multi-stsd.mov -c copy
> +FATE_SAMPLES_FFMPEG-$(call FRAMECRC, MOV, , SETTS_BSF) += fate-ffmpeg-bsf-input
> diff --git a/tests/ref/fate/ffmpeg-bsf-input b/tests/ref/fate/ffmpeg-bsf-input
> new file mode 100644
> index 0000000000..67dd57cf6d
> --- /dev/null
> +++ b/tests/ref/fate/ffmpeg-bsf-input
> @@ -0,0 +1,18 @@
> +#extradata 0: 110, 0xb4031479
> +#tb 0: 1/25
> +#media_type 0: video
> +#codec_id 0: hevc
> +#dimensions 0: 128x128
> +#sar 0: 1/1
> +0, 0, 0, 1, 2108, 0x57c38f64
> +0, 2, 2, 1, 31, 0xabe10d25, F=0x0
> +0, 4, 4, 1, 1915, 0xd430347f, S=1, 109
> +0, 6, 6, 1, 35, 0xc4ad0d4c, F=0x0
> +0, 8, 8, 1, 2108, 0x57c38f64, S=1, 110
> +0, 10, 10, 1, 31, 0xabe10d25, F=0x0
> +0, 12, 12, 1, 1915, 0xd430347f, S=1, 109
> +0, 14, 14, 1, 35, 0xc4ad0d4c, F=0x0
> +0, 16, 16, 1, 2108, 0x57c38f64, S=1, 110
> +0, 18, 18, 1, 31, 0xabe10d25, F=0x0
> +0, 20, 20, 1, 1915, 0xd430347f, S=1, 109
> +0, 22, 22, 1, 35, 0xc4ad0d4c, F=0x0
LGTM otherwise (and nice feature!).
More information about the ffmpeg-devel
mailing list