]> git.sesse.net Git - casparcg/blob - modules/ffmpeg/audio_channel_remapper.cpp
[scene] Include LiberationSans-Regular into distribution for use by text producer.
[casparcg] / modules / ffmpeg / audio_channel_remapper.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
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.
10 *
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.
15 *
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/>.
18 *
19 * Author: Helge Norberg, helge.norberg@svt.se
20 */
21
22 #include "StdAfx.h"
23
24 #include "ffmpeg.h"
25
26 #include <core/frame/audio_channel_layout.h>
27
28 #include <common/except.h>
29 #include <common/assert.h>
30
31 #include "producer/filter/audio_filter.h"
32 #include "producer/util/util.h"
33
34 #include <asmlib.h>
35
36 #include <boost/algorithm/string/split.hpp>
37 #include <boost/format.hpp>
38
39 #include <cstdint>
40 #include <sstream>
41
42 #if defined(_MSC_VER)
43 #pragma warning (push)
44 #pragma warning (disable : 4244)
45 #endif
46 extern "C"
47 {
48 #include <libavfilter/avfilter.h>
49 #include <libavutil/channel_layout.h>
50 }
51 #if defined(_MSC_VER)
52 #pragma warning (pop)
53 #endif
54
55 namespace caspar { namespace core {
56
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)
61 {
62         std::wstringstream result;
63
64         result << L"pan=" << (boost::wformat(L"0x%|1$x|") % ffmpeg::create_channel_layout_bitmask(output.num_channels)).str();
65
66         if (!mix_config)
67         {
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;
71
72                         for (auto& input_name : input.channel_order)
73                                 mappings.push_back(input_name + L"=" + input_name);
74
75                         mix_config = boost::join(mappings, L"|");
76                 }
77                 else
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;
81
82                         CASPAR_LOG(debug) << "[audio_channel_remapper] Passthru " << input.num_channels << " channels into " << output.num_channels;
83
84                         return result.str();
85                 }
86         }
87
88         CASPAR_LOG(debug) << L"[audio_channel_remapper] Using mix config: " << *mix_config;
89
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);
93
94         for (auto& output_section : output_sections)
95         {
96                 bool normalize_ratios = boost::contains(output_section, L"<");
97                 std::wstring mix_char = normalize_ratios ? L"<" : L"=";
98
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);
103
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));
107
108                 auto output_name = boost::trim_copy(output_and_spec.at(0));
109                 auto actual_output_indexes = output.indexes_of(output_name);
110
111                 for (auto actual_output_index : actual_output_indexes)
112                 {
113                         result << L"|c" << actual_output_index << L" " << mix_char;
114                         result << mix_spec;
115                 }
116         }
117
118         return result.str();
119 }
120
121 struct audio_channel_remapper::impl
122 {
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_;
127
128         impl(
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))
134         {
135                 if (input_layout_ == audio_channel_layout::invalid())
136                         CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Input audio channel layout is invalid"));
137
138                 if (output_layout_ == audio_channel_layout::invalid())
139                         CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Output audio channel layout is invalid"));
140
141                 CASPAR_LOG(debug) << L"[audio_channel_remapper] Input:  " << input_layout_.print();
142                 CASPAR_LOG(debug) << L"[audio_channel_remapper] Output: " << output_layout_.print();
143
144                 if (!the_same_layouts_)
145                 {
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]";
148
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) }) },
154                                         pan_filter));
155                 }
156                 else
157                         CASPAR_LOG(debug) << "[audio_channel_remapper] No remapping/mixing needed because the input and output layout is equal.";
158         }
159
160         audio_buffer mix_and_rearrange(audio_buffer input)
161         {
162                 CASPAR_ENSURE(input.size() % input_layout_.num_channels == 0);
163
164                 if (the_same_layouts_)
165                         return std::move(input);
166
167                 auto num_samples                        =       input.size() / input_layout_.num_channels;
168                 auto expected_output_size       =       num_samples * output_layout_.num_channels;
169
170                 filter_->push(0, boost::make_iterator_range(input));
171
172                 auto frames = filter_->poll_all(0);
173
174                 CASPAR_ENSURE(frames.size() == 1); // Expect 1:1 from pan filter
175
176                 auto& frame = frames.front();
177                 auto output_size = frame->channels * frame->nb_samples;
178
179                 CASPAR_ENSURE(output_size == expected_output_size);
180
181                 return audio_buffer(
182                                 reinterpret_cast<std::int32_t*>(frame->extended_data[0]),
183                                 output_size,
184                                 true,
185                                 std::move(frame));
186         }
187 };
188
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)))
194 {
195 }
196
197 audio_buffer audio_channel_remapper::mix_and_rearrange(audio_buffer input)
198 {
199         return impl_->mix_and_rearrange(std::move(input));
200 }
201
202 }}