2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Helge Norberg, helge.norberg@svt.se
26 #include <core/frame/audio_channel_layout.h>
28 #include <common/except.h>
29 #include <common/assert.h>
31 #include "producer/filter/audio_filter.h"
32 #include "producer/util/util.h"
36 #include <boost/algorithm/string/split.hpp>
37 #include <boost/format.hpp>
43 #pragma warning (push)
44 #pragma warning (disable : 4244)
48 #include <libavfilter/avfilter.h>
49 #include <libavutil/channel_layout.h>
55 namespace caspar { namespace core {
57 std::wstring generate_pan_filter_str(
58 const audio_channel_layout& input,
59 const audio_channel_layout& output,
60 boost::optional<std::wstring> mix_config)
62 std::wstringstream result;
64 result << L"pan=" << (boost::wformat(L"0x%|1$x|") % ffmpeg::create_channel_layout_bitmask(output.num_channels)).str();
68 if (input.type == output.type && !input.channel_order.empty() && !input.channel_order.empty())
69 { // No config needed because the layouts are of the same type. Generate mix config string.
70 std::vector<std::wstring> mappings;
72 for (auto& input_name : input.channel_order)
73 mappings.push_back(input_name + L"=" + input_name);
75 mix_config = boost::join(mappings, L"|");
78 { // Fallback to passthru c0=c0| c1=c1 | ...
79 for (int i = 0; i < output.num_channels; ++i)
80 result << L"|c" << i << L"=c" << i;
82 CASPAR_LOG(debug) << "[audio_channel_remapper] Passthru " << input.num_channels << " channels into " << output.num_channels;
88 CASPAR_LOG(debug) << L"[audio_channel_remapper] Using mix config: " << *mix_config;
90 // Split on | to find the output sections
91 std::vector<std::wstring> output_sections;
92 boost::split(output_sections, *mix_config, boost::is_any_of(L"|"), boost::algorithm::token_compress_off);
94 for (auto& output_section : output_sections)
96 bool normalize_ratios = boost::contains(output_section, L"<");
97 std::wstring mix_char = normalize_ratios ? L"<" : L"=";
99 // Split on either = or < to get the output name and mix spec
100 std::vector<std::wstring> output_and_spec;
101 boost::split(output_and_spec, output_section, boost::is_any_of(mix_char), boost::algorithm::token_compress_off);
102 auto& mix_spec = output_and_spec.at(1);
104 // Replace each occurance of each channel name with c<index>
105 for (int i = 0; i < input.channel_order.size(); ++i)
106 boost::replace_all(mix_spec, input.channel_order.at(i), L"c" + boost::lexical_cast<std::wstring>(i));
108 auto output_name = boost::trim_copy(output_and_spec.at(0));
109 auto actual_output_indexes = output.indexes_of(output_name);
111 for (auto actual_output_index : actual_output_indexes)
113 result << L"|c" << actual_output_index << L" " << mix_char;
121 struct audio_channel_remapper::impl
123 const audio_channel_layout input_layout_;
124 const audio_channel_layout output_layout_;
125 const bool the_same_layouts_ = input_layout_ == output_layout_;
126 std::unique_ptr<ffmpeg::audio_filter> filter_;
129 audio_channel_layout input_layout,
130 audio_channel_layout output_layout,
131 spl::shared_ptr<audio_mix_config_repository> mix_repo)
132 : input_layout_(std::move(input_layout))
133 , output_layout_(std::move(output_layout))
135 if (input_layout_ == audio_channel_layout::invalid())
136 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Input audio channel layout is invalid"));
138 if (output_layout_ == audio_channel_layout::invalid())
139 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Output audio channel layout is invalid"));
141 CASPAR_LOG(debug) << L"[audio_channel_remapper] Input: " << input_layout_.print();
142 CASPAR_LOG(debug) << L"[audio_channel_remapper] Output: " << output_layout_.print();
144 if (!the_same_layouts_)
146 auto mix_config = mix_repo->get_config(input_layout_.type, output_layout_.type);
147 auto pan_filter = "[a:0] " + u8(generate_pan_filter_str(input_layout_, output_layout_, mix_config)) + " [aout:0]";
149 CASPAR_LOG(debug) << "[audio_channel_remapper] Using audio filter: " << pan_filter;
150 auto quiet_logging = ffmpeg::temporary_enable_quiet_logging_for_thread(true);
151 filter_.reset(new ffmpeg::audio_filter(
152 { ffmpeg::audio_input_pad(boost::rational<int>(1, 1), 48000, AV_SAMPLE_FMT_S32, ffmpeg::create_channel_layout_bitmask(input_layout_.num_channels)) },
153 { ffmpeg::audio_output_pad({ 48000 }, { AV_SAMPLE_FMT_S32 }, { ffmpeg::create_channel_layout_bitmask(output_layout_.num_channels) }) },
157 CASPAR_LOG(debug) << "[audio_channel_remapper] No remapping/mixing needed because the input and output layout is equal.";
160 audio_buffer mix_and_rearrange(audio_buffer input)
162 CASPAR_ENSURE(input.size() % input_layout_.num_channels == 0);
164 if (the_same_layouts_)
165 return std::move(input);
167 auto num_samples = input.size() / input_layout_.num_channels;
168 auto expected_output_size = num_samples * output_layout_.num_channels;
170 filter_->push(0, boost::make_iterator_range(input));
172 auto frames = filter_->poll_all(0);
174 CASPAR_ENSURE(frames.size() == 1); // Expect 1:1 from pan filter
176 auto& frame = frames.front();
177 auto output_size = frame->channels * frame->nb_samples;
179 CASPAR_ENSURE(output_size == expected_output_size);
182 reinterpret_cast<std::int32_t*>(frame->extended_data[0]),
189 audio_channel_remapper::audio_channel_remapper(
190 audio_channel_layout input_layout,
191 audio_channel_layout output_layout,
192 spl::shared_ptr<audio_mix_config_repository> mix_repo)
193 : impl_(new impl(std::move(input_layout), std::move(output_layout), std::move(mix_repo)))
197 audio_buffer audio_channel_remapper::mix_and_rearrange(audio_buffer input)
199 return impl_->mix_and_rearrange(std::move(input));