#include "audio_filter.h"
#include "../../ffmpeg_error.h"
+#include "../../ffmpeg.h"
#include <common/assert.h>
#include <common/except.h>
#include <cstdio>
#include <sstream>
#include <string>
+#include <algorithm>
#if defined(_MSC_VER)
#pragma warning (push)
#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_filter_list(const std::vector<std::string>& items)
+{
+ return boost::join(items, "|");
+}
+
+std::string channel_layout_to_string(int64_t channel_layout)
+{
+ return (boost::format("0x%|1$x|") % channel_layout).str();
+}
+
+std::string create_sinkfilter_str(const audio_output_pad& output_pad, std::string name)
+{
+ const auto asink_options = (boost::format("[%4%] abuffersink")//=sample_fmts=%1%:channel_layouts=%2%:sample_rates=%3%")
+ % create_filter_list(cpplinq::from(output_pad.sample_fmts)
+ .select(&av_get_sample_fmt_name)
+ .select([](const char* str) { return std::string(str); })
+ .to_vector())
+ % create_filter_list(cpplinq::from(output_pad.sample_fmts)
+ .select(&channel_layout_to_string)
+ .to_vector())
+ % create_filter_list(cpplinq::from(output_pad.sample_rates)
+ .select([](int samplerate) { return boost::lexical_cast<std::string>(samplerate); })
+ .to_vector())
+ % name).str();
+
+ return asink_options;
+}
+
struct audio_filter::implementation
{
std::string filtergraph_;
- std::shared_ptr<AVFilterGraph> audio_graph_;
- AVFilterContext* audio_graph_in_;
- AVFilterContext* audio_graph_out_;
+ std::shared_ptr<AVFilterGraph> audio_graph_;
+ std::vector<AVFilterContext*> audio_graph_inputs_;
+ std::vector<AVFilterContext*> audio_graph_outputs_;
implementation(
- boost::rational<int> in_time_base,
- int in_sample_rate,
- AVSampleFormat in_sample_fmt,
- std::int64_t in_audio_channel_layout,
- std::vector<int> out_sample_rates,
- std::vector<AVSampleFormat> out_sample_fmts,
- std::vector<std::int64_t> out_audio_channel_layouts,
- const std::string& filtergraph)
+ std::vector<audio_input_pad> input_pads,
+ std::vector<audio_output_pad> output_pads,
+ const std::string& filtergraph)
: filtergraph_(boost::to_lower_copy(filtergraph))
{
- 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<std::string> 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<std::string>(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)
- {
- 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)
-
- 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));
+ {
+ int i = 0;
+ for (auto& output_pad : output_pads)
+ complete_filter_graph.push_back(create_sinkfilter_str(output_pad, "aout:" + boost::lexical_cast<std::string>(i++)));
+ }
-#pragma warning (pop)
-
configure_filtergraph(
*audio_graph_,
- filtergraph_,
- *filt_asrc,
- *filt_asink);
-
- audio_graph_in_ = filt_asrc;
- audio_graph_out_ = filt_asink;
+ boost::join(complete_filter_graph, ";"),
+ audio_graph_inputs_,
+ audio_graph_outputs_);
- CASPAR_LOG(info)
- << u16(std::string("\n")
- + avfilter_graph_dump(
- audio_graph_.get(),
+ if (is_logging_quiet_for_thread())
+ CASPAR_LOG(trace)
+ << u16(std::string("\n")
+ + avfilter_graph_dump(
+ audio_graph_.get(),
+ nullptr));
+ else
+ CASPAR_LOG(debug)
+ << u16(std::string("\n")
+ + avfilter_graph_dump(
+ audio_graph_.get(),
nullptr));
}
void configure_filtergraph(
AVFilterGraph& graph,
const std::string& filtergraph,
- AVFilterContext& source_ctx,
- AVFilterContext& sink_ctx)
+ std::vector<AVFilterContext*>& source_contexts,
+ std::vector<AVFilterContext*>& sink_contexts)
{
- AVFilterInOut* outputs = nullptr;
- AVFilterInOut* inputs = nullptr;
-
try
{
- 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
+ AVFilterInOut* outputs = nullptr;
+ AVFilterInOut* inputs = nullptr;
+
+ FF(avfilter_graph_parse2(
+ &graph,
+ filtergraph.c_str(),
+ &inputs,
+ &outputs));
+
+ // Workaround because outputs and inputs are not filled in for some reason
+ for (unsigned i = 0; i < graph.nb_filters; ++i)
{
- 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);
+
+ if (std::string(filter->filter->name) == "abuffersink")
+ sink_contexts.push_back(filter);
}
+ 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);
+
FF(avfilter_graph_config(
&graph,
nullptr));
}
}
- void push(const std::shared_ptr<AVFrame>& src_av_frame)
+ void push(int input_pad_id, const std::shared_ptr<AVFrame>& 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<AVFrame> poll()
+ std::shared_ptr<AVFrame> poll(int output_pad_id)
{
std::shared_ptr<AVFrame> filt_frame(
av_frame_alloc(),
});
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))
};
audio_filter::audio_filter(
- boost::rational<int> in_time_base,
- int in_sample_rate,
- AVSampleFormat in_sample_fmt,
- std::int64_t in_audio_channel_layout,
- std::vector<int> out_sample_rates,
- std::vector<AVSampleFormat> out_sample_fmts,
- std::vector<std::int64_t> out_audio_channel_layouts,
+ std::vector<audio_input_pad> input_pads,
+ std::vector<audio_output_pad> 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<AVFrame>& frame){impl_->push(frame);}
-std::shared_ptr<AVFrame> audio_filter::poll(){return impl_->poll();}
+void audio_filter::push(int input_pad_id, const std::shared_ptr<AVFrame>& frame){impl_->push(input_pad_id, frame);}
+std::shared_ptr<AVFrame> 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<spl::shared_ptr<AVFrame>> audio_filter::poll_all()
+std::vector<spl::shared_ptr<AVFrame>> audio_filter::poll_all(int output_pad_id)
{
std::vector<spl::shared_ptr<AVFrame>> 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;
}