Add multichannel support for input of JavaScriptAudioNode
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 1 May 2012 01:50:35 +0000 (01:50 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 1 May 2012 01:50:35 +0000 (01:50 +0000)
https://bugs.webkit.org/show_bug.cgi?id=84687

Patch by Xingnan Wang <xingnan.wang@intel.com> on 2012-04-30
Reviewed by Chris Rogers.

Source/WebCore:

Tests: webaudio/javascriptaudionode-downmix8-2channel-input.html
       webaudio/javascriptaudionode-upmix2-8channel-input.html

* Modules/webaudio/JavaScriptAudioNode.cpp:
(WebCore::JavaScriptAudioNode::create):
(WebCore::JavaScriptAudioNode::JavaScriptAudioNode):
(WebCore::JavaScriptAudioNode::initialize):
(WebCore::JavaScriptAudioNode::process):
* Modules/webaudio/JavaScriptAudioNode.h:
(JavaScriptAudioNode):

LayoutTests:

* webaudio/javascriptaudionode-downmix8-2channel-input-expected.txt: Added.
* webaudio/javascriptaudionode-downmix8-2channel-input.html: Added.
* webaudio/javascriptaudionode-upmix2-8channel-input-expected.txt: Added.
* webaudio/javascriptaudionode-upmix2-8channel-input.html: Added.
* webaudio/resources/javascriptaudionode-testing.js: Added.
(createBuffer):
(processAudioData):
(fillData):
(checkStereoOnlyData):
(runJSNodeTest):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@115699 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/webaudio/javascriptaudionode-downmix8-2channel-input-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/javascriptaudionode-downmix8-2channel-input.html [new file with mode: 0644]
LayoutTests/webaudio/javascriptaudionode-upmix2-8channel-input-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/javascriptaudionode-upmix2-8channel-input.html [new file with mode: 0644]
LayoutTests/webaudio/resources/javascriptaudionode-testing.js [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/webaudio/JavaScriptAudioNode.cpp
Source/WebCore/Modules/webaudio/JavaScriptAudioNode.h

index f8599a9..a88cf79 100644 (file)
@@ -1,3 +1,21 @@
+2012-04-30  Xingnan Wang  <xingnan.wang@intel.com>
+
+        Add multichannel support for input of JavaScriptAudioNode
+        https://bugs.webkit.org/show_bug.cgi?id=84687
+
+        Reviewed by Chris Rogers.
+
+        * webaudio/javascriptaudionode-downmix8-2channel-input-expected.txt: Added.
+        * webaudio/javascriptaudionode-downmix8-2channel-input.html: Added.
+        * webaudio/javascriptaudionode-upmix2-8channel-input-expected.txt: Added.
+        * webaudio/javascriptaudionode-upmix2-8channel-input.html: Added.
+        * webaudio/resources/javascriptaudionode-testing.js: Added.
+        (createBuffer):
+        (processAudioData):
+        (fillData):
+        (checkStereoOnlyData):
+        (runJSNodeTest):
+
 2012-04-30  Zhenyao Mo  <zmo@google.com>
 
         Unreviewed, test expectations update.
diff --git a/LayoutTests/webaudio/javascriptaudionode-downmix8-2channel-input-expected.txt b/LayoutTests/webaudio/javascriptaudionode-downmix8-2channel-input-expected.txt
new file mode 100644 (file)
index 0000000..9260ede
--- /dev/null
@@ -0,0 +1,9 @@
+Tests downmixing an 8-channel source connected to a JavaScriptAudioNode with 2-channel input.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS onaudioprocess was called with correct input data.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/javascriptaudionode-downmix8-2channel-input.html b/LayoutTests/webaudio/javascriptaudionode-downmix8-2channel-input.html
new file mode 100644 (file)
index 0000000..dea5d32
--- /dev/null
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script type="text/javascript" src="resources/javascriptaudionode-testing.js"></script>
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests downmixing an 8-channel source connected to a JavaScriptAudioNode with 2-channel input.");
+
+var sampleRate = 44100.0;
+var sourceChannels = 8;
+var inputChannels = 2;
+var outputChannels = 6;
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+
+    window.jsTestIsAsync = true;
+
+    runJSNodeTest();
+}
+
+runTest();
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/javascriptaudionode-upmix2-8channel-input-expected.txt b/LayoutTests/webaudio/javascriptaudionode-upmix2-8channel-input-expected.txt
new file mode 100644 (file)
index 0000000..a1550d4
--- /dev/null
@@ -0,0 +1,9 @@
+Tests upmixing a 2-channel source connected to a JavaScriptAudioNode with 8-channel input.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS onaudioprocess was called with correct input data.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/javascriptaudionode-upmix2-8channel-input.html b/LayoutTests/webaudio/javascriptaudionode-upmix2-8channel-input.html
new file mode 100644 (file)
index 0000000..ca7db0b
--- /dev/null
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script type="text/javascript" src="resources/javascriptaudionode-testing.js"></script>
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests upmixing a 2-channel source connected to a JavaScriptAudioNode with 8-channel input.");
+
+var sampleRate = 44100.0;
+var sourceChannels = 2;
+var inputChannels = 8;
+var outputChannels = 6;
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+
+    window.jsTestIsAsync = true;
+
+    runJSNodeTest();
+}
+
+runTest();
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/resources/javascriptaudionode-testing.js b/LayoutTests/webaudio/resources/javascriptaudionode-testing.js
new file mode 100644 (file)
index 0000000..1197848
--- /dev/null
@@ -0,0 +1,84 @@
+// For the current implementation of JavaScriptAudioNode, when it works with OfflineAudioContext (which runs much faster
+// than real-time) the event.inputBuffer might be overwrite again before onaudioprocess ever get chance to be called.
+// We carefully arrange the renderLengthInFrames and bufferSize to have exactly the same value to avoid this issue.
+var renderLengthInFrames = 512;
+var bufferSize = 512;
+
+var context;
+
+function createBuffer(context, numberOfChannels, length) {
+    var audioBuffer = context.createBuffer(numberOfChannels, length, sampleRate);
+
+    fillData(audioBuffer, numberOfChannels, audioBuffer.length);
+    return audioBuffer;
+}
+
+function processAudioData(event) {
+    buffer = event.outputBuffer;
+    if (buffer.numberOfChannels != outputChannels)
+        testFailed("numberOfOutputChannels doesn't match!");
+
+    if (buffer.length != bufferSize)
+        testFailed("length of buffer doesn't match!");
+
+    buffer = event.inputBuffer;
+    
+    var success = checkStereoOnlyData(buffer, inputChannels, buffer.length);
+
+    if (success) {
+        testPassed("onaudioprocess was called with correct input data.");
+    } else {
+        testFailed("onaudioprocess was called with wrong input data.");
+    }
+}
+
+function fillData(buffer, numberOfChannels, length) {
+    for (var i = 0; i < numberOfChannels; ++i) {
+        var data = buffer.getChannelData(i);
+
+        for (var j = 0; j < length; ++j)
+            if (i < 2)
+                data[j] = i * 2 - 1;
+            else
+                data[j] = 0;
+    }
+}
+
+// Both 2 to 8 upmix and 8 to 2 downmix are just directly copy the first two channels and left channels are zeroed.
+function checkStereoOnlyData(buffer, numberOfChannels, length) {
+    for (var i = 0; i < numberOfChannels; ++i) {
+        var data = buffer.getChannelData(i);
+
+        for (var j = 0; j < length; ++j) {
+            if (i < 2) {
+                if (data[j] != i * 2 - 1)
+                    return false;
+            } else {
+                if (data[j] != 0)
+                    return false;
+            }
+        }
+    }
+    return true;
+}
+
+function runJSNodeTest()
+{
+    // Create offline audio context.
+    context = new webkitAudioContext(2, renderLengthInFrames, sampleRate);
+
+    var sourceBuffer = createBuffer(context, sourceChannels, renderLengthInFrames);
+
+    var bufferSource = context.createBufferSource();
+    bufferSource.buffer = sourceBuffer;
+
+    var jsnode = context.createJavaScriptNode(bufferSize, inputChannels, outputChannels);
+
+    bufferSource.connect(jsnode);
+    jsnode.connect(context.destination);
+    jsnode.onaudioprocess = processAudioData;
+
+    bufferSource.noteOn(0);
+    context.oncomplete = finishJSTest;
+    context.startRendering();
+}
index 9082ce2..dad6461 100644 (file)
@@ -1,3 +1,21 @@
+2012-04-30  Xingnan Wang  <xingnan.wang@intel.com>
+
+        Add multichannel support for input of JavaScriptAudioNode
+        https://bugs.webkit.org/show_bug.cgi?id=84687
+
+        Reviewed by Chris Rogers.
+
+        Tests: webaudio/javascriptaudionode-downmix8-2channel-input.html
+               webaudio/javascriptaudionode-upmix2-8channel-input.html
+
+        * Modules/webaudio/JavaScriptAudioNode.cpp:
+        (WebCore::JavaScriptAudioNode::create):
+        (WebCore::JavaScriptAudioNode::JavaScriptAudioNode):
+        (WebCore::JavaScriptAudioNode::initialize):
+        (WebCore::JavaScriptAudioNode::process):
+        * Modules/webaudio/JavaScriptAudioNode.h:
+        (JavaScriptAudioNode):
+
 2012-04-30  Oliver Hunt  <oliver@apple.com>
 
         Harden arithmetic in ImageBufferDataCG
index 81c1dc7..7f541d5 100644 (file)
@@ -58,8 +58,8 @@ PassRefPtr<JavaScriptAudioNode> JavaScriptAudioNode::create(AudioContext* contex
         return 0;
     }
 
-    // FIXME: We still need to implement numberOfInputChannels.
-    ASSERT_UNUSED(numberOfInputChannels, numberOfInputChannels <= AudioContext::maxNumberOfChannels());
+    if (numberOfInputChannels > AudioContext::maxNumberOfChannels())
+        return 0;
 
     if (!numberOfOutputChannels || numberOfOutputChannels > AudioContext::maxNumberOfChannels())
         return 0;
@@ -74,13 +74,15 @@ JavaScriptAudioNode::JavaScriptAudioNode(AudioContext* context, float sampleRate
     , m_bufferSize(bufferSize)
     , m_bufferReadWriteIndex(0)
     , m_isRequestOutstanding(false)
+    , m_numberOfInputChannels(numberOfInputChannels)
+    , m_numberOfOutputChannels(numberOfOutputChannels)
+    , m_internalInputBus(numberOfInputChannels, AudioNode::ProcessingSizeInFrames, false)
 {
     // Regardless of the allowed buffer sizes, we still need to process at the granularity of the AudioNode.
     if (m_bufferSize < AudioNode::ProcessingSizeInFrames)
         m_bufferSize = AudioNode::ProcessingSizeInFrames;
 
-    // FIXME: We still need to implement numberOfInputChannels.
-    ASSERT_UNUSED(numberOfInputChannels, numberOfInputChannels > 0);
+    ASSERT(numberOfInputChannels <= AudioContext::maxNumberOfChannels());
 
     addInput(adoptPtr(new AudioNodeInput(this)));
     addOutput(adoptPtr(new AudioNodeOutput(this, numberOfOutputChannels)));
@@ -105,8 +107,11 @@ void JavaScriptAudioNode::initialize()
     // Create double buffers on both the input and output sides.
     // These AudioBuffers will be directly accessed in the main thread by JavaScript.
     for (unsigned i = 0; i < 2; ++i) {
-        m_inputBuffers.append(AudioBuffer::create(2, bufferSize(), sampleRate));
-        m_outputBuffers.append(AudioBuffer::create(this->output(0)->numberOfChannels(), bufferSize(), sampleRate));
+        RefPtr<AudioBuffer> inputBuffer = m_numberOfInputChannels ? AudioBuffer::create(m_numberOfInputChannels, bufferSize(), sampleRate) : 0;
+        RefPtr<AudioBuffer> outputBuffer = m_numberOfOutputChannels ? AudioBuffer::create(m_numberOfOutputChannels, bufferSize(), sampleRate) : 0;
+
+        m_inputBuffers.append(inputBuffer);
+        m_outputBuffers.append(outputBuffer);
     }
 
     AudioNode::initialize();
@@ -157,33 +162,24 @@ void JavaScriptAudioNode::process(size_t framesToProcess)
     ASSERT(isFramesToProcessGood);
     if (!isFramesToProcessGood)
         return;
-        
-    unsigned numberOfInputChannels = inputBus->numberOfChannels();
+
+    unsigned numberOfInputChannels = m_internalInputBus.numberOfChannels();
     unsigned numberOfOutputChannels = outputBus->numberOfChannels();
-    
-    bool channelsAreGood = (numberOfInputChannels == 1 || numberOfInputChannels == 2);
+
+    bool channelsAreGood = (numberOfInputChannels == m_numberOfInputChannels) && (numberOfOutputChannels == m_numberOfOutputChannels);
     ASSERT(channelsAreGood);
     if (!channelsAreGood)
         return;
 
-    const float* sourceL = inputBus->channel(0)->data();
-    const float* sourceR = numberOfInputChannels > 1 ? inputBus->channel(1)->data() : 0;
+    for (unsigned i = 0; i < numberOfInputChannels; i++)
+        m_internalInputBus.setChannelMemory(i, inputBuffer->getChannelData(i)->data() + m_bufferReadWriteIndex, framesToProcess);
+
+    if (numberOfInputChannels)
+        m_internalInputBus.copyFrom(*inputBus);
 
-    // Copy from the input to the input buffer.  See "buffersAreGood" check above for safety.
-    size_t bytesToCopy = sizeof(float) * framesToProcess;
-    memcpy(inputBuffer->getChannelData(0)->data() + m_bufferReadWriteIndex, sourceL, bytesToCopy);
-    
-    if (numberOfInputChannels == 2)
-        memcpy(inputBuffer->getChannelData(1)->data() + m_bufferReadWriteIndex, sourceR, bytesToCopy);
-    else if (numberOfInputChannels == 1) {
-        // If the input is mono, then also copy the mono input to the right channel of the AudioBuffer which the AudioProcessingEvent uses.
-        // FIXME: it is likely the audio API will evolve to present an AudioBuffer with the same number of channels as our input.
-        memcpy(inputBuffer->getChannelData(1)->data() + m_bufferReadWriteIndex, sourceL, bytesToCopy);
-    }
-    
     // Copy from the output buffer to the output. 
     for (unsigned i = 0; i < numberOfOutputChannels; ++i)
-        memcpy(outputBus->channel(i)->mutableData(), outputBuffer->getChannelData(i)->data() + m_bufferReadWriteIndex, bytesToCopy);
+        memcpy(outputBus->channel(i)->mutableData(), outputBuffer->getChannelData(i)->data() + m_bufferReadWriteIndex, sizeof(float) * framesToProcess);
 
     // Update the buffering index.
     m_bufferReadWriteIndex = (m_bufferReadWriteIndex + framesToProcess) % bufferSize();
index b940e1a..4098cfc 100644 (file)
@@ -26,6 +26,7 @@
 #define JavaScriptAudioNode_h
 
 #include "ActiveDOMObject.h"
+#include "AudioBus.h"
 #include "AudioNode.h"
 #include "EventListener.h"
 #include "EventTarget.h"
@@ -101,6 +102,11 @@ private:
     size_t m_bufferSize;
     unsigned m_bufferReadWriteIndex;
     volatile bool m_isRequestOutstanding;
+
+    unsigned m_numberOfInputChannels;
+    unsigned m_numberOfOutputChannels;
+
+    AudioBus m_internalInputBus;
 };
 
 } // namespace WebCore