]> git.sesse.net Git - casparcg/blob - modules/ffmpeg/audio_channel_remapper.cpp
920aa7203d17d0baca4e33b10bc60fd43586c3bd
[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                 auto input_frame                        =       ffmpeg::create_frame();
170
171                 input_frame->channels           =       input_layout_.num_channels;
172                 input_frame->channel_layout     =       ffmpeg::create_channel_layout_bitmask(input_layout_.num_channels);
173                 input_frame->sample_rate        =       48000;
174                 input_frame->nb_samples         =       static_cast<int>(num_samples);
175                 input_frame->format                     =       AV_SAMPLE_FMT_S32;
176                 input_frame->pts                        =       0;
177
178                 av_samples_fill_arrays(
179                                 input_frame->extended_data,
180                                 input_frame->linesize,
181                                 reinterpret_cast<const std::uint8_t*>(input.data()),
182                                 input_frame->channels,
183                                 input_frame->nb_samples,
184                                 static_cast<AVSampleFormat>(input_frame->format),
185                                 16);
186
187                 filter_->push(0, input_frame);
188
189                 auto frames = filter_->poll_all(0);
190
191                 CASPAR_ENSURE(frames.size() == 1); // Expect 1:1 from pan filter
192
193                 auto& frame = frames.front();
194                 auto output_size = frame->channels * frame->nb_samples;
195
196                 CASPAR_ENSURE(output_size == expected_output_size);
197
198                 return audio_buffer(
199                                 reinterpret_cast<std::int32_t*>(frame->extended_data[0]),
200                                 output_size,
201                                 true,
202                                 std::move(frame));
203         }
204 };
205
206 audio_channel_remapper::audio_channel_remapper(
207                 audio_channel_layout input_layout,
208                 audio_channel_layout output_layout,
209                 spl::shared_ptr<audio_mix_config_repository> mix_repo)
210         : impl_(new impl(std::move(input_layout), std::move(output_layout), std::move(mix_repo)))
211 {
212 }
213
214 audio_buffer audio_channel_remapper::mix_and_rearrange(audio_buffer input)
215 {
216         return impl_->mix_and_rearrange(std::move(input));
217 }
218
219 }}