[FFmpeg-devel] ffmpeg -af, libavfilter
Stefano Sabatini
stefasab at gmail.com
Fri Feb 24 19:13:33 CET 2012
On date Tuesday 2012-02-21 17:44:28 +0100, Clément Bœsch encoded:
> Hi folks,
>
> This is the 3rd attempt to get the -af option in ffmpeg (Hemanth → Stefano →
> Clément). And guess what, it's still a work in progress because there are still
You forget Mina ;).
> a few issues to fix:
>
> * -async option is now disabled
> * -map_channel is now disabled
> * audio resampling filter doesn't flush
> * on-the-fly resampling won't work anymore
>
> The main reason of all of this is that the current do_audio_out() in ffmpeg.c
> implements its own resampling, audio compensation and related stuff. And this
> code is using a lot the audio decode context. This is a problem because the
> audio buffer in the decoded frame now always contain the data which goes out the
> filtergraph, and not the data just after the decode.
>
> Example with a channel layout change:
>
> Current:
>
> dec do_audio_out() enc
> [6ch] --- [6ch ~~~~ 2ch] --- [2ch]
>
> With filtergraph system (always active, even without specifying -af):
>
> dec filtergraph do_audio_out() enc
> [6ch] --- [6ch ~~~~ 2ch] --- [2ch ~~~~ 2ch] --- [2ch]
>
> So now, do_audio_out() is supposed to receive a sanitized input (which means
> exactly the same sample rate, number of channels, and format as the output) all
> the time, and thus can't do the audio sync anymore, as well as -map_channel;
> the heuristics in do_audio_out() make excessive use of the decode context.
>
> I can deal with -map_channel: I would "just" have to insert af_pan filters (and
> af_amerge to finally support the merge) at the end of the filtergraph, just like
> we could do for the volume option (with af_volume). So I'll be working on this
> in the next days.
> On the other hand, I don't think I'll be able to correctly deal with the -async
> option, which we should somehow integrates to libavfilter; anyone wants to do
> that?
What should the filter exactly do? (I suppose it would be a A|V -> A|V
filter).
>
> Concerning the audio resampling filter which doesn't flush, as Michael said on
> IRC, it's likely to be handled in a request_frame() callback in af_aresample.
>
> Last issue is the on-the-fly resampling heuristics (check resample_changed).
> Libavfilter is not able to deal with that, right? How are we supposed to
> workaround this?
This is dynamic reconfiguration, I keep talking about this since ages
but I never implement it, maybe we could add it to the GSoC task, some
brainstorming on it may help so we have at least a clear design idea.
Currently the abuffersrc source works by normalizing input (same for
the source video buffer).
> Hopefully, fixing these issues will allow to replace a lot of audio "hacks" in
> ffmpeg.c (and we should consider make a hard dependency on libavfilter to get
> rid of all the old code).
Are there still use cases when it makes sense to compile ffmpeg
without libavfilter? Benchmarks? But I agree we should get rid of the
non-lavfi path if keeping it is making the code more harder to
maintain.
> Anyone motivated to lend me a hand on all of this?
>
> --
> Clément B.
> From 22e02b3b5be58f4e28395a2ff10bf65269da2d8a Mon Sep 17 00:00:00 2001
> From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= <clement.boesch at smartjog.com>
> Date: Tue, 14 Feb 2012 17:00:53 +0100
> Subject: [PATCH 1/2] lavfi/WIP: add
> avfilter_fill_frame_from_audio_buffer_ref().
>
> ---
> libavfilter/avcodec.c | 14 ++++++++++++++
> libavfilter/avcodec.h | 11 +++++++++++
> 2 files changed, 25 insertions(+), 0 deletions(-)
>
> diff --git a/libavfilter/avcodec.c b/libavfilter/avcodec.c
> index 2850c4d..5fea5b7 100644
> --- a/libavfilter/avcodec.c
> +++ b/libavfilter/avcodec.c
> @@ -56,6 +56,20 @@ AVFilterBufferRef *avfilter_get_video_buffer_ref_from_frame(const AVFrame *frame
> return picref;
> }
>
> +int avfilter_fill_frame_from_audio_buffer_ref(AVFrame *frame,
> + const AVFilterBufferRef *samplesref)
> +{
> + if (!samplesref || !samplesref->audio || !frame)
> + return AVERROR(EINVAL);
> +
> + memcpy(frame->data, samplesref->data, sizeof(frame->data));
> + frame->pkt_pos = samplesref->pos;
> + frame->format = samplesref->format;
> + frame->nb_samples = samplesref->audio->nb_samples;
> +
> + return 0;
> +}
> +
> int avfilter_fill_frame_from_video_buffer_ref(AVFrame *frame,
> const AVFilterBufferRef *picref)
> {
> diff --git a/libavfilter/avcodec.h b/libavfilter/avcodec.h
> index 22dd1a2..36ab917 100644
> --- a/libavfilter/avcodec.h
> +++ b/libavfilter/avcodec.h
> @@ -47,6 +47,17 @@ int avfilter_copy_frame_props(AVFilterBufferRef *dst, const AVFrame *src);
> AVFilterBufferRef *avfilter_get_video_buffer_ref_from_frame(const AVFrame *frame, int perms);
>
> /**
> + * Fill an AVFrame with the information stored in samplesref.
> + *
> + * @param frame an already allocated AVFrame
> + * @param samplesref an audio buffer reference
> + * @return 0 in case of success, a negative AVERROR code in case of
> + * failure
> + */
> +int avfilter_fill_frame_from_audio_buffer_ref(AVFrame *frame,
> + const AVFilterBufferRef *samplesref);
> +
> +/**
> * Fill an AVFrame with the information stored in picref.
> *
> * @param frame an already allocated AVFrame
> --
> 1.7.9
OK, but maybe we should unify the A/V API and have a single
avfilter_fill_frame_from_buffer_ref().
> From 6f8dabe1fa5e3611134b7a20063d691d1c429437 Mon Sep 17 00:00:00 2001
> From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= <clement.boesch at smartjog.com>
> Date: Tue, 7 Feb 2012 09:36:40 +0100
> Subject: [PATCH 2/2] ffmpeg/WIP: add -af option.
>
> Based on a patch by Stefano which itself is based on a patch by Hemanth.
Based on a patch by Mina Nagy Zaky which was based on a patch by
Stefano which itself is based on a patch by Hemanth.
> ---
> ffmpeg.c | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
> 1 files changed, 175 insertions(+), 10 deletions(-)
>
> diff --git a/ffmpeg.c b/ffmpeg.c
> index 8d727a3..0da8461 100644
> --- a/ffmpeg.c
> +++ b/ffmpeg.c
> @@ -62,6 +62,7 @@
> # include "libavfilter/buffersink.h"
> # include "libavfilter/buffersrc.h"
> # include "libavfilter/vsrc_buffer.h"
> +# include "libavfilter/asrc_abuffer.h"
> #endif
>
> #if HAVE_SYS_RESOURCE_H
> @@ -286,9 +287,13 @@ typedef struct OutputStream {
> #if CONFIG_AVFILTER
> AVFilterContext *output_video_filter;
> AVFilterContext *input_video_filter;
> + AVFilterContext *output_audio_filter;
> + AVFilterContext *input_audio_filter;
> AVFilterBufferRef *picref;
> + AVFilterBufferRef *samplesref;
> char *avfilter;
> AVFilterGraph *graph;
> + AVFilterGraph *agraph;
> #endif
>
> int64_t sws_flags;
> @@ -703,6 +708,96 @@ static int configure_video_filters(InputStream *ist, OutputStream *ost)
>
> return 0;
> }
> +
> +static int configure_audio_filters(InputStream *ist, OutputStream *ost)
> +{
> + AVFilterContext *last_filter, *filter;
> + AVCodecContext * const icodec = ist->st->codec;
> + AVCodecContext * const ocodec = ost->st->codec;
> + const AVFilterLink *outlink;
> +
> + const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16, -1 };
> + const int packing_fmts[] = { AVFILTER_PACKED, -1 };
> + const int64_t *chlayouts = avfilter_all_channel_layouts;
> + AVABufferSinkParams *abuffersink_params;
> +
> + char args[255];
> + int ret;
> +
> + ost->agraph = avfilter_graph_alloc();
> + if (!ost->agraph)
> + return AVERROR(ENOMEM);
> +
> + /* input */
> + if (!icodec->channel_layout)
> + icodec->channel_layout = av_get_default_channel_layout(icodec->channels);
> + snprintf(args, sizeof(args), "%d:%d:0x%"PRIx64":packed",
> + icodec->sample_rate, icodec->sample_fmt, icodec->channel_layout);
> + ret = avfilter_graph_create_filter(&ost->input_audio_filter,
> + avfilter_get_by_name("abuffer"), "asrc",
> + args, NULL, ost->agraph);
> + if (ret < 0)
> + return ret;
> +
> + /* output */
> + abuffersink_params = av_abuffersink_params_alloc();
> + abuffersink_params->sample_fmts = sample_fmts;
> + abuffersink_params->channel_layouts = chlayouts;
> + abuffersink_params->packing_fmts = packing_fmts;
> + ret = avfilter_graph_create_filter(&ost->output_audio_filter,
> + avfilter_get_by_name("abuffersink"), "aout", NULL,
> + abuffersink_params, ost->agraph);
> + if (ret < 0)
> + return ret;
> +
> + /* auto insert resampling in case of sample rate mismatch */
> + last_filter = ost->input_audio_filter;
> + if (icodec->sample_rate != ocodec->sample_rate) {
> + snprintf(args, sizeof(args), "%d", ocodec->sample_rate);
> + if ((ret = avfilter_graph_create_filter(&filter, avfilter_get_by_name("aresample"),
> + NULL, args, NULL, ost->agraph)) < 0)
> + return ret;
> + if ((ret = avfilter_link(last_filter, 0, filter, 0)) < 0)
> + return ret;
> + last_filter = filter;
> + }
> +
> + /* insert user filter (-af) */
> + if (ost->avfilter) {
> + AVFilterInOut *outputs = avfilter_inout_alloc();
> + AVFilterInOut *inputs = avfilter_inout_alloc();
> +
> + outputs->name = av_strdup("in");
> + outputs->filter_ctx = last_filter;
> + outputs->pad_idx = 0;
> + outputs->next = NULL;
> +
> + inputs->name = av_strdup("out");
> + inputs->filter_ctx = ost->output_audio_filter;
> + inputs->pad_idx = 0;
> + inputs->next = NULL;
> +
> + if ((ret = avfilter_graph_parse(ost->agraph, ost->avfilter, &inputs, &outputs, NULL)) < 0)
> + return ret;
> + av_freep(&ost->avfilter);
> + } else {
> + if ((ret = avfilter_link(last_filter, 0, ost->output_audio_filter, 0)) < 0)
> + return ret;
> + }
> +
> + if ((ret = avfilter_graph_config(ost->agraph, NULL)) < 0)
> + return ret;
> +
> + /* set output codec context with settings of the audio buffer sink */
> + outlink = ost->output_audio_filter->inputs[0];
> + ocodec->sample_rate = outlink->sample_rate;
> + ocodec->channel_layout = outlink->channel_layout;
> + ocodec->channels = av_get_channel_layout_nb_channels(outlink->channel_layout);
> + ocodec->sample_fmt = outlink->format;
> +
> + return 0;
> +}
I wonder how much of this could be merged with the
configure_video_filters() code (and if that would be convenient from
the maintainance point of view).
> +
> #endif /* CONFIG_AVFILTER */
>
> static void term_exit(void)
> @@ -1123,12 +1218,15 @@ static void do_audio_out(AVFormatContext *s, OutputStream *ost,
> InputStream *ist, AVFrame *decoded_frame)
> {
> uint8_t *buftmp;
> - int64_t audio_buf_size, size_out;
> -
> - int frame_bytes, resample_changed;
> + int64_t size_out;
> AVCodecContext *enc = ost->st->codec;
> - AVCodecContext *dec = ist->st->codec;
> int osize = av_get_bytes_per_sample(enc->sample_fmt);
> +
> + /* FIXME: restore -map_channel and -async option when avfilter is on */
> +#if !CONFIG_AVFILTER
> + int64_t audio_buf_size;
> + int resample_changed;
> + AVCodecContext *dec = ist->st->codec;
> int isize = av_get_bytes_per_sample(dec->sample_fmt);
> uint8_t *buf = decoded_frame->data[0];
> int size = decoded_frame->nb_samples * dec->channels * isize;
> @@ -1260,9 +1358,12 @@ need_realloc:
> }
> }
> } else
> +#else
> ost->sync_opts = lrintf(get_sync_ipts(ost, ist->pts) * enc->sample_rate) -
> av_fifo_size(ost->fifo) / (enc->channels * osize); // FIXME wrong
> +#endif
>
> +#if !CONFIG_AVFILTER
> if (ost->audio_resample || ost->audio_channels_mapped) {
> buftmp = audio_buf;
> size_out = swr_convert(ost->swr, ( uint8_t*[]){buftmp}, audio_buf_size / (enc->channels * osize),
> @@ -1274,9 +1375,14 @@ need_realloc:
> }
>
> av_assert0(ost->audio_resample || dec->sample_fmt==enc->sample_fmt);
> +#else
> + buftmp = decoded_frame->data[0];
> + size_out = decoded_frame->nb_samples * enc->channels * osize;
> +#endif
>
> /* now encode as many frames as possible */
> if (!(enc->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE)) {
> + int frame_bytes;
> /* output resampled raw samples */
> if (av_fifo_realloc2(ost->fifo, av_fifo_size(ost->fifo) + size_out) < 0) {
> av_log(NULL, AV_LOG_FATAL, "av_fifo_realloc2() failed\n");
> @@ -1969,6 +2075,8 @@ static int transcode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
> AVCodecContext *avctx = ist->st->codec;
> int bps = av_get_bytes_per_sample(ist->st->codec->sample_fmt);
> int i, ret;
> + int decoded_data_size;
> + void *samples;
>
> if (!ist->decoded_frame && !(ist->decoded_frame = avcodec_alloc_frame()))
> return AVERROR(ENOMEM);
> @@ -2000,9 +2108,9 @@ static int transcode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
>
>
> // preprocess audio (volume)
> + decoded_data_size = decoded_frame->nb_samples * avctx->channels * bps;
> + samples = decoded_frame->data[0];
> if (audio_volume != 256) {
> - int decoded_data_size = decoded_frame->nb_samples * avctx->channels * bps;
> - void *samples = decoded_frame->data[0];
> switch (avctx->sample_fmt) {
> case AV_SAMPLE_FMT_U8:
> {
> @@ -2057,6 +2165,23 @@ static int transcode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
> }
> }
>
> +#if CONFIG_AVFILTER
> + for (i = 0; i < nb_output_streams; i++) {
> + OutputStream *ost = &output_streams[i];
> + if (!check_output_constraints(ist, ost) || !ost->encoding_needed)
> + continue;
> + if (av_asrc_buffer_add_buffer(ost->input_audio_filter,
> + samples, decoded_data_size,
> + ist->st->codec->sample_rate,
> + ist->st->codec->sample_fmt,
> + ist->st->codec->channel_layout,
> + 0, ist->pts, 0) < 0) {
> + av_log(NULL, AV_LOG_FATAL, "Failed ton inject audio samples into filter network\n");
> + exit_program(1);
> + }
> + }
> +#endif
> +
> rate_emu_sleep(ist);
>
> for (i = 0; i < nb_output_streams; i++) {
> @@ -2064,9 +2189,32 @@ static int transcode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
>
> if (!check_output_constraints(ist, ost) || !ost->encoding_needed)
> continue;
> +
> +#if CONFIG_AVFILTER
> + while (av_buffersink_poll_frame(ost->output_audio_filter)) {
> + AVFrame *filtered_frame;
> +
> + if (av_buffersink_get_buffer_ref(ost->output_audio_filter, &ost->samplesref, 0) < 0) {
> + av_log(NULL, AV_LOG_WARNING, "AV Filter told us it has audio samples available but failed to output some\n");
This message is a bit lame (think of what "AV Filter" means to the
average user reading the log), yes I know this is consistent with the
video path.
> + goto cont;
> + }
> + if (!ist->filtered_frame && !(ist->filtered_frame = avcodec_alloc_frame())) {
> + ret = AVERROR(ENOMEM);
> + goto end;
> + }
> + filtered_frame = ist->filtered_frame;
> + *filtered_frame = *decoded_frame;
> + avfilter_fill_frame_from_audio_buffer_ref(filtered_frame, ost->samplesref);
> + do_audio_out(output_files[ost->file_index].ctx, ost, ist, filtered_frame);
> + cont:
> + avfilter_unref_buffer(ost->samplesref);
> + }
> +#else
> do_audio_out(output_files[ost->file_index].ctx, ost, ist, decoded_frame);
> +#endif
> }
>
> +end:
> return ret;
> }
>
> @@ -2577,6 +2725,12 @@ static int transcode_init(OutputFile *output_files, int nb_output_files,
> ost->audio_resample |= codec->sample_fmt != icodec->sample_fmt
> || codec->channel_layout != icodec->channel_layout;
> icodec->request_channels = codec->channels;
> +#if CONFIG_AVFILTER
> + if (configure_audio_filters(ist, ost)) {
> + av_log(NULL, AV_LOG_FATAL, "Error opening audio filters!\n");
> + exit_program(1);
> + }
> +#endif
> ost->resample_sample_fmt = icodec->sample_fmt;
> ost->resample_sample_rate = icodec->sample_rate;
> ost->resample_channels = icodec->channels;
> @@ -3113,6 +3267,7 @@ static int transcode(OutputFile *output_files, int nb_output_files,
> }
> #if CONFIG_AVFILTER
> avfilter_graph_free(&ost->graph);
> + avfilter_graph_free(&ost->agraph);
> #endif
> }
>
> @@ -4103,7 +4258,7 @@ static OutputStream *new_audio_stream(OptionsContext *o, AVFormatContext *oc)
> audio_enc->codec_type = AVMEDIA_TYPE_AUDIO;
>
> if (!ost->stream_copy) {
> - char *sample_fmt = NULL;
> + char *sample_fmt = NULL, *filters = NULL;
>
> MATCH_PER_STREAM_OPT(audio_channels, i, audio_enc->channels, oc, st);
>
> @@ -4118,6 +4273,12 @@ static OutputStream *new_audio_stream(OptionsContext *o, AVFormatContext *oc)
>
> ost->rematrix_volume=1.0;
> MATCH_PER_STREAM_OPT(rematrix_volume, f, ost->rematrix_volume, oc, st);
> +
> +#if CONFIG_AVFILTER
> + MATCH_PER_STREAM_OPT(filters, str, filters, oc, st);
> + if (filters)
> + ost->avfilter = av_strdup(filters);
> +#endif
> }
>
> /* check for channel mapping for this audio stream */
> @@ -4938,9 +5099,12 @@ static int opt_qscale(OptionsContext *o, const char *opt, const char *arg)
> return ret;
> }
>
> -static int opt_video_filters(OptionsContext *o, const char *opt, const char *arg)
> +static int opt_avfilters(OptionsContext *o, const char *opt, const char *arg)
> {
> - return parse_option(o, "filter:v", arg, options);
> + char *s = av_asprintf("filter:%c", *opt);
> + int ret = parse_option(o, s, arg, options);
> + av_free(s);
> + return ret;
> }
>
> static int opt_vsync(const char *opt, const char *arg)
> @@ -5049,7 +5213,8 @@ static const OptionDef options[] = {
> { "vstats", OPT_EXPERT | OPT_VIDEO, {(void*)&opt_vstats}, "dump video coding statistics to file" },
> { "vstats_file", HAS_ARG | OPT_EXPERT | OPT_VIDEO, {(void*)opt_vstats_file}, "dump video coding statistics to file", "file" },
> #if CONFIG_AVFILTER
> - { "vf", HAS_ARG | OPT_VIDEO | OPT_FUNC2, {(void*)opt_video_filters}, "video filters", "filter list" },
> + { "vf", HAS_ARG | OPT_VIDEO | OPT_FUNC2, {(void*)opt_avfilters}, "video filters", "filter list" },
> + { "af", HAS_ARG | OPT_VIDEO | OPT_FUNC2, {(void*)opt_avfilters}, "audio filters", "filter list" },
> #endif
> { "intra_matrix", HAS_ARG | OPT_EXPERT | OPT_VIDEO | OPT_STRING | OPT_SPEC, {.off = OFFSET(intra_matrices)}, "specify intra matrix coeffs", "matrix" },
> { "inter_matrix", HAS_ARG | OPT_EXPERT | OPT_VIDEO | OPT_STRING | OPT_SPEC, {.off = OFFSET(inter_matrices)}, "specify inter matrix coeffs", "matrix" },
[...]
Note: this requires a FATE test of course, my last incomplete &
outdated variant can be found here:
http://gitorious.org/~saste/ffmpeg/sastes-ffmpeg/commit/48d568e058f7356de1ae733ffd53cd63e3d97c69
--
FFmpeg = Fantastic Faithful Maxi Patchable Elastic Guru
More information about the ffmpeg-devel
mailing list