OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 // MSVC++ requires this to be set before any other includes to get M_SQRT1_2. | |
6 #define _USE_MATH_DEFINES | |
7 | |
8 #include "media/base/channel_mixer.h" | |
9 | |
10 #include <algorithm> | |
11 #include <cmath> | |
12 #include <limits> | |
13 | |
14 #include "base/logging.h" | |
15 #include "media/base/audio_bus.h" | |
16 #include "media/base/vector_math.h" | |
17 | |
18 namespace media { | |
19 | |
20 // Default scale factor for mixing two channels together. We use a different | |
21 // value for stereo -> mono and mono -> stereo mixes. | |
22 static const float kDefaultScale = static_cast<float>(M_SQRT1_2); | |
23 | |
24 static int ValidateLayout(ChannelLayout layout) { | |
25 CHECK_NE(layout, CHANNEL_LAYOUT_NONE); | |
26 CHECK_NE(layout, CHANNEL_LAYOUT_UNSUPPORTED); | |
27 CHECK_NE(layout, CHANNEL_LAYOUT_MAX); | |
28 | |
29 // Verify there's at least one channel. Should always be true here by virtue | |
30 // of not being one of the invalid layouts, but lets double check to be sure. | |
31 int channel_count = ChannelLayoutToChannelCount(layout); | |
32 DCHECK_GT(channel_count, 0); | |
33 | |
34 // If we have more than one channel, verify a symmetric layout for sanity. | |
35 // The unit test will verify all possible layouts, so this can be a DCHECK. | |
36 // Symmetry allows simplifying the matrix building code by allowing us to | |
37 // assume that if one channel of a pair exists, the other will too. | |
38 if (channel_count > 1) { | |
39 DCHECK((ChannelOrder(layout, LEFT) >= 0 && | |
40 ChannelOrder(layout, RIGHT) >= 0) || | |
41 (ChannelOrder(layout, SIDE_LEFT) >= 0 && | |
42 ChannelOrder(layout, SIDE_RIGHT) >= 0) || | |
43 (ChannelOrder(layout, BACK_LEFT) >= 0 && | |
44 ChannelOrder(layout, BACK_RIGHT) >= 0) || | |
45 (ChannelOrder(layout, LEFT_OF_CENTER) >= 0 && | |
46 ChannelOrder(layout, RIGHT_OF_CENTER) >= 0)) | |
47 << "Non-symmetric channel layout encountered."; | |
48 } else { | |
49 DCHECK_EQ(layout, CHANNEL_LAYOUT_MONO); | |
50 } | |
51 | |
52 return channel_count; | |
53 } | |
54 | |
55 ChannelMixer::ChannelMixer(ChannelLayout input, ChannelLayout output) | |
56 : input_layout_(input), | |
57 output_layout_(output), | |
58 just_remap_(false) { | |
59 // Stereo down mix should never be the output layout. | |
60 CHECK_NE(output_layout_, CHANNEL_LAYOUT_STEREO_DOWNMIX); | |
61 | |
62 int input_channels = ValidateLayout(input_layout_); | |
63 int output_channels = ValidateLayout(output_layout_); | |
64 | |
65 // Size out the initial matrix. | |
66 matrix_.reserve(output_channels); | |
67 for (int output_ch = 0; output_ch < output_channels; ++output_ch) | |
68 matrix_.push_back(std::vector<float>(input_channels, 0)); | |
69 | |
70 // Route matching channels and figure out which ones aren't accounted for. | |
71 for (Channels ch = LEFT; ch < CHANNELS_MAX; | |
72 ch = static_cast<Channels>(ch + 1)) { | |
73 int input_ch_index = ChannelOrder(input_layout_, ch); | |
74 int output_ch_index = ChannelOrder(output_layout_, ch); | |
75 | |
76 if (input_ch_index < 0) | |
77 continue; | |
78 | |
79 if (output_ch_index < 0) { | |
80 unaccounted_inputs_.push_back(ch); | |
81 continue; | |
82 } | |
83 | |
84 DCHECK_LT(static_cast<size_t>(output_ch_index), matrix_.size()); | |
85 DCHECK_LT(static_cast<size_t>(input_ch_index), | |
86 matrix_[output_ch_index].size()); | |
87 matrix_[output_ch_index][input_ch_index] = 1; | |
88 } | |
89 | |
90 // If all input channels are accounted for, there's nothing left to do. | |
91 if (unaccounted_inputs_.empty()) { | |
92 // Since all output channels map directly to inputs we can optimize. | |
93 just_remap_ = true; | |
94 return; | |
95 } | |
96 | |
97 // Mix front LR into center. | |
98 if (IsUnaccounted(LEFT)) { | |
99 // When down mixing to mono from stereo, we need to be careful of full scale | |
100 // stereo mixes. Scaling by 1 / sqrt(2) here will likely lead to clipping | |
101 // so we use 1 / 2 instead. | |
102 float scale = (output == CHANNEL_LAYOUT_MONO && input_channels == 2) ? | |
103 0.5 : kDefaultScale; | |
104 Mix(LEFT, CENTER, scale); | |
105 Mix(RIGHT, CENTER, scale); | |
106 } | |
107 | |
108 // Mix center into front LR. | |
109 if (IsUnaccounted(CENTER)) { | |
110 // When up mixing from mono, just do a copy to front LR. | |
111 float scale = (input == CHANNEL_LAYOUT_MONO) ? 1 : kDefaultScale; | |
112 MixWithoutAccounting(CENTER, LEFT, scale); | |
113 Mix(CENTER, RIGHT, scale); | |
114 } | |
115 | |
116 // Mix back LR into: side LR || back center || front LR || front center. | |
117 if (IsUnaccounted(BACK_LEFT)) { | |
118 if (HasOutputChannel(SIDE_LEFT)) { | |
119 // If we have side LR, mix back LR into side LR, but instead if the input | |
120 // doesn't have side LR (but output does) copy back LR to side LR. | |
121 float scale = HasInputChannel(SIDE_LEFT) ? kDefaultScale : 1; | |
122 Mix(BACK_LEFT, SIDE_LEFT, scale); | |
123 Mix(BACK_RIGHT, SIDE_RIGHT, scale); | |
124 } else if (HasOutputChannel(BACK_CENTER)) { | |
125 // Mix back LR into back center. | |
126 Mix(BACK_LEFT, BACK_CENTER, kDefaultScale); | |
127 Mix(BACK_RIGHT, BACK_CENTER, kDefaultScale); | |
128 } else if (output > CHANNEL_LAYOUT_MONO) { | |
129 // Mix back LR into front LR. | |
130 Mix(BACK_LEFT, LEFT, kDefaultScale); | |
131 Mix(BACK_RIGHT, RIGHT, kDefaultScale); | |
132 } else { | |
133 // Mix back LR into front center. | |
134 Mix(BACK_LEFT, CENTER, kDefaultScale); | |
135 Mix(BACK_RIGHT, CENTER, kDefaultScale); | |
136 } | |
137 } | |
138 | |
139 // Mix side LR into: back LR || back center || front LR || front center. | |
140 if (IsUnaccounted(SIDE_LEFT)) { | |
141 if (HasOutputChannel(BACK_LEFT)) { | |
142 // If we have back LR, mix side LR into back LR, but instead if the input | |
143 // doesn't have back LR (but output does) copy side LR to back LR. | |
144 float scale = HasInputChannel(BACK_LEFT) ? kDefaultScale : 1; | |
145 Mix(SIDE_LEFT, BACK_LEFT, scale); | |
146 Mix(SIDE_RIGHT, BACK_RIGHT, scale); | |
147 } else if (HasOutputChannel(BACK_CENTER)) { | |
148 // Mix side LR into back center. | |
149 Mix(SIDE_LEFT, BACK_CENTER, kDefaultScale); | |
150 Mix(SIDE_RIGHT, BACK_CENTER, kDefaultScale); | |
151 } else if (output > CHANNEL_LAYOUT_MONO) { | |
152 // Mix side LR into front LR. | |
153 Mix(SIDE_LEFT, LEFT, kDefaultScale); | |
154 Mix(SIDE_RIGHT, RIGHT, kDefaultScale); | |
155 } else { | |
156 // Mix side LR into front center. | |
157 Mix(SIDE_LEFT, CENTER, kDefaultScale); | |
158 Mix(SIDE_RIGHT, CENTER, kDefaultScale); | |
159 } | |
160 } | |
161 | |
162 // Mix back center into: back LR || side LR || front LR || front center. | |
163 if (IsUnaccounted(BACK_CENTER)) { | |
164 if (HasOutputChannel(BACK_LEFT)) { | |
165 // Mix back center into back LR. | |
166 MixWithoutAccounting(BACK_CENTER, BACK_LEFT, kDefaultScale); | |
167 Mix(BACK_CENTER, BACK_RIGHT, kDefaultScale); | |
168 } else if (HasOutputChannel(SIDE_LEFT)) { | |
169 // Mix back center into side LR. | |
170 MixWithoutAccounting(BACK_CENTER, SIDE_LEFT, kDefaultScale); | |
171 Mix(BACK_CENTER, SIDE_RIGHT, kDefaultScale); | |
172 } else if (output > CHANNEL_LAYOUT_MONO) { | |
173 // Mix back center into front LR. | |
174 // TODO(dalecurtis): Not sure about these values? | |
175 MixWithoutAccounting(BACK_CENTER, LEFT, kDefaultScale * kDefaultScale); | |
176 Mix(BACK_CENTER, RIGHT, kDefaultScale * kDefaultScale); | |
177 } else { | |
178 // Mix back center into front center. | |
179 // TODO(dalecurtis): Not sure about these values? | |
180 Mix(BACK_CENTER, CENTER, kDefaultScale * kDefaultScale); | |
181 } | |
182 } | |
183 | |
184 // Mix LR of center into: front center || front LR. | |
185 if (IsUnaccounted(LEFT_OF_CENTER)) { | |
186 if (HasOutputChannel(CENTER)) { | |
187 // Mix LR of center into front center. | |
188 Mix(LEFT_OF_CENTER, CENTER, kDefaultScale); | |
189 Mix(RIGHT_OF_CENTER, CENTER, kDefaultScale); | |
190 } else { | |
191 // Mix LR of center into front LR. | |
192 Mix(LEFT_OF_CENTER, LEFT, kDefaultScale); | |
193 Mix(RIGHT_OF_CENTER, RIGHT, kDefaultScale); | |
194 } | |
195 } | |
196 | |
197 // Mix LFE into: front LR || front center. | |
198 if (IsUnaccounted(LFE)) { | |
199 if (!HasOutputChannel(CENTER)) { | |
200 // Mix LFE into front LR. | |
201 MixWithoutAccounting(LFE, LEFT, kDefaultScale); | |
202 Mix(LFE, RIGHT, kDefaultScale); | |
203 } else { | |
204 // Mix LFE into front center. | |
205 Mix(LFE, CENTER, kDefaultScale); | |
206 } | |
207 } | |
208 | |
209 // All channels should now be accounted for. | |
210 DCHECK(unaccounted_inputs_.empty()); | |
211 | |
212 // See if the output |matrix_| is simply a remapping matrix. If each input | |
213 // channel maps to a single output channel we can simply remap. Doing this | |
214 // programmatically is less fragile than logic checks on channel mappings. | |
215 for (int output_ch = 0; output_ch < output_channels; ++output_ch) { | |
216 int input_mappings = 0; | |
217 for (int input_ch = 0; input_ch < input_channels; ++input_ch) { | |
218 // We can only remap if each row contains a single scale of 1. | |
scherkus (not reviewing)
2012/10/18 05:39:23
"...or we have more than one input mapped to an ou
DaleCurtis
2012/10/18 06:30:38
That's not correct? If more than one input maps to
| |
219 if (matrix_[output_ch][input_ch] != 1 || ++input_mappings > 1) | |
220 return; | |
221 } | |
222 } | |
223 | |
224 // If we've gotten here, |matrix_| is simply a remapping. | |
225 just_remap_ = true; | |
226 } | |
227 | |
228 ChannelMixer::~ChannelMixer() {} | |
229 | |
230 void ChannelMixer::Rematrix(const AudioBus* input, AudioBus* output) { | |
231 CHECK_EQ(matrix_.size(), static_cast<size_t>(output->channels())); | |
232 CHECK_EQ(matrix_[0].size(), static_cast<size_t>(input->channels())); | |
233 CHECK_EQ(input->frames(), output->frames()); | |
234 | |
235 // If we're just remapping we can simply memcpy the correct input to output. | |
scherkus (not reviewing)
2012/10/18 05:39:23
memcpy() / copy
DaleCurtis
2012/10/18 06:30:38
Done.
| |
236 if (just_remap_) { | |
237 for (int output_ch = 0; output_ch < output->channels(); ++output_ch) { | |
238 for (int input_ch = 0; input_ch < input->channels(); ++input_ch) { | |
239 float scale = matrix_[output_ch][input_ch]; | |
240 if (scale > 0) { | |
241 DCHECK_LT(fabs(scale - 1), std::numeric_limits<float>::epsilon()); | |
scherkus (not reviewing)
2012/10/18 05:39:23
what are the cases when scale is not exactly 1?
DaleCurtis
2012/10/18 06:30:38
None, I'm just wary of float precision. Tests seem
| |
242 memcpy(output->channel(output_ch), input->channel(input_ch), | |
scherkus (not reviewing)
2012/10/18 05:39:23
is |output| zeroed in the first place or should we
DaleCurtis
2012/10/18 06:30:38
Oh, good point, yes output needs to be zeroed.
| |
243 sizeof(*output->channel(output_ch)) * output->frames()); | |
244 break; | |
245 } | |
246 } | |
247 } | |
248 return; | |
249 } | |
250 | |
251 // Zero initialize |output| so we're accumulating from zero. | |
252 output->Zero(); | |
253 for (int output_ch = 0; output_ch < output->channels(); ++output_ch) { | |
254 for (int input_ch = 0; input_ch < input->channels(); ++input_ch) { | |
255 float scale = matrix_[output_ch][input_ch]; | |
256 // Scale should always be positive. Don't bother scaling by zero. | |
257 DCHECK_GE(scale, 0); | |
258 if (scale > std::numeric_limits<float>::epsilon()) { | |
259 vector_math::FMAC(input->channel(input_ch), scale, output->frames(), | |
260 output->channel(output_ch)); | |
261 } | |
262 } | |
263 } | |
264 } | |
265 | |
266 void ChannelMixer::AccountFor(Channels ch) { | |
267 unaccounted_inputs_.erase(std::find( | |
268 unaccounted_inputs_.begin(), unaccounted_inputs_.end(), ch)); | |
269 } | |
270 | |
271 bool ChannelMixer::IsUnaccounted(Channels ch) { | |
272 return std::find(unaccounted_inputs_.begin(), unaccounted_inputs_.end(), | |
273 ch) != unaccounted_inputs_.end(); | |
274 } | |
275 | |
276 bool ChannelMixer::HasInputChannel(Channels ch) { | |
277 return ChannelOrder(input_layout_, ch) >= 0; | |
278 } | |
279 | |
280 bool ChannelMixer::HasOutputChannel(Channels ch) { | |
281 return ChannelOrder(output_layout_, ch) >= 0; | |
282 } | |
283 | |
284 void ChannelMixer::Mix(Channels input_ch, Channels output_ch, float scale) { | |
285 MixWithoutAccounting(input_ch, output_ch, scale); | |
286 AccountFor(input_ch); | |
287 } | |
288 | |
289 void ChannelMixer::MixWithoutAccounting(Channels input_ch, Channels output_ch, | |
290 float scale) { | |
291 int input_ch_index = ChannelOrder(input_layout_, input_ch); | |
292 int output_ch_index = ChannelOrder(output_layout_, output_ch); | |
293 | |
294 DCHECK(IsUnaccounted(input_ch)); | |
295 DCHECK_GE(input_ch_index, 0); | |
296 DCHECK_GE(output_ch_index, 0); | |
297 | |
298 matrix_[output_ch_index][input_ch_index] += scale; | |
299 } | |
300 | |
301 } // namespace media | |
OLD | NEW |