X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fffmpeg%2Fproducer%2Ffilter%2Faudio_filter.cpp;h=b9059309a334a19277e6e71ae02160912f5c9245;hb=e0e1a2b1fdcab66e8da72e3eaa2db5564f8f9d72;hp=c592023b3b6966bec916897eb0d4d6473f611b88;hpb=609f14202502c94ee3cf1c07dad1bddb7fb0d0c2;p=casparcg diff --git a/modules/ffmpeg/producer/filter/audio_filter.cpp b/modules/ffmpeg/producer/filter/audio_filter.cpp index c592023b3..b9059309a 100644 --- a/modules/ffmpeg/producer/filter/audio_filter.cpp +++ b/modules/ffmpeg/producer/filter/audio_filter.cpp @@ -25,6 +25,7 @@ #include "../../ffmpeg_error.h" #include "../../ffmpeg.h" +#include "../util/util.h" #include #include @@ -37,18 +38,18 @@ #include #include #include +#include #if defined(_MSC_VER) #pragma warning (push) #pragma warning (disable : 4244) #endif -extern "C" +extern "C" { #include #include #include #include - #include #include #include } @@ -57,110 +58,93 @@ extern "C" #endif namespace caspar { namespace ffmpeg { - + +std::string create_sourcefilter_str(const audio_input_pad& input_pad, std::string name) +{ + const auto asrc_options = (boost::format("abuffer=time_base=%1%/%2%:sample_rate=%3%:sample_fmt=%4%:channel_layout=0x%|5$x| [%6%]") + % input_pad.time_base.numerator() % input_pad.time_base.denominator() + % input_pad.sample_rate + % av_get_sample_fmt_name(input_pad.sample_fmt) + % input_pad.audio_channel_layout + % name).str(); + + return asrc_options; +} + +std::string create_sinkfilter_str(const audio_output_pad& output_pad, std::string name) +{ + const auto asink_options = (boost::format("[%1%] abuffersink") % name).str(); + + return asink_options; +} + struct audio_filter::implementation { std::string filtergraph_; - std::shared_ptr audio_graph_; - AVFilterContext* audio_graph_in_; - AVFilterContext* audio_graph_out_; + std::shared_ptr audio_graph_; + std::vector audio_graph_inputs_; + std::vector audio_graph_outputs_; + + std::vector input_pads_; implementation( - boost::rational in_time_base, - int in_sample_rate, - AVSampleFormat in_sample_fmt, - std::int64_t in_audio_channel_layout, - std::vector out_sample_rates, - std::vector out_sample_fmts, - std::vector out_audio_channel_layouts, + std::vector input_pads, + std::vector output_pads, const std::string& filtergraph) : filtergraph_(boost::to_lower_copy(filtergraph)) + , input_pads_(std::move(input_pads)) { - if (out_sample_rates.empty()) - out_sample_rates.push_back(48000); + if (input_pads_.empty()) + CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info("input_pads cannot be empty")); - out_sample_rates.push_back(-1); + if (output_pads.empty()) + CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info("output_pads cannot be empty")); - if (out_sample_fmts.empty()) - out_sample_fmts.push_back(AV_SAMPLE_FMT_S32); + audio_graph_.reset( + avfilter_graph_alloc(), + [](AVFilterGraph* p) + { + avfilter_graph_free(&p); + }); - out_sample_fmts.push_back(AV_SAMPLE_FMT_NONE); + std::vector complete_filter_graph; - if (out_audio_channel_layouts.empty()) - out_audio_channel_layouts.push_back(AV_CH_LAYOUT_NATIVE); + { + int i = 0; + for (auto& input_pad : input_pads_) + complete_filter_graph.push_back(create_sourcefilter_str(input_pad, "a:" + boost::lexical_cast(i++))); + } - out_audio_channel_layouts.push_back(-1); + if (filtergraph_.empty()) + complete_filter_graph.push_back("[a:0] anull [aout:0]"); + else + complete_filter_graph.push_back(filtergraph_); - audio_graph_.reset( - avfilter_graph_alloc(), - [](AVFilterGraph* p) + { + int i = 0; + for (auto& output_pad : output_pads) { - avfilter_graph_free(&p); - }); - - const auto asrc_options = (boost::format("time_base=%1%/%2%:sample_rate=%3%:sample_fmt=%4%:channel_layout=0x%|5$x|") - % in_time_base.numerator() % in_time_base.denominator() - % in_sample_rate - % av_get_sample_fmt_name(in_sample_fmt) - % in_audio_channel_layout).str(); - - AVFilterContext* filt_asrc = nullptr; - FF(avfilter_graph_create_filter( - &filt_asrc, - avfilter_get_by_name("abuffer"), - "filter_buffer", - asrc_options.c_str(), - nullptr, - audio_graph_.get())); - - AVFilterContext* filt_asink = nullptr; - FF(avfilter_graph_create_filter( - &filt_asink, - avfilter_get_by_name("abuffersink"), - "filter_buffersink", - nullptr, - nullptr, - audio_graph_.get())); - -#pragma warning (push) -#pragma warning (disable : 4245) + complete_filter_graph.push_back(create_sinkfilter_str(output_pad, "aout:" + boost::lexical_cast(i++))); - FF(av_opt_set_int_list( - filt_asink, - "sample_fmts", - out_sample_fmts.data(), - -1, - AV_OPT_SEARCH_CHILDREN)); - FF(av_opt_set_int_list( - filt_asink, - "channel_layouts", - out_audio_channel_layouts.data(), - -1, - AV_OPT_SEARCH_CHILDREN)); - FF(av_opt_set_int_list( - filt_asink, - "sample_rates", - out_sample_rates.data(), - -1, - AV_OPT_SEARCH_CHILDREN)); + output_pad.sample_fmts.push_back(AVSampleFormat::AV_SAMPLE_FMT_NONE); + output_pad.audio_channel_layouts.push_back(0); + output_pad.sample_rates.push_back(-1); + } + } -#pragma warning (pop) - configure_filtergraph( *audio_graph_, - filtergraph_, - *filt_asrc, - *filt_asink); + boost::join(complete_filter_graph, ";"), + audio_graph_inputs_, + audio_graph_outputs_, + output_pads); - audio_graph_in_ = filt_asrc; - audio_graph_out_ = filt_asink; - if (is_logging_quiet_for_thread()) CASPAR_LOG(trace) - << u16(std::string("\n") + << u16(std::string("\n") + avfilter_graph_dump( - audio_graph_.get(), + audio_graph_.get(), nullptr)); else CASPAR_LOG(debug) @@ -169,121 +153,166 @@ struct audio_filter::implementation audio_graph_.get(), nullptr)); } - + void configure_filtergraph( AVFilterGraph& graph, const std::string& filtergraph, - AVFilterContext& source_ctx, - AVFilterContext& sink_ctx) + std::vector& source_contexts, + std::vector& sink_contexts, + const std::vector& output_pads) { - AVFilterInOut* outputs = nullptr; - AVFilterInOut* inputs = nullptr; + AVFilterInOut* outputs = nullptr; + AVFilterInOut* inputs = nullptr; + + FF(avfilter_graph_parse2( + &graph, + filtergraph.c_str(), + &inputs, + &outputs)); - try + // Workaround because outputs and inputs are not filled in for some reason + for (unsigned i = 0; i < graph.nb_filters; ++i) { - if(!filtergraph.empty()) - { - outputs = avfilter_inout_alloc(); - inputs = avfilter_inout_alloc(); - - CASPAR_VERIFY(outputs && inputs); - - outputs->name = av_strdup("in"); - outputs->filter_ctx = &source_ctx; - outputs->pad_idx = 0; - outputs->next = nullptr; - - inputs->name = av_strdup("out"); - inputs->filter_ctx = &sink_ctx; - inputs->pad_idx = 0; - inputs->next = nullptr; - - FF(avfilter_graph_parse( - &graph, - filtergraph.c_str(), - inputs, - outputs, - nullptr)); - } - else - { - FF(avfilter_link( - &source_ctx, - 0, - &sink_ctx, - 0)); - } + auto filter = graph.filters[i]; + + if (std::string(filter->filter->name) == "abuffer") + source_contexts.push_back(filter); - FF(avfilter_graph_config( - &graph, - nullptr)); + if (std::string(filter->filter->name) == "abuffersink") + sink_contexts.push_back(filter); } - catch(...) + + for (AVFilterInOut* iter = inputs; iter; iter = iter->next) + source_contexts.push_back(iter->filter_ctx); + + for (AVFilterInOut* iter = outputs; iter; iter = iter->next) + sink_contexts.push_back(iter->filter_ctx); + + for (int i = 0; i < sink_contexts.size(); ++i) { - //avfilter_inout_free(&outputs); - //avfilter_inout_free(&inputs); - throw; + auto sink_context = sink_contexts.at(i); + auto& output_pad = output_pads.at(i); + +#pragma warning (push) +#pragma warning (disable : 4245) + FF(av_opt_set_int_list( + sink_context, + "sample_fmts", + output_pad.sample_fmts.data(), + -1, + AV_OPT_SEARCH_CHILDREN)); + + FF(av_opt_set_int_list( + sink_context, + "channel_layouts", + output_pad.audio_channel_layouts.data(), + 0, + AV_OPT_SEARCH_CHILDREN)); + + FF(av_opt_set_int_list( + sink_context, + "sample_rates", + output_pad.sample_rates.data(), + -1, + AV_OPT_SEARCH_CHILDREN)); +#pragma warning (pop) } + + FF(avfilter_graph_config( + &graph, + nullptr)); } - void push(const std::shared_ptr& src_av_frame) - { + void set_guaranteed_output_num_samples_per_frame(int output_pad_id, int num_samples) + { + av_buffersink_set_frame_size(audio_graph_outputs_.at(output_pad_id), num_samples); + } + + void push(int input_pad_id, const std::shared_ptr& src_av_frame) + { FF(av_buffersrc_add_frame( - audio_graph_in_, + audio_graph_inputs_.at(input_pad_id), src_av_frame.get())); } - std::shared_ptr poll() + void push(int input_pad_id, const boost::iterator_range& frame_samples) { - std::shared_ptr filt_frame( - av_frame_alloc(), - [](AVFrame* p) - { - av_frame_free(&p); - }); - + auto& input_pad = input_pads_.at(input_pad_id); + auto num_samples = frame_samples.size() / av_get_channel_layout_nb_channels(input_pad.audio_channel_layout); + auto input_frame = ffmpeg::create_frame(); + + input_frame->channels = av_get_channel_layout_nb_channels(input_pad.audio_channel_layout); + input_frame->channel_layout = input_pad.audio_channel_layout; + input_frame->sample_rate = input_pad.sample_rate; + input_frame->nb_samples = static_cast(num_samples); + input_frame->format = input_pad.sample_fmt; + input_frame->pts = 0; + + av_samples_fill_arrays( + input_frame->extended_data, + input_frame->linesize, + reinterpret_cast(frame_samples.begin()), + input_frame->channels, + input_frame->nb_samples, + static_cast(input_frame->format), + 16); + + push(input_pad_id, input_frame); + } + + std::shared_ptr poll(int output_pad_id) + { + auto filt_frame = create_frame(); + const auto ret = av_buffersink_get_frame( - audio_graph_out_, + audio_graph_outputs_.at(output_pad_id), filt_frame.get()); - + if(ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) return nullptr; - + FF_RET(ret, "poll"); return filt_frame; } + + const AVFilterLink& get_output_pad_info(int output_pad_id) const + { + return *audio_graph_outputs_.at(output_pad_id)->inputs[0]; + } }; audio_filter::audio_filter( - boost::rational in_time_base, - int in_sample_rate, - AVSampleFormat in_sample_fmt, - std::int64_t in_audio_channel_layout, - std::vector out_sample_rates, - std::vector out_sample_fmts, - std::vector out_audio_channel_layouts, + std::vector input_pads, + std::vector output_pads, const std::string& filtergraph) - : impl_(new implementation( - in_time_base, - in_sample_rate, - in_sample_fmt, - in_audio_channel_layout, - std::move(out_sample_rates), - std::move(out_sample_fmts), - std::move(out_audio_channel_layouts), - filtergraph)){} + : impl_(new implementation(std::move(input_pads), std::move(output_pads), filtergraph)) +{ +} audio_filter::audio_filter(audio_filter&& other) : impl_(std::move(other.impl_)){} audio_filter& audio_filter::operator=(audio_filter&& other){impl_ = std::move(other.impl_); return *this;} -void audio_filter::push(const std::shared_ptr& frame){impl_->push(frame);} -std::shared_ptr audio_filter::poll(){return impl_->poll();} +void audio_filter::set_guaranteed_output_num_samples_per_frame(int output_pad_id, int num_samples) { impl_->set_guaranteed_output_num_samples_per_frame(output_pad_id, num_samples); } +void audio_filter::push(int input_pad_id, const std::shared_ptr& frame){impl_->push(input_pad_id, frame);} +void audio_filter::push(int input_pad_id, const boost::iterator_range& frame_samples) { impl_->push(input_pad_id, frame_samples); } +std::shared_ptr audio_filter::poll(int output_pad_id){return impl_->poll(output_pad_id);} std::wstring audio_filter::filter_str() const{return u16(impl_->filtergraph_);} -std::vector> audio_filter::poll_all() -{ +int audio_filter::get_num_output_pads() const { return static_cast(impl_->audio_graph_outputs_.size()); } +const AVFilterLink& audio_filter::get_output_pad_info(int output_pad_id) const { return impl_->get_output_pad_info(output_pad_id); } +std::vector> audio_filter::poll_all(int output_pad_id) +{ std::vector> frames; - for(auto frame = poll(); frame; frame = poll()) + for(auto frame = poll(output_pad_id); frame; frame = poll(output_pad_id)) frames.push_back(spl::make_shared_ptr(frame)); return frames; } +ffmpeg::audio_input_pad create_input_pad(const core::video_format_desc& in_format, int num_channels) +{ + return ffmpeg::audio_input_pad( + boost::rational(1, in_format.audio_sample_rate), + in_format.audio_sample_rate, + AVSampleFormat::AV_SAMPLE_FMT_S32, + av_get_default_channel_layout(num_channels)); +} + }}