content/media/webaudio/test/test_mixingRules.html

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:b0c4982d82ee
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Testcase for AudioNode channel up-mix/down-mix rules</title>
5 <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
6 <script type="text/javascript" src="webaudio.js"></script>
7 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
8 </head>
9
10 <body>
11
12 <script>
13
14 // This test is based on http://src.chromium.org/viewvc/blink/trunk/LayoutTests/webaudio/audionode-channel-rules.html
15
16 var context = null;
17 var sp = null;
18 var renderNumberOfChannels = 8;
19 var singleTestFrameLength = 8;
20 var testBuffers;
21
22 // A list of connections to an AudioNode input, each of which is to be used in one or more specific test cases.
23 // Each element in the list is a string, with the number of connections corresponding to the length of the string,
24 // and each character in the string is from '1' to '8' representing a 1 to 8 channel connection (from an AudioNode output).
25 // For example, the string "128" means 3 connections, having 1, 2, and 8 channels respectively.
26 var connectionsList = [];
27 for (var i = 1; i <= 8; ++i) {
28 connectionsList.push(i.toString());
29 for (var j = 1; j <= 8; ++j) {
30 connectionsList.push(i.toString() + j.toString());
31 }
32 }
33
34 // A list of mixing rules, each of which will be tested against all of the connections in connectionsList.
35 var mixingRulesList = [
36 {channelCount: 1, channelCountMode: "max", channelInterpretation: "speakers"},
37 {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
38 {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
39 {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
40 {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
41 {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
42 {channelCount: 7, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
43 {channelCount: 2, channelCountMode: "explicit", channelInterpretation: "speakers"},
44 {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "speakers"},
45 {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "speakers"},
46 {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "speakers"},
47 {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "speakers"},
48 {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "speakers"},
49 {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "speakers"},
50 {channelCount: 1, channelCountMode: "max", channelInterpretation: "discrete"},
51 {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
52 {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
53 {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
54 {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
55 {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
56 {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "discrete"},
57 {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "discrete"},
58 {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "discrete"},
59 {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "discrete"},
60 {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "discrete"},
61 {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "discrete"},
62 ];
63
64 var numberOfTests = mixingRulesList.length * connectionsList.length;
65
66 // Create an n-channel buffer, with all sample data zero except for a shifted impulse.
67 // The impulse position depends on the channel index.
68 // For example, for a 4-channel buffer:
69 // channel0: 1 0 0 0 0 0 0 0
70 // channel1: 0 1 0 0 0 0 0 0
71 // channel2: 0 0 1 0 0 0 0 0
72 // channel3: 0 0 0 1 0 0 0 0
73 function createTestBuffer(numberOfChannels) {
74 var buffer = context.createBuffer(numberOfChannels, singleTestFrameLength, context.sampleRate);
75 for (var i = 0; i < numberOfChannels; ++i) {
76 var data = buffer.getChannelData(i);
77 data[i] = 1;
78 }
79 return buffer;
80 }
81
82 // Discrete channel interpretation mixing:
83 // https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix
84 // up-mix by filling channels until they run out then ignore remaining dest channels.
85 // down-mix by filling as many channels as possible, then dropping remaining source channels.
86 function discreteSum(sourceBuffer, destBuffer) {
87 if (sourceBuffer.length != destBuffer.length) {
88 is(sourceBuffer.length, destBuffer.length, "source and destination buffers should have the same length");
89 }
90
91 var numberOfChannels = Math.min(sourceBuffer.numberOfChannels, destBuffer.numberOfChannels);
92 var length = sourceBuffer.length;
93
94 for (var c = 0; c < numberOfChannels; ++c) {
95 var source = sourceBuffer.getChannelData(c);
96 var dest = destBuffer.getChannelData(c);
97 for (var i = 0; i < length; ++i) {
98 dest[i] += source[i];
99 }
100 }
101 }
102
103 // Speaker channel interpretation mixing:
104 // https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix
105 function speakersSum(sourceBuffer, destBuffer)
106 {
107 var numberOfSourceChannels = sourceBuffer.numberOfChannels;
108 var numberOfDestinationChannels = destBuffer.numberOfChannels;
109 var length = destBuffer.length;
110
111 if ((numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) ||
112 (numberOfDestinationChannels == 4 && numberOfSourceChannels == 1)) {
113 // Handle mono -> stereo/Quad case (summing mono channel into both left and right).
114 var source = sourceBuffer.getChannelData(0);
115 var destL = destBuffer.getChannelData(0);
116 var destR = destBuffer.getChannelData(1);
117
118 for (var i = 0; i < length; ++i) {
119 destL[i] += source[i];
120 destR[i] += source[i];
121 }
122 } else if ((numberOfDestinationChannels == 4 && numberOfSourceChannels == 2) ||
123 (numberOfDestinationChannels == 6 && numberOfSourceChannels == 2)) {
124 // Handle stereo -> Quad/5.1 case (summing left and right channels into the output's left and right).
125 var sourceL = sourceBuffer.getChannelData(0);
126 var sourceR = sourceBuffer.getChannelData(1);
127 var destL = destBuffer.getChannelData(0);
128 var destR = destBuffer.getChannelData(1);
129
130 for (var i = 0; i < length; ++i) {
131 destL[i] += sourceL[i];
132 destR[i] += sourceR[i];
133 }
134 } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 2) {
135 // Handle stereo -> mono case. output += 0.5 * (input.L + input.R).
136 var sourceL = sourceBuffer.getChannelData(0);
137 var sourceR = sourceBuffer.getChannelData(1);
138 var dest = destBuffer.getChannelData(0);
139
140 for (var i = 0; i < length; ++i) {
141 dest[i] += 0.5 * (sourceL[i] + sourceR[i]);
142 }
143 } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 4) {
144 // Handle Quad -> mono case. output += 0.25 * (input.L + input.R + input.SL + input.SR).
145 var sourceL = sourceBuffer.getChannelData(0);
146 var sourceR = sourceBuffer.getChannelData(1);
147 var sourceSL = sourceBuffer.getChannelData(2);
148 var sourceSR = sourceBuffer.getChannelData(3);
149 var dest = destBuffer.getChannelData(0);
150
151 for (var i = 0; i < length; ++i) {
152 dest[i] += 0.25 * (sourceL[i] + sourceR[i] + sourceSL[i] + sourceSR[i]);
153 }
154 } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 4) {
155 // Handle Quad -> stereo case. outputLeft += 0.5 * (input.L + input.SL),
156 // outputRight += 0.5 * (input.R + input.SR).
157 var sourceL = sourceBuffer.getChannelData(0);
158 var sourceR = sourceBuffer.getChannelData(1);
159 var sourceSL = sourceBuffer.getChannelData(2);
160 var sourceSR = sourceBuffer.getChannelData(3);
161 var destL = destBuffer.getChannelData(0);
162 var destR = destBuffer.getChannelData(1);
163
164 for (var i = 0; i < length; ++i) {
165 destL[i] += 0.5 * (sourceL[i] + sourceSL[i]);
166 destR[i] += 0.5 * (sourceR[i] + sourceSR[i]);
167 }
168 } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 4) {
169 // Handle Quad -> 5.1 case. outputLeft += (inputL, inputR, 0, 0, inputSL, inputSR)
170 var sourceL = sourceBuffer.getChannelData(0);
171 var sourceR = sourceBuffer.getChannelData(1);
172 var sourceSL = sourceBuffer.getChannelData(2);
173 var sourceSR = sourceBuffer.getChannelData(3);
174 var destL = destBuffer.getChannelData(0);
175 var destR = destBuffer.getChannelData(1);
176 var destSL = destBuffer.getChannelData(4);
177 var destSR = destBuffer.getChannelData(5);
178
179 for (var i = 0; i < length; ++i) {
180 destL[i] += sourceL[i];
181 destR[i] += sourceR[i];
182 destSL[i] += sourceSL[i];
183 destSR[i] += sourceSR[i];
184 }
185 } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 1) {
186 // Handle mono -> 5.1 case, sum mono channel into center.
187 var source = sourceBuffer.getChannelData(0);
188 var dest = destBuffer.getChannelData(2);
189
190 for (var i = 0; i < length; ++i) {
191 dest[i] += source[i];
192 }
193 } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) {
194 // Handle 5.1 -> mono.
195 var sourceL = sourceBuffer.getChannelData(0);
196 var sourceR = sourceBuffer.getChannelData(1);
197 var sourceC = sourceBuffer.getChannelData(2);
198 // skip LFE for now, according to current spec.
199 var sourceSL = sourceBuffer.getChannelData(4);
200 var sourceSR = sourceBuffer.getChannelData(5);
201 var dest = destBuffer.getChannelData(0);
202
203 for (var i = 0; i < length; ++i) {
204 dest[i] += 0.7071 * (sourceL[i] + sourceR[i]) + sourceC[i] + 0.5 * (sourceSL[i] + sourceSR[i]);
205 }
206 } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 6) {
207 // Handle 5.1 -> stereo.
208 var sourceL = sourceBuffer.getChannelData(0);
209 var sourceR = sourceBuffer.getChannelData(1);
210 var sourceC = sourceBuffer.getChannelData(2);
211 // skip LFE for now, according to current spec.
212 var sourceSL = sourceBuffer.getChannelData(4);
213 var sourceSR = sourceBuffer.getChannelData(5);
214 var destL = destBuffer.getChannelData(0);
215 var destR = destBuffer.getChannelData(1);
216
217 for (var i = 0; i < length; ++i) {
218 destL[i] += sourceL[i] + 0.7071 * (sourceC[i] + sourceSL[i]);
219 destR[i] += sourceR[i] + 0.7071 * (sourceC[i] + sourceSR[i]);
220 }
221 } else if (numberOfDestinationChannels == 4 && numberOfSourceChannels == 6) {
222 // Handle 5.1 -> Quad.
223 var sourceL = sourceBuffer.getChannelData(0);
224 var sourceR = sourceBuffer.getChannelData(1);
225 var sourceC = sourceBuffer.getChannelData(2);
226 // skip LFE for now, according to current spec.
227 var sourceSL = sourceBuffer.getChannelData(4);
228 var sourceSR = sourceBuffer.getChannelData(5);
229 var destL = destBuffer.getChannelData(0);
230 var destR = destBuffer.getChannelData(1);
231 var destSL = destBuffer.getChannelData(2);
232 var destSR = destBuffer.getChannelData(3);
233
234 for (var i = 0; i < length; ++i) {
235 destL[i] += sourceL[i] + 0.7071 * sourceC[i];
236 destR[i] += sourceR[i] + 0.7071 * sourceC[i];
237 destSL[i] += sourceSL[i];
238 destSR[i] += sourceSR[i];
239 }
240 } else {
241 // Fallback for unknown combinations.
242 discreteSum(sourceBuffer, destBuffer);
243 }
244 }
245
246 function scheduleTest(testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
247 var mixNode = context.createGain();
248 mixNode.channelCount = channelCount;
249 mixNode.channelCountMode = channelCountMode;
250 mixNode.channelInterpretation = channelInterpretation;
251 mixNode.connect(sp);
252
253 for (var i = 0; i < connections.length; ++i) {
254 var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
255
256 var source = context.createBufferSource();
257 // Get a buffer with the right number of channels, converting from 1-based to 0-based index.
258 var buffer = testBuffers[connectionNumberOfChannels - 1];
259 source.buffer = buffer;
260 source.connect(mixNode);
261
262 // Start at the right offset.
263 var sampleFrameOffset = testNumber * singleTestFrameLength;
264 var time = sampleFrameOffset / context.sampleRate;
265 source.start(time);
266 }
267 }
268
269 function computeNumberOfChannels(connections, channelCount, channelCountMode) {
270 if (channelCountMode == "explicit")
271 return channelCount;
272
273 var computedNumberOfChannels = 1; // Must have at least one channel.
274
275 // Compute "computedNumberOfChannels" based on all the connections.
276 for (var i = 0; i < connections.length; ++i) {
277 var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
278 computedNumberOfChannels = Math.max(computedNumberOfChannels, connectionNumberOfChannels);
279 }
280
281 if (channelCountMode == "clamped-max")
282 computedNumberOfChannels = Math.min(computedNumberOfChannels, channelCount);
283
284 return computedNumberOfChannels;
285 }
286
287 function checkTestResult(renderedBuffer, testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
288 var computedNumberOfChannels = computeNumberOfChannels(connections, channelCount, channelCountMode);
289
290 // Create a zero-initialized silent AudioBuffer with computedNumberOfChannels.
291 var destBuffer = context.createBuffer(computedNumberOfChannels, singleTestFrameLength, context.sampleRate);
292
293 // Mix all of the connections into the destination buffer.
294 for (var i = 0; i < connections.length; ++i) {
295 var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
296 var sourceBuffer = testBuffers[connectionNumberOfChannels - 1]; // convert from 1-based to 0-based index
297
298 if (channelInterpretation == "speakers") {
299 speakersSum(sourceBuffer, destBuffer);
300 } else if (channelInterpretation == "discrete") {
301 discreteSum(sourceBuffer, destBuffer);
302 } else {
303 ok(false, "Invalid channel interpretation!");
304 }
305 }
306
307 // Validate that destBuffer matches the rendered output.
308 // We need to check the rendered output at a specific sample-frame-offset corresponding
309 // to the specific test case we're checking for based on testNumber.
310
311 var sampleFrameOffset = testNumber * singleTestFrameLength;
312 for (var c = 0; c < renderNumberOfChannels; ++c) {
313 var renderedData = renderedBuffer.getChannelData(c);
314 for (var frame = 0; frame < singleTestFrameLength; ++frame) {
315 var renderedValue = renderedData[frame + sampleFrameOffset];
316
317 var expectedValue = 0;
318 if (c < destBuffer.numberOfChannels) {
319 var expectedData = destBuffer.getChannelData(c);
320 expectedValue = expectedData[frame];
321 }
322
323 if (Math.abs(renderedValue - expectedValue) > 1e-4) {
324 var s = "connections: " + connections + ", " + channelCountMode;
325
326 // channelCount is ignored in "max" mode.
327 if (channelCountMode == "clamped-max" || channelCountMode == "explicit") {
328 s += "(" + channelCount + ")";
329 }
330
331 s += ", " + channelInterpretation + ". ";
332
333 var message = s + "rendered: " + renderedValue + " expected: " + expectedValue + " channel: " + c + " frame: " + frame;
334 is(renderedValue, expectedValue, message);
335 }
336 }
337 }
338 }
339
340 function checkResult(event) {
341 var buffer = event.inputBuffer;
342
343 // Sanity check result.
344 ok(buffer.length != numberOfTests * singleTestFrameLength ||
345 buffer.numberOfChannels != renderNumberOfChannels, "Sanity check");
346
347 // Check all the tests.
348 var testNumber = 0;
349 for (var m = 0; m < mixingRulesList.length; ++m) {
350 var mixingRules = mixingRulesList[m];
351 for (var i = 0; i < connectionsList.length; ++i, ++testNumber) {
352 checkTestResult(buffer, testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation);
353 }
354 }
355
356 sp.onaudioprocess = null;
357 SimpleTest.finish();
358 }
359
360 SimpleTest.waitForExplicitFinish();
361 function runTest() {
362 // Create 8-channel offline audio context.
363 // Each test will render 8 sample-frames starting at sample-frame position testNumber * 8.
364 var totalFrameLength = numberOfTests * singleTestFrameLength;
365 context = new AudioContext();
366 var nextPowerOfTwo = 256;
367 while (nextPowerOfTwo < totalFrameLength) {
368 nextPowerOfTwo *= 2;
369 }
370 sp = context.createScriptProcessor(nextPowerOfTwo, renderNumberOfChannels);
371
372 // Set destination to discrete mixing.
373 sp.channelCount = renderNumberOfChannels;
374 sp.channelCountMode = "explicit";
375 sp.channelInterpretation = "discrete";
376
377 // Create test buffers from 1 to 8 channels.
378 testBuffers = new Array();
379 for (var i = 0; i < renderNumberOfChannels; ++i) {
380 testBuffers[i] = createTestBuffer(i + 1);
381 }
382
383 // Schedule all the tests.
384 var testNumber = 0;
385 for (var m = 0; m < mixingRulesList.length; ++m) {
386 var mixingRules = mixingRulesList[m];
387 for (var i = 0; i < connectionsList.length; ++i, ++testNumber) {
388 scheduleTest(testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation);
389 }
390 }
391
392 // Render then check results.
393 sp.onaudioprocess = checkResult;
394 }
395
396 runTest();
397
398 </script>
399
400 </body>
401 </html>

mercurial