#include "../../ffmpeg_error.h"
#include "../../ffmpeg.h"
+#include "../util/util.h"
#include <common/assert.h>
#include <common/except.h>
#include <cstdio>
#include <sstream>
#include <string>
+#include <algorithm>
#if defined(_MSC_VER)
#pragma warning (push)
#pragma warning (disable : 4244)
#endif
-extern "C"
+extern "C"
{
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#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<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_;
+
+ std::vector<audio_input_pad> input_pads_;
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,
+ std::vector<audio_input_pad> input_pads,
+ std::vector<audio_output_pad> 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<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)
+ {
+ 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<std::string>(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);
-
- audio_graph_in_ = filt_asrc;
- audio_graph_out_ = filt_asink;
-
- if (!is_logging_disabled_for_thread())
- CASPAR_LOG(info)
- << u16(std::string("\n")
+ boost::join(complete_filter_graph, ";"),
+ audio_graph_inputs_,
+ audio_graph_outputs_,
+ output_pads);
+
+ if (is_logging_quiet_for_thread())
+ CASPAR_LOG(trace)
+ << u16(std::string("\n")
+ avfilter_graph_dump(
- audio_graph_.get(),
+ 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,
+ const std::vector<audio_output_pad>& output_pads)
{
- AVFilterInOut* outputs = nullptr;
- AVFilterInOut* inputs = nullptr;
+ AVFilterInOut* outputs = nullptr;
+ AVFilterInOut* inputs = nullptr;
- try
+ 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)
{
- 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<AVFrame>& 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<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()
+ void push(int input_pad_id, const boost::iterator_range<const int32_t*>& frame_samples)
{
- std::shared_ptr<AVFrame> 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<int>(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<const std::uint8_t*>(frame_samples.begin()),
+ input_frame->channels,
+ input_frame->nb_samples,
+ static_cast<AVSampleFormat>(input_frame->format),
+ 16);
+
+ push(input_pad_id, input_frame);
+ }
+
+ std::shared_ptr<AVFrame> 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<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::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<AVFrame>& frame){impl_->push(input_pad_id, frame);}
+void audio_filter::push(int input_pad_id, const boost::iterator_range<const int32_t*>& frame_samples) { impl_->push(input_pad_id, frame_samples); }
+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()
-{
+int audio_filter::get_num_output_pads() const { return static_cast<int>(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<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;
}