]> git.sesse.net Git - casparcg/blob - core/mixer/audio/audio_util.cpp
Added support for more than 2 audio channels
[casparcg] / core / mixer / audio / audio_util.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 "audio_util.h"
25
26 #include <boost/algorithm/string/split.hpp>
27 #include <boost/algorithm/string.hpp>
28 #include <boost/thread/mutex.hpp>
29 #include <boost/foreach.hpp>
30 #include <boost/assign.hpp>
31 #include <boost/lexical_cast.hpp>
32 #include <boost/property_tree/exceptions.hpp>
33
34 namespace caspar { namespace core {
35
36 bool channel_layout::operator==(const channel_layout& other) const
37 {
38         return channel_names == other.channel_names
39                         && num_channels == other.num_channels;
40 }
41
42 int channel_layout::channel_index(const std::wstring& channel_name) const
43 {
44         auto iter =
45                         std::find(channel_names.begin(), channel_names.end(), channel_name);
46
47         if (iter == channel_names.end())
48                 return -1;
49
50         return iter - channel_names.begin();
51 }
52
53 bool channel_layout::has_channel(const std::wstring& channel_name) const
54 {
55         return channel_index(channel_name) != -1;
56 }
57
58 bool channel_layout::no_channel_names() const
59 {
60         return channel_names.empty();
61 }
62
63 bool needs_rearranging(
64                 const channel_layout& source, const channel_layout& destination)
65 {
66         if ((source.no_channel_names() || destination.no_channel_names())
67                         && source.num_channels == destination.num_channels)
68                 return false;
69
70         return !(source == destination);
71 }
72
73 struct channel_layout_repository::impl
74 {
75         std::map<std::wstring, const channel_layout> layouts;
76         boost::mutex mutex;
77 };
78
79 channel_layout_repository::channel_layout_repository()
80         : impl_(new impl)
81 {
82 }
83
84 channel_layout_repository::~channel_layout_repository()
85 {
86 }
87
88 void channel_layout_repository::register_layout(const channel_layout& layout)
89 {
90         boost::unique_lock<boost::mutex> lock(impl_->mutex);
91
92         impl_->layouts.erase(layout.name);
93         impl_->layouts.insert(std::make_pair(layout.name, layout));
94 }
95
96 const channel_layout& channel_layout_repository::get_by_name(
97                 const std::wstring& layout_name) const
98 {
99         boost::unique_lock<boost::mutex> lock(impl_->mutex);
100
101         auto iter = impl_->layouts.find(layout_name);
102
103         if (iter == impl_->layouts.end())
104                 BOOST_THROW_EXCEPTION(invalid_argument()
105                                 << msg_info(narrow(layout_name) + " not found"));
106
107         return iter->second;
108 }
109
110 channel_layout create_layout_from_string(
111                 const std::wstring& name,
112                 const std::wstring& layout_type,
113                 int num_channels,
114                 const std::wstring& channels)
115 {
116         channel_layout layout;
117
118         layout.name = boost::to_upper_copy(name);
119         layout.layout_type = boost::to_upper_copy(layout_type);
120         auto upper_channels = boost::to_upper_copy(channels);
121         
122         if (channels.length() > 0)
123                 boost::split(
124                                 layout.channel_names,
125                                 upper_channels,
126                                 boost::is_any_of(L"\t "),
127                                 boost::token_compress_on);
128
129         layout.num_channels = num_channels == -1
130                         ? layout.channel_names.size() : num_channels;
131
132         return layout;
133 }
134
135 channel_layout create_unspecified_layout(int num_channels)
136 {
137         channel_layout layout;
138
139         layout.name = L"UNORDERED" + boost::lexical_cast<std::wstring>(
140                         num_channels) + L"CH";
141         layout.layout_type = L"UNORDERED";
142         layout.num_channels = num_channels;
143
144         return layout;
145 }
146
147 void register_default_channel_layouts(channel_layout_repository& repository)
148 {
149         repository.register_layout(create_layout_from_string(
150                         L"mono",         L"1.0",           1, L"C"));
151         repository.register_layout(create_layout_from_string(
152                         L"stereo",       L"2.0",           2, L"L R"));
153         repository.register_layout(create_layout_from_string(
154                         L"dts",          L"5.1",           6, L"C L R Ls Rs LFE"));
155         repository.register_layout(create_layout_from_string(
156                         L"dolbye",       L"5.1+stereomix", 8, L"L R C LFE Ls Rs Lmix Rmix"
157         ));
158         repository.register_layout(create_layout_from_string(
159                         L"dolbydigital", L"5.1",           6, L"L C R Ls Rs LFE"));
160         repository.register_layout(create_layout_from_string(
161                         L"smpte",        L"5.1",           6, L"L R C LFE Ls Rs"));
162         repository.register_layout(create_layout_from_string(
163                         L"passthru",     L"16ch",         16, L""));
164 }
165
166 void parse_channel_layouts(
167                 channel_layout_repository& repository,
168                 const boost::property_tree::wptree& layouts_element)
169 {
170         BOOST_FOREACH(auto& layout, layouts_element)
171         {
172                 repository.register_layout(create_layout_from_string(
173                                 layout.first,
174                                 layout.second.get<std::wstring>(L"type"),
175                                 layout.second.get<int>(L"num-channels"),
176                                 layout.second.get<std::wstring>(L"channels")));
177         }
178 }
179
180 channel_layout_repository& default_channel_layout_repository()
181 {
182         static channel_layout_repository repository;
183
184         return repository;
185 }
186
187 struct mix_config_repository::impl
188 {
189         std::map<std::wstring, std::map<std::wstring, const mix_config>> configs;
190         boost::mutex mutex;
191 };
192
193 mix_config_repository::mix_config_repository()
194         : impl_(new impl)
195 {
196 }
197
198 mix_config_repository::~mix_config_repository()
199 {
200 }
201
202 void mix_config_repository::register_mix_config(const mix_config& config)
203 {
204         boost::unique_lock<boost::mutex> lock(impl_->mutex);
205
206         impl_->configs[config.from_layout_type].erase(config.to_layout_type);
207         impl_->configs[config.from_layout_type].insert(
208                         std::make_pair(config.to_layout_type, config));
209 }
210
211 boost::optional<mix_config> mix_config_repository::get_mix_config(
212                 const std::wstring& from_layout_type,
213                 const std::wstring& to_layout_type) const
214 {
215         boost::unique_lock<boost::mutex> lock(impl_->mutex);
216
217         auto iter = impl_->configs[from_layout_type].find(to_layout_type);
218
219         if (iter == impl_->configs[from_layout_type].end())
220                 return boost::optional<mix_config>();
221
222         return iter->second;
223 }
224
225 mix_config create_mix_config_from_string(
226                 const std::wstring& from_layout_type,
227                 const std::wstring& to_layout_type,
228                 mix_config::mix_strategy strategy,
229                 const std::vector<std::wstring>& mappings)
230 {
231         mix_config config;
232         config.from_layout_type = boost::to_upper_copy(from_layout_type);
233         config.to_layout_type = boost::to_upper_copy(to_layout_type);
234         config.strategy = strategy;
235
236         BOOST_FOREACH(auto& mapping, mappings)
237         {
238                 auto upper_mapping = boost::to_upper_copy(mapping);
239                 std::vector<std::wstring> words;
240                 boost::split(
241                                 words,
242                                 upper_mapping,
243                                 boost::is_any_of(L"\t "),
244                                 boost::token_compress_on);
245
246                 if (words.size() != 3)
247                         BOOST_THROW_EXCEPTION(invalid_argument() << msg_info(
248                                         "mix_config mapping string must have 3 tokens"));
249
250                 auto from = words.at(0);
251                 auto to = words.at(1);
252                 auto influence = boost::lexical_cast<double>(words.at(2));
253
254                 config.destination_ch_by_source_ch.insert(std::make_pair(
255                                 from,
256                                 mix_config::destination(to, influence)));
257         }
258
259         return config;
260 }
261
262 void register_default_mix_configs(mix_config_repository& repository)
263 {
264         using namespace boost::assign;
265
266         // From 1.0
267         repository.register_mix_config(create_mix_config_from_string(
268                         L"1.0", L"2.0", mix_config::add, list_of
269                                         (L"C L 1.0")
270                                         (L"C R 1.0")
271         ));
272         repository.register_mix_config(create_mix_config_from_string(
273                         L"1.0", L"5.1", mix_config::add, list_of
274                                         (L"C L 1.0")
275                                         (L"C R 1.0")
276         ));
277         repository.register_mix_config(create_mix_config_from_string(
278                         L"1.0", L"5.1+stereomix", mix_config::add, list_of
279                                         (L"C L    1.0")
280                                         (L"C R    1.0")
281                                         (L"C Lmix 1.0")
282                                         (L"C Rmix 1.0")
283         ));
284         // From 2.0
285         repository.register_mix_config(create_mix_config_from_string(
286                         L"2.0", L"1.0", mix_config::add, list_of
287                                         (L"L C 1.0")
288                                         (L"R C 1.0")
289         ));
290         repository.register_mix_config(create_mix_config_from_string(
291                         L"2.0", L"5.1", mix_config::add, list_of
292                                         (L"L L    1.0")
293                                         (L"R R    1.0")
294         ));
295         repository.register_mix_config(create_mix_config_from_string(
296                         L"2.0", L"5.1+stereomix", mix_config::add, list_of
297                                         (L"L L    1.0")
298                                         (L"R R    1.0")
299                                         (L"L Lmix 1.0")
300                                         (L"R Rmix 1.0")
301         ));
302         // From 5.1
303         repository.register_mix_config(create_mix_config_from_string(
304                         L"5.1", L"1.0", mix_config::average, list_of
305                                         (L"L  C 1.0")
306                                         (L"R  C 1.0")
307                                         (L"C  C 0.707")
308                                         (L"Ls C 0.707")
309                                         (L"Rs C 0.707")
310         ));
311         repository.register_mix_config(create_mix_config_from_string(
312                         L"5.1", L"2.0", mix_config::average, list_of
313                                         (L"L  L 1.0")
314                                         (L"R  R 1.0")
315                                         (L"C  L 0.707")
316                                         (L"C  R 0.707")
317                                         (L"Ls L 0.707")
318                                         (L"Rs R 0.707")
319         ));
320         repository.register_mix_config(create_mix_config_from_string(
321                         L"5.1", L"5.1+stereomix", mix_config::average, list_of
322                                         (L"L   L   1.0")
323                                         (L"R   R   1.0")
324                                         (L"C   C   1.0")
325                                         (L"Ls  Ls  1.0")
326                                         (L"Rs  Rs  1.0")
327                                         (L"LFE LFE 1.0")
328
329                                         (L"L  Lmix 1.0")
330                                         (L"R  Rmix 1.0")
331                                         (L"C  Lmix 0.707")
332                                         (L"C  Rmix 0.707")
333                                         (L"Ls Lmix 0.707")
334                                         (L"Rs Rmix 0.707")
335         ));
336         // From 5.1+stereomix
337         repository.register_mix_config(create_mix_config_from_string(
338                         L"5.1+stereomix", L"1.0", mix_config::add, list_of
339                                         (L"Lmix C 1.0")
340                                         (L"Rmix C 1.0")
341         ));
342         repository.register_mix_config(create_mix_config_from_string(
343                         L"5.1+stereomix", L"2.0", mix_config::add, list_of
344                                         (L"Lmix L 1.0")
345                                         (L"Rmix R 1.0")
346         ));
347         repository.register_mix_config(create_mix_config_from_string(
348                         L"5.1+stereomix", L"5.1", mix_config::add, list_of
349                                         (L"L   L   1.0")
350                                         (L"R   R   1.0")
351                                         (L"C   C   1.0")
352                                         (L"Ls  Ls  1.0")
353                                         (L"Rs  Rs  1.0")
354                                         (L"LFE LFE 1.0")
355         ));
356 }
357
358 void parse_mix_configs(
359                 mix_config_repository& repository, 
360                 const boost::property_tree::wptree& channel_mixings_element)
361 {
362         BOOST_FOREACH(auto element, channel_mixings_element)
363         {
364                 if (element.first != L"mix-config")
365                 {
366                         throw boost::property_tree::ptree_error(
367                                 "Expected mix-config element");
368                 }
369
370                 std::vector<std::wstring> mappings;
371
372                 boost::transform(
373                                 element.second.get_child(L"mappings"),
374                                 std::insert_iterator<std::vector<std::wstring>>(
375                                                 mappings, mappings.begin()),
376                                 [](
377                                                 const std::pair<std::wstring,
378                                                 boost::property_tree::wptree>& mapping)
379                                 {
380                                         return mapping.second.get_value<std::wstring>();
381                                 });
382
383                 repository.register_mix_config(create_mix_config_from_string(
384                                 element.second.get<std::wstring>(L"from"),
385                                 element.second.get<std::wstring>(L"to"),
386                                 boost::to_upper_copy(element.second.get<std::wstring>(
387                                                 L"mix", L"ADD")) == L"AVERAGE"
388                                                                 ? mix_config::average : mix_config::add,
389                                 mappings));
390         }
391 }
392
393 mix_config_repository& default_mix_config_repository()
394 {
395         static mix_config_repository repository;
396
397         return repository;
398 }
399
400 channel_layout create_custom_channel_layout(
401                 const std::wstring& custom_channel_order,
402                 const channel_layout_repository& repository)
403 {
404         std::vector<std::wstring> splitted;
405         boost::split(
406                         splitted,
407                         custom_channel_order,
408                         boost::is_any_of(L":"),
409                         boost::token_compress_on);
410
411         if (splitted.size() == 1) // Named layout
412         {
413                 try
414                 {
415                         return repository.get_by_name(splitted[0]);
416                 }
417                 catch (const std::exception&)
418                 {
419                         CASPAR_LOG_CURRENT_EXCEPTION();
420                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(
421                                         "CHANNEL_LAYOUT must be in a format like: "
422                                         "\"5.1:L R C LFE Ls Rs\" or like \"SMPTE\""));
423                 }
424         }
425
426         if (splitted.size() != 2)
427                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(
428                                 "CHANNEL_LAYOUT must be in a format like: "
429                                 "\"5.1:L R C LFE Ls Rs\" or like \"SMPTE\""));
430
431         // Custom layout
432         return create_layout_from_string(
433                         custom_channel_order,
434                         splitted[0],
435                         -1,
436                         splitted[1]);
437 }
438
439
440 }}