mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 865234 - Part 8: Add a test case for channel mixing rules; r=roc
This commit is contained in:
parent
28722fca86
commit
40c7bffd01
@ -38,6 +38,7 @@ MOCHITEST_FILES := \
|
||||
test_decodeAudioData.html \
|
||||
test_dynamicsCompressorNode.html \
|
||||
test_gainNode.html \
|
||||
test_mixingRules.html \
|
||||
test_pannerNode.html \
|
||||
test_scriptProcessorNode.html \
|
||||
test_singleSourceDest.html \
|
||||
|
403
content/media/webaudio/test/test_mixingRules.html
Normal file
403
content/media/webaudio/test/test_mixingRules.html
Normal file
@ -0,0 +1,403 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Testcase for AudioNode channel up-mix/down-mix rules</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="webaudio.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
|
||||
// This test is based on http://src.chromium.org/viewvc/blink/trunk/LayoutTests/webaudio/audionode-channel-rules.html
|
||||
|
||||
var context = null;
|
||||
var sp = null;
|
||||
var renderNumberOfChannels = 8;
|
||||
var singleTestFrameLength = 8;
|
||||
var testBuffers;
|
||||
|
||||
// A list of connections to an AudioNode input, each of which is to be used in one or more specific test cases.
|
||||
// Each element in the list is a string, with the number of connections corresponding to the length of the string,
|
||||
// and each character in the string is from '1' to '8' representing a 1 to 8 channel connection (from an AudioNode output).
|
||||
// For example, the string "128" means 3 connections, having 1, 2, and 8 channels respectively.
|
||||
var connectionsList = [];
|
||||
for (var i = 1; i <= 8; ++i) {
|
||||
connectionsList.push(i.toString());
|
||||
for (var j = 1; j <= 8; ++j) {
|
||||
connectionsList.push(i.toString() + j.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// A list of mixing rules, each of which will be tested against all of the connections in connectionsList.
|
||||
var mixingRulesList = [
|
||||
{channelCount: 1, channelCountMode: "max", channelInterpretation: "speakers"},
|
||||
{channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
|
||||
{channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
|
||||
{channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
|
||||
{channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
|
||||
{channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
|
||||
{channelCount: 7, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
|
||||
{channelCount: 2, channelCountMode: "explicit", channelInterpretation: "speakers"},
|
||||
{channelCount: 3, channelCountMode: "explicit", channelInterpretation: "speakers"},
|
||||
{channelCount: 4, channelCountMode: "explicit", channelInterpretation: "speakers"},
|
||||
{channelCount: 5, channelCountMode: "explicit", channelInterpretation: "speakers"},
|
||||
{channelCount: 6, channelCountMode: "explicit", channelInterpretation: "speakers"},
|
||||
{channelCount: 7, channelCountMode: "explicit", channelInterpretation: "speakers"},
|
||||
{channelCount: 8, channelCountMode: "explicit", channelInterpretation: "speakers"},
|
||||
{channelCount: 1, channelCountMode: "max", channelInterpretation: "discrete"},
|
||||
{channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
|
||||
{channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
|
||||
{channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
|
||||
{channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
|
||||
{channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
|
||||
{channelCount: 3, channelCountMode: "explicit", channelInterpretation: "discrete"},
|
||||
{channelCount: 4, channelCountMode: "explicit", channelInterpretation: "discrete"},
|
||||
{channelCount: 5, channelCountMode: "explicit", channelInterpretation: "discrete"},
|
||||
{channelCount: 6, channelCountMode: "explicit", channelInterpretation: "discrete"},
|
||||
{channelCount: 7, channelCountMode: "explicit", channelInterpretation: "discrete"},
|
||||
{channelCount: 8, channelCountMode: "explicit", channelInterpretation: "discrete"},
|
||||
];
|
||||
|
||||
var numberOfTests = mixingRulesList.length * connectionsList.length;
|
||||
|
||||
// Create an n-channel buffer, with all sample data zero except for a shifted impulse.
|
||||
// The impulse position depends on the channel index.
|
||||
// For example, for a 4-channel buffer:
|
||||
// channel0: 1 0 0 0 0 0 0 0
|
||||
// channel1: 0 1 0 0 0 0 0 0
|
||||
// channel2: 0 0 1 0 0 0 0 0
|
||||
// channel3: 0 0 0 1 0 0 0 0
|
||||
function createTestBuffer(numberOfChannels) {
|
||||
var buffer = context.createBuffer(numberOfChannels, singleTestFrameLength, context.sampleRate);
|
||||
for (var i = 0; i < numberOfChannels; ++i) {
|
||||
var data = buffer.getChannelData(i);
|
||||
data[i] = 1;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Discrete channel interpretation mixing:
|
||||
// https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix
|
||||
// up-mix by filling channels until they run out then ignore remaining dest channels.
|
||||
// down-mix by filling as many channels as possible, then dropping remaining source channels.
|
||||
function discreteSum(sourceBuffer, destBuffer) {
|
||||
if (sourceBuffer.length != destBuffer.length) {
|
||||
is(sourceBuffer.length, destBuffer.length, "source and destination buffers should have the same length");
|
||||
}
|
||||
|
||||
var numberOfChannels = Math.min(sourceBuffer.numberOfChannels, destBuffer.numberOfChannels);
|
||||
var length = sourceBuffer.length;
|
||||
|
||||
for (var c = 0; c < numberOfChannels; ++c) {
|
||||
var source = sourceBuffer.getChannelData(c);
|
||||
var dest = destBuffer.getChannelData(c);
|
||||
for (var i = 0; i < length; ++i) {
|
||||
dest[i] += source[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Speaker channel interpretation mixing:
|
||||
// https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix
|
||||
function speakersSum(sourceBuffer, destBuffer)
|
||||
{
|
||||
var numberOfSourceChannels = sourceBuffer.numberOfChannels;
|
||||
var numberOfDestinationChannels = destBuffer.numberOfChannels;
|
||||
var length = destBuffer.length;
|
||||
|
||||
if ((numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) ||
|
||||
(numberOfDestinationChannels == 4 && numberOfSourceChannels == 1)) {
|
||||
// Handle mono -> stereo/Quad case (summing mono channel into both left and right).
|
||||
var source = sourceBuffer.getChannelData(0);
|
||||
var destL = destBuffer.getChannelData(0);
|
||||
var destR = destBuffer.getChannelData(1);
|
||||
|
||||
for (var i = 0; i < length; ++i) {
|
||||
destL[i] += source[i];
|
||||
destR[i] += source[i];
|
||||
}
|
||||
} else if ((numberOfDestinationChannels == 4 && numberOfSourceChannels == 2) ||
|
||||
(numberOfDestinationChannels == 6 && numberOfSourceChannels == 2)) {
|
||||
// Handle stereo -> Quad/5.1 case (summing left and right channels into the output's left and right).
|
||||
var sourceL = sourceBuffer.getChannelData(0);
|
||||
var sourceR = sourceBuffer.getChannelData(1);
|
||||
var destL = destBuffer.getChannelData(0);
|
||||
var destR = destBuffer.getChannelData(1);
|
||||
|
||||
for (var i = 0; i < length; ++i) {
|
||||
destL[i] += sourceL[i];
|
||||
destR[i] += sourceR[i];
|
||||
}
|
||||
} else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 2) {
|
||||
// Handle stereo -> mono case. output += 0.5 * (input.L + input.R).
|
||||
var sourceL = sourceBuffer.getChannelData(0);
|
||||
var sourceR = sourceBuffer.getChannelData(1);
|
||||
var dest = destBuffer.getChannelData(0);
|
||||
|
||||
for (var i = 0; i < length; ++i) {
|
||||
dest[i] += 0.5 * (sourceL[i] + sourceR[i]);
|
||||
}
|
||||
} else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 4) {
|
||||
// Handle Quad -> mono case. output += 0.25 * (input.L + input.R + input.SL + input.SR).
|
||||
var sourceL = sourceBuffer.getChannelData(0);
|
||||
var sourceR = sourceBuffer.getChannelData(1);
|
||||
var sourceSL = sourceBuffer.getChannelData(2);
|
||||
var sourceSR = sourceBuffer.getChannelData(3);
|
||||
var dest = destBuffer.getChannelData(0);
|
||||
|
||||
for (var i = 0; i < length; ++i) {
|
||||
dest[i] += 0.25 * (sourceL[i] + sourceR[i] + sourceSL[i] + sourceSR[i]);
|
||||
}
|
||||
} else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 4) {
|
||||
// Handle Quad -> stereo case. outputLeft += 0.5 * (input.L + input.SL),
|
||||
// outputRight += 0.5 * (input.R + input.SR).
|
||||
var sourceL = sourceBuffer.getChannelData(0);
|
||||
var sourceR = sourceBuffer.getChannelData(1);
|
||||
var sourceSL = sourceBuffer.getChannelData(2);
|
||||
var sourceSR = sourceBuffer.getChannelData(3);
|
||||
var destL = destBuffer.getChannelData(0);
|
||||
var destR = destBuffer.getChannelData(1);
|
||||
|
||||
for (var i = 0; i < length; ++i) {
|
||||
destL[i] += 0.5 * (sourceL[i] + sourceSL[i]);
|
||||
destR[i] += 0.5 * (sourceR[i] + sourceSR[i]);
|
||||
}
|
||||
} else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 4) {
|
||||
// Handle Quad -> 5.1 case. outputLeft += (inputL, inputR, 0, 0, inputSL, inputSR)
|
||||
var sourceL = sourceBuffer.getChannelData(0);
|
||||
var sourceR = sourceBuffer.getChannelData(1);
|
||||
var sourceSL = sourceBuffer.getChannelData(2);
|
||||
var sourceSR = sourceBuffer.getChannelData(3);
|
||||
var destL = destBuffer.getChannelData(0);
|
||||
var destR = destBuffer.getChannelData(1);
|
||||
var destSL = destBuffer.getChannelData(4);
|
||||
var destSR = destBuffer.getChannelData(5);
|
||||
|
||||
for (var i = 0; i < length; ++i) {
|
||||
destL[i] += sourceL[i];
|
||||
destR[i] += sourceR[i];
|
||||
destSL[i] += sourceSL[i];
|
||||
destSR[i] += sourceSR[i];
|
||||
}
|
||||
} else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 1) {
|
||||
// Handle mono -> 5.1 case, sum mono channel into center.
|
||||
var source = sourceBuffer.getChannelData(0);
|
||||
var dest = destBuffer.getChannelData(2);
|
||||
|
||||
for (var i = 0; i < length; ++i) {
|
||||
dest[i] += source[i];
|
||||
}
|
||||
} else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) {
|
||||
// Handle 5.1 -> mono.
|
||||
var sourceL = sourceBuffer.getChannelData(0);
|
||||
var sourceR = sourceBuffer.getChannelData(1);
|
||||
var sourceC = sourceBuffer.getChannelData(2);
|
||||
// skip LFE for now, according to current spec.
|
||||
var sourceSL = sourceBuffer.getChannelData(4);
|
||||
var sourceSR = sourceBuffer.getChannelData(5);
|
||||
var dest = destBuffer.getChannelData(0);
|
||||
|
||||
for (var i = 0; i < length; ++i) {
|
||||
dest[i] += 0.7071 * (sourceL[i] + sourceR[i]) + sourceC[i] + 0.5 * (sourceSL[i] + sourceSR[i]);
|
||||
}
|
||||
} else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 6) {
|
||||
// Handle 5.1 -> stereo.
|
||||
var sourceL = sourceBuffer.getChannelData(0);
|
||||
var sourceR = sourceBuffer.getChannelData(1);
|
||||
var sourceC = sourceBuffer.getChannelData(2);
|
||||
// skip LFE for now, according to current spec.
|
||||
var sourceSL = sourceBuffer.getChannelData(4);
|
||||
var sourceSR = sourceBuffer.getChannelData(5);
|
||||
var destL = destBuffer.getChannelData(0);
|
||||
var destR = destBuffer.getChannelData(1);
|
||||
|
||||
for (var i = 0; i < length; ++i) {
|
||||
destL[i] += sourceL[i] + 0.7071 * (sourceC[i] + sourceSL[i]);
|
||||
destR[i] += sourceR[i] + 0.7071 * (sourceC[i] + sourceSR[i]);
|
||||
}
|
||||
} else if (numberOfDestinationChannels == 4 && numberOfSourceChannels == 6) {
|
||||
// Handle 5.1 -> Quad.
|
||||
var sourceL = sourceBuffer.getChannelData(0);
|
||||
var sourceR = sourceBuffer.getChannelData(1);
|
||||
var sourceC = sourceBuffer.getChannelData(2);
|
||||
// skip LFE for now, according to current spec.
|
||||
var sourceSL = sourceBuffer.getChannelData(4);
|
||||
var sourceSR = sourceBuffer.getChannelData(5);
|
||||
var destL = destBuffer.getChannelData(0);
|
||||
var destR = destBuffer.getChannelData(1);
|
||||
var destSL = destBuffer.getChannelData(2);
|
||||
var destSR = destBuffer.getChannelData(3);
|
||||
|
||||
for (var i = 0; i < length; ++i) {
|
||||
destL[i] += sourceL[i] + 0.7071 * sourceC[i];
|
||||
destR[i] += sourceR[i] + 0.7071 * sourceC[i];
|
||||
destSL[i] += sourceSL[i];
|
||||
destSR[i] += sourceSR[i];
|
||||
}
|
||||
} else {
|
||||
// Fallback for unknown combinations.
|
||||
discreteSum(sourceBuffer, destBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleTest(testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
|
||||
var mixNode = context.createGain();
|
||||
mixNode.channelCount = channelCount;
|
||||
mixNode.channelCountMode = channelCountMode;
|
||||
mixNode.channelInterpretation = channelInterpretation;
|
||||
mixNode.connect(sp);
|
||||
|
||||
for (var i = 0; i < connections.length; ++i) {
|
||||
var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
|
||||
|
||||
var source = context.createBufferSource();
|
||||
// Get a buffer with the right number of channels, converting from 1-based to 0-based index.
|
||||
var buffer = testBuffers[connectionNumberOfChannels - 1];
|
||||
source.buffer = buffer;
|
||||
source.connect(mixNode);
|
||||
|
||||
// Start at the right offset.
|
||||
var sampleFrameOffset = testNumber * singleTestFrameLength;
|
||||
var time = sampleFrameOffset / context.sampleRate;
|
||||
source.start(time);
|
||||
}
|
||||
}
|
||||
|
||||
function computeNumberOfChannels(connections, channelCount, channelCountMode) {
|
||||
if (channelCountMode == "explicit")
|
||||
return channelCount;
|
||||
|
||||
var computedNumberOfChannels = 1; // Must have at least one channel.
|
||||
|
||||
// Compute "computedNumberOfChannels" based on all the connections.
|
||||
for (var i = 0; i < connections.length; ++i) {
|
||||
var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
|
||||
computedNumberOfChannels = Math.max(computedNumberOfChannels, connectionNumberOfChannels);
|
||||
}
|
||||
|
||||
if (channelCountMode == "clamped-max")
|
||||
computedNumberOfChannels = Math.min(computedNumberOfChannels, channelCount);
|
||||
|
||||
return computedNumberOfChannels;
|
||||
}
|
||||
|
||||
function checkTestResult(renderedBuffer, testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
|
||||
var computedNumberOfChannels = computeNumberOfChannels(connections, channelCount, channelCountMode);
|
||||
|
||||
// Create a zero-initialized silent AudioBuffer with computedNumberOfChannels.
|
||||
var destBuffer = context.createBuffer(computedNumberOfChannels, singleTestFrameLength, context.sampleRate);
|
||||
|
||||
// Mix all of the connections into the destination buffer.
|
||||
for (var i = 0; i < connections.length; ++i) {
|
||||
var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
|
||||
var sourceBuffer = testBuffers[connectionNumberOfChannels - 1]; // convert from 1-based to 0-based index
|
||||
|
||||
if (channelInterpretation == "speakers") {
|
||||
speakersSum(sourceBuffer, destBuffer);
|
||||
} else if (channelInterpretation == "discrete") {
|
||||
discreteSum(sourceBuffer, destBuffer);
|
||||
} else {
|
||||
ok(false, "Invalid channel interpretation!");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that destBuffer matches the rendered output.
|
||||
// We need to check the rendered output at a specific sample-frame-offset corresponding
|
||||
// to the specific test case we're checking for based on testNumber.
|
||||
|
||||
var sampleFrameOffset = testNumber * singleTestFrameLength;
|
||||
for (var c = 0; c < renderNumberOfChannels; ++c) {
|
||||
var renderedData = renderedBuffer.getChannelData(c);
|
||||
for (var frame = 0; frame < singleTestFrameLength; ++frame) {
|
||||
var renderedValue = renderedData[frame + sampleFrameOffset];
|
||||
|
||||
var expectedValue = 0;
|
||||
if (c < destBuffer.numberOfChannels) {
|
||||
var expectedData = destBuffer.getChannelData(c);
|
||||
expectedValue = expectedData[frame];
|
||||
}
|
||||
|
||||
if (Math.abs(renderedValue - expectedValue) > 1e-4) {
|
||||
var s = "connections: " + connections + ", " + channelCountMode;
|
||||
|
||||
// channelCount is ignored in "max" mode.
|
||||
if (channelCountMode == "clamped-max" || channelCountMode == "explicit") {
|
||||
s += "(" + channelCount + ")";
|
||||
}
|
||||
|
||||
s += ", " + channelInterpretation + ". ";
|
||||
|
||||
var message = s + "rendered: " + renderedValue + " expected: " + expectedValue + " channel: " + c + " frame: " + frame;
|
||||
is(renderedValue, expectedValue, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkResult(event) {
|
||||
var buffer = event.inputBuffer;
|
||||
|
||||
// Sanity check result.
|
||||
ok(buffer.length != numberOfTests * singleTestFrameLength ||
|
||||
buffer.numberOfChannels != renderNumberOfChannels, "Sanity check");
|
||||
|
||||
// Check all the tests.
|
||||
var testNumber = 0;
|
||||
for (var m = 0; m < mixingRulesList.length; ++m) {
|
||||
var mixingRules = mixingRulesList[m];
|
||||
for (var i = 0; i < connectionsList.length; ++i, ++testNumber) {
|
||||
checkTestResult(buffer, testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation);
|
||||
}
|
||||
}
|
||||
|
||||
sp.onaudioprocess = null;
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
function runTest() {
|
||||
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
|
||||
|
||||
// Create 8-channel offline audio context.
|
||||
// Each test will render 8 sample-frames starting at sample-frame position testNumber * 8.
|
||||
var totalFrameLength = numberOfTests * singleTestFrameLength;
|
||||
context = new AudioContext();
|
||||
var nextPowerOfTwo = 256;
|
||||
while (nextPowerOfTwo < totalFrameLength) {
|
||||
nextPowerOfTwo *= 2;
|
||||
}
|
||||
sp = context.createScriptProcessor(nextPowerOfTwo, renderNumberOfChannels);
|
||||
|
||||
// Set destination to discrete mixing.
|
||||
sp.channelCount = renderNumberOfChannels;
|
||||
sp.channelCountMode = "explicit";
|
||||
sp.channelInterpretation = "discrete";
|
||||
|
||||
// Create test buffers from 1 to 8 channels.
|
||||
testBuffers = new Array();
|
||||
for (var i = 0; i < renderNumberOfChannels; ++i) {
|
||||
testBuffers[i] = createTestBuffer(i + 1);
|
||||
}
|
||||
|
||||
// Schedule all the tests.
|
||||
var testNumber = 0;
|
||||
for (var m = 0; m < mixingRulesList.length; ++m) {
|
||||
var mixingRules = mixingRulesList[m];
|
||||
for (var i = 0; i < connectionsList.length; ++i, ++testNumber) {
|
||||
scheduleTest(testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation);
|
||||
}
|
||||
}
|
||||
|
||||
// Render then check results.
|
||||
sp.onaudioprocess = checkResult;
|
||||
}
|
||||
|
||||
runTest();
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user