Bug 1644847 [wpt PR 24093] - Avoid memory allocation in AudioWorkletProcessor.process(), a=testonly
authorHongchan Choi <hongchan@chromium.org>
Mon, 22 Jun 2020 10:41:15 +0000
changeset 536829 2ce989e05dbcd551b9620ea54066f9b1bcd511cb
parent 536828 13f4bcdcf354c384a4a6144c30ee9efa2be93634
child 536830 3b84d46b5df4e8b94c6fd9186a36e464a12c77af
push id119680
push userwptsync@mozilla.com
push dateTue, 23 Jun 2020 11:08:22 +0000
treeherderautoland@7ca3d4bada73 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1644847, 24093, 1086665, 1071085, 2218702, 779052
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1644847 [wpt PR 24093] - Avoid memory allocation in AudioWorkletProcessor.process(), a=testonly Automatic update from web-platform-tests Avoid memory allocation in AudioWorkletProcessor.process() Based on the spec [1], the current implementation of AudioWorkletProcessor needs to create a new data container (i.e. WebIDL sequence<>) for input, output, and param arrays. With the new spec change [2], this CL changes the overall design of the audio processing callback: 1. Moves the processing call from AudioWorkletGlobalScope to AudioWorkletProcessor object. 2. AudioWorkletProcessor now keeps the data container within the object and allocate memory when it is needed. The preliminary benchmark shows the sizable improvement in the audio stream quality. The glitch score (= buffer underrun/total callback) is improved by ~9x in the low-tier machine. [3] This is an API change [4], but the real world impact would be negligible because there's no functionality change. [1]: https://webaudio.github.io/web-audio-api/#dom-audioworkletprocessor-process-inputs-outputs-parameters-inputs [2]: https://github.com/WebAudio/web-audio-api/issues/1933#issuecomment-616632754 [3]: https://bugs.chromium.org/p/chromium/issues/detail?id=1086665#c2 [4]: https://chromestatus.com/feature/5647541725036544 Bug: 1071085, 1086665 Change-Id: I3e664754973d4d86649d38c1807c6b9d7830fb96 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2218702 Reviewed-by: Raymond Toy <rtoy@chromium.org> Reviewed-by: Yuki Shiino <yukishiino@chromium.org> Commit-Queue: Hongchan Choi <hongchan@chromium.org> Cr-Commit-Position: refs/heads/master@{#779052} -- wpt-commits: 48a5574b7e1a3d7d952a4b9fe84b049375773e42 wpt-pr: 24093
testing/web-platform/tests/webaudio/the-audio-api/the-audioworklet-interface/audioworkletprocessor-process-frozen-array.https.html
testing/web-platform/tests/webaudio/the-audio-api/the-audioworklet-interface/processors/array-check-processor.js
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-audioworklet-interface/audioworkletprocessor-process-frozen-array.https.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<html>
+  <head>
+    <title>
+      Test given arrays within AudioWorkletProcessor.process() method
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+
+  <body>
+    <script>
+      const audit = Audit.createTaskRunner();
+      const filePath = 'processors/array-check-processor.js';
+      const context = new AudioContext();
+
+      // Test if the incoming arrays are frozen as expected.
+      audit.define('check-frozen-array', (task, should) => {
+        context.audioWorklet.addModule(filePath).then(() => {
+          const workletNode =
+              new AudioWorkletNode(context, 'array-frozen-processor');
+          workletNode.port.onmessage = (message) => {
+            const actual = message.data;
+            should(actual.isInputFrozen, '|inputs| is frozen').beTrue();
+            should(actual.isOutputFrozen, '|outputs| is frozen').beTrue();
+            task.done();
+          };
+        });
+      });
+
+      // The incoming arrays should not be transferred, but the associated
+      // ArrayBuffers can be transferred. See the `array-transfer-processor`
+      // definition for the details.
+      audit.define('transfer-frozen-array', (task, should) => {
+        const sourceNode = new ConstantSourceNode(context);
+        const workletNode =
+            new AudioWorkletNode(context, 'array-transfer-processor');
+        workletNode.port.onmessage = (message) => {
+          const actual = message.data;
+          if (actual.type === 'assertion')
+            should(actual.success, actual.message).beTrue();
+          if (actual.done)
+            task.done();
+        };
+        sourceNode.connect(workletNode);
+        sourceNode.start();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-audioworklet-interface/processors/array-check-processor.js
@@ -0,0 +1,94 @@
+/**
+ * @class ArrayFrozenProcessor
+ * @extends AudioWorkletProcessor
+ */
+class ArrayFrozenProcessor extends AudioWorkletProcessor {
+  constructor() {
+    super();
+    this._messageSent = false;
+  }
+
+  process(inputs, outputs, parameters) {
+    const input = inputs[0];
+    const output = outputs[0];
+
+    if (!this._messageSent) {
+      this.port.postMessage({
+        inputLength: input.length,
+        isInputFrozen: Object.isFrozen(inputs) && Object.isFrozen(input),
+        outputLength: output.length,
+        isOutputFrozen: Object.isFrozen(outputs) && Object.isFrozen(output)
+      });
+      this._messageSent = true;
+    }
+
+    return false;
+  }
+}
+
+/**
+ * @class ArrayTransferProcessor
+ * @extends AudioWorkletProcessor
+ */
+class ArrayTransferProcessor extends AudioWorkletProcessor {
+  constructor() {
+    super();
+    this._messageSent = false;
+  }
+
+  process(inputs, outputs, parameters) {
+    const input = inputs[0];
+    const output = outputs[0];
+
+    if (!this._messageSent) {
+      try {
+        // Transferring Array objects should NOT work.
+        this.port.postMessage({
+          inputs, input, inputChannel: input[0],
+          outputs, output, outputChannel: output[0]
+        }, [inputs, input, inputs[0], outputs, output, output[0]]);
+        // Hence, the following must NOT be reached.
+        this.port.postMessage({
+          type: 'assertion',
+          success: false,
+          message: 'Transferring inputs/outputs, an individual input/output ' +
+              'array, or a channel Float32Array MUST fail, but succeeded.'
+        });
+      } catch (error) {
+        this.port.postMessage({
+          type: 'assertion',
+          success: true,
+          message: 'Transferring inputs/outputs, an individual input/output ' +
+              'array, or a channel Float32Array is not allowed as expected.'
+        });
+      }
+
+      try {
+        // Transferring ArrayBuffers should work.
+        this.port.postMessage(
+          {inputChannel: input[0], outputChannel: output[0]},
+          [input[0].buffer, output[0].buffer]);
+        this.port.postMessage({
+          type: 'assertion',
+          success: true,
+          message: 'Transferring ArrayBuffers was successful as expected.'
+        });
+      } catch (error) {
+        // This must NOT be reached.
+        this.port.postMessage({
+          type: 'assertion',
+          success: false,
+          message: 'Transferring ArrayBuffers unexpectedly failed.'
+        });
+      }
+
+      this.port.postMessage({done: true});
+      this._messageSent = true;
+    }
+
+    return false;
+  }
+}
+
+registerProcessor('array-frozen-processor', ArrayFrozenProcessor);
+registerProcessor('array-transfer-processor', ArrayTransferProcessor);