]> git.sesse.net Git - casparcg/blob - modules/ffmpeg/audio_channel_remapper.cpp
* Reenabled unit-test project after move to CMake.
[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 <core/frame/audio_channel_layout.h>
25
26 #include <common/except.h>
27 #include <common/assert.h>
28
29 #include "producer/filter/audio_filter.h"
30 #include "producer/util/util.h"
31
32 #include <asmlib.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 = u8(generate_pan_filter_str(input_layout_, output_layout_, mix_config));
146
147                         CASPAR_LOG(debug) << "[audio_channel_remapper] Using audio filter: " << pan_filter;
148                         filter_.reset(new ffmpeg::audio_filter(
149                                         boost::rational<int>(1, 1),
150                                         48000,
151                                         AV_SAMPLE_FMT_S32,
152                                         ffmpeg::create_channel_layout_bitmask(input_layout_.num_channels),
153                                         { 48000 },
154                                         { AV_SAMPLE_FMT_S32 },
155                                         { ffmpeg::create_channel_layout_bitmask(output_layout_.num_channels) },
156                                         pan_filter));
157                 }
158                 else
159                         CASPAR_LOG(debug) << "[audio_channel_remapper] No remapping/mixing needed because the input and output layout is equal.";
160         }
161
162         audio_buffer mix_and_rearrange(audio_buffer input)
163         {
164                 CASPAR_ENSURE(input.size() % input_layout_.num_channels == 0);
165
166                 if (the_same_layouts_)
167                         return std::move(input);
168
169                 auto num_samples                        =       input.size() / input_layout_.num_channels;
170                 auto expected_output_size       =       num_samples * output_layout_.num_channels;
171                 auto input_frame                        =       std::shared_ptr<AVFrame>(av_frame_alloc(), [](AVFrame* p)
172                                                                                 {
173                                                                                         if (p)
174                                                                                                 av_frame_free(&p);
175                                                                                 });
176
177                 input_frame->channels           =       input_layout_.num_channels;
178                 input_frame->channel_layout     =       ffmpeg::create_channel_layout_bitmask(input_layout_.num_channels);
179                 input_frame->sample_rate        =       48000;
180                 input_frame->nb_samples         =       static_cast<int>(num_samples);
181                 input_frame->format                     =       AV_SAMPLE_FMT_S32;
182                 input_frame->pts                        =       0;
183
184                 av_samples_fill_arrays(
185                                 input_frame->extended_data,
186                                 input_frame->linesize,
187                                 reinterpret_cast<const std::uint8_t*>(input.data()),
188                                 input_frame->channels,
189                                 input_frame->nb_samples,
190                                 static_cast<AVSampleFormat>(input_frame->format),
191                                 16);
192
193                 filter_->push(input_frame);
194
195                 auto frames = filter_->poll_all();
196
197                 CASPAR_ENSURE(frames.size() == 1); // Expect 1:1 from pan filter
198
199                 auto& frame = frames.front();
200                 auto output_size = frame->channels * frame->nb_samples;
201
202                 CASPAR_ENSURE(output_size == expected_output_size);
203
204                 return audio_buffer(
205                                 reinterpret_cast<std::int32_t*>(frame->extended_data[0]),
206                                 output_size,
207                                 true,
208                                 std::move(frame));
209         }
210 };
211
212 audio_channel_remapper::audio_channel_remapper(
213                 audio_channel_layout input_layout,
214                 audio_channel_layout output_layout,
215                 spl::shared_ptr<audio_mix_config_repository> mix_repo)
216         : impl_(new impl(std::move(input_layout), std::move(output_layout), std::move(mix_repo)))
217 {
218 }
219
220 audio_buffer audio_channel_remapper::mix_and_rearrange(audio_buffer input)
221 {
222         return impl_->mix_and_rearrange(std::move(input));
223 }
224
225 }}