]> git.sesse.net Git - casparcg/blob - modules/ffmpeg/audio_channel_remapper.cpp
[general] #598 Removed all usages of asmlib, because it is worse performing than...
[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 <boost/algorithm/string/split.hpp>
35 #include <boost/format.hpp>
36
37 #include <cstdint>
38 #include <sstream>
39
40 #if defined(_MSC_VER)
41 #pragma warning (push)
42 #pragma warning (disable : 4244)
43 #endif
44 extern "C"
45 {
46 #include <libavfilter/avfilter.h>
47 #include <libavutil/channel_layout.h>
48 }
49 #if defined(_MSC_VER)
50 #pragma warning (pop)
51 #endif
52
53 namespace caspar { namespace core {
54
55 std::wstring generate_pan_filter_str(
56                 const audio_channel_layout& input,
57                 const audio_channel_layout& output,
58                 boost::optional<std::wstring> mix_config)
59 {
60         std::wstringstream result;
61
62         result << L"pan=" << (boost::wformat(L"0x%|1$x|") % ffmpeg::create_channel_layout_bitmask(output.num_channels)).str();
63
64         if (!mix_config)
65         {
66                 if (input.type == output.type && !input.channel_order.empty() && !input.channel_order.empty())
67                 {       // No config needed because the layouts are of the same type. Generate mix config string.
68                         std::vector<std::wstring> mappings;
69
70                         for (auto& input_name : input.channel_order)
71                                 mappings.push_back(input_name + L"=" + input_name);
72
73                         mix_config = boost::join(mappings, L"|");
74                 }
75                 else
76                 {       // Fallback to passthru c0=c0| c1=c1 | ...
77                         for (int i = 0; i < output.num_channels; ++i)
78                                 result << L"|c" << i << L"=c" << i;
79
80                         CASPAR_LOG(debug) << "[audio_channel_remapper] Passthru " << input.num_channels << " channels into " << output.num_channels;
81
82                         return result.str();
83                 }
84         }
85
86         CASPAR_LOG(debug) << L"[audio_channel_remapper] Using mix config: " << *mix_config;
87
88         // Split on | to find the output sections
89         std::vector<std::wstring> output_sections;
90         boost::split(output_sections, *mix_config, boost::is_any_of(L"|"), boost::algorithm::token_compress_off);
91
92         for (auto& output_section : output_sections)
93         {
94                 bool normalize_ratios = boost::contains(output_section, L"<");
95                 std::wstring mix_char = normalize_ratios ? L"<" : L"=";
96
97                 // Split on either = or < to get the output name and mix spec
98                 std::vector<std::wstring> output_and_spec;
99                 boost::split(output_and_spec, output_section, boost::is_any_of(mix_char), boost::algorithm::token_compress_off);
100                 auto& mix_spec = output_and_spec.at(1);
101
102                 // Replace each occurance of each channel name with c<index>
103                 for (int i = 0; i < input.channel_order.size(); ++i)
104                         boost::replace_all(mix_spec, input.channel_order.at(i), L"c" + boost::lexical_cast<std::wstring>(i));
105
106                 auto output_name = boost::trim_copy(output_and_spec.at(0));
107                 auto actual_output_indexes = output.indexes_of(output_name);
108
109                 for (auto actual_output_index : actual_output_indexes)
110                 {
111                         result << L"|c" << actual_output_index << L" " << mix_char;
112                         result << mix_spec;
113                 }
114         }
115
116         return result.str();
117 }
118
119 struct audio_channel_remapper::impl
120 {
121         const audio_channel_layout                              input_layout_;
122         const audio_channel_layout                              output_layout_;
123         const bool                                                              the_same_layouts_       = input_layout_ == output_layout_;
124         std::unique_ptr<ffmpeg::audio_filter>   filter_;
125
126         impl(
127                         audio_channel_layout input_layout,
128                         audio_channel_layout output_layout,
129                         spl::shared_ptr<audio_mix_config_repository> mix_repo)
130                 : input_layout_(std::move(input_layout))
131                 , output_layout_(std::move(output_layout))
132         {
133                 if (input_layout_ == audio_channel_layout::invalid())
134                         CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Input audio channel layout is invalid"));
135
136                 if (output_layout_ == audio_channel_layout::invalid())
137                         CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Output audio channel layout is invalid"));
138
139                 CASPAR_LOG(debug) << L"[audio_channel_remapper] Input:  " << input_layout_.print();
140                 CASPAR_LOG(debug) << L"[audio_channel_remapper] Output: " << output_layout_.print();
141
142                 if (!the_same_layouts_)
143                 {
144                         auto mix_config = mix_repo->get_config(input_layout_.type, output_layout_.type);
145                         auto pan_filter = "[a:0] " + u8(generate_pan_filter_str(input_layout_, output_layout_, mix_config)) + " [aout:0]";
146
147                         CASPAR_LOG(debug) << "[audio_channel_remapper] Using audio filter: " << pan_filter;
148                         auto quiet_logging = ffmpeg::temporary_enable_quiet_logging_for_thread(true);
149                         filter_.reset(new ffmpeg::audio_filter(
150                                         { ffmpeg::audio_input_pad(boost::rational<int>(1, 1), 48000, AV_SAMPLE_FMT_S32, ffmpeg::create_channel_layout_bitmask(input_layout_.num_channels)) },
151                                         { ffmpeg::audio_output_pad({ 48000 }, { AV_SAMPLE_FMT_S32 }, { ffmpeg::create_channel_layout_bitmask(output_layout_.num_channels) }) },
152                                         pan_filter));
153                 }
154                 else
155                         CASPAR_LOG(debug) << "[audio_channel_remapper] No remapping/mixing needed because the input and output layout is equal.";
156         }
157
158         audio_buffer mix_and_rearrange(audio_buffer input)
159         {
160                 CASPAR_ENSURE(input.size() % input_layout_.num_channels == 0);
161
162                 if (the_same_layouts_)
163                         return std::move(input);
164
165                 auto num_samples                        =       input.size() / input_layout_.num_channels;
166                 auto expected_output_size       =       num_samples * output_layout_.num_channels;
167
168                 filter_->push(0, boost::make_iterator_range(input));
169
170                 auto frames = filter_->poll_all(0);
171
172                 CASPAR_ENSURE(frames.size() == 1); // Expect 1:1 from pan filter
173
174                 auto& frame = frames.front();
175                 auto output_size = frame->channels * frame->nb_samples;
176
177                 CASPAR_ENSURE(output_size == expected_output_size);
178
179                 return audio_buffer(
180                                 reinterpret_cast<std::int32_t*>(frame->extended_data[0]),
181                                 output_size,
182                                 true,
183                                 std::move(frame));
184         }
185 };
186
187 audio_channel_remapper::audio_channel_remapper(
188                 audio_channel_layout input_layout,
189                 audio_channel_layout output_layout,
190                 spl::shared_ptr<audio_mix_config_repository> mix_repo)
191         : impl_(new impl(std::move(input_layout), std::move(output_layout), std::move(mix_repo)))
192 {
193 }
194
195 audio_buffer audio_channel_remapper::mix_and_rearrange(audio_buffer input)
196 {
197         return impl_->mix_and_rearrange(std::move(input));
198 }
199
200 }}