Skip to content

Commit 35482d3

Browse files
authored
[AUDIO_WORKET] Add support for setting the render quantum size (#25747)
The `renderSizeHint` is now available in Chrome Canary (`--enable-features=WebAudioConfigurableRenderQuantum`) so this can finally be tested. Most of the work was already done in preparation, so this required few changes. Test with: ``` test/runner interactive.test_audio_worklet_params_mixing ``` Which has been extended to request a large `2048` sample audio frame (which in an older browser will fallback to the default 128 samples). Note for me: look at why and since how long we can bust out of the stack without asserting. The stackAlloc() no longer appears to trip when requesting more bytes than `wwParams.stackSize`.
1 parent 7738b5e commit 35482d3

10 files changed

+66
-37
lines changed

src/audio_worklet.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function createWasmAudioWorkletProcessor() {
5050
// 64 frames, for the case where a multi-MB stack is passed.
5151
this.outputViews = new Array(Math.min(((wwParams.stackSize - {{{ STACK_ALIGN }}}) / this.bytesPerChannel) | 0, /*sensible limit*/ 64));
5252
#if ASSERTIONS
53-
console.assert(this.outputViews.length > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`);
53+
assert(this.outputViews.length > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`);
5454
#endif
5555
this.createOutputViews();
5656

@@ -138,8 +138,8 @@ function createWasmAudioWorkletProcessor() {
138138
#endif
139139
var oldStackPtr = stackSave();
140140
#if ASSERTIONS
141-
console.assert(oldStackPtr == this.ctorOldStackPtr, 'AudioWorklet stack address has unexpectedly moved');
142-
console.assert(outputViewsNeeded <= this.outputViews.length, `Too many AudioWorklet outputs (need ${outputViewsNeeded} but have stack space for ${this.outputViews.length})`);
141+
assert(oldStackPtr == this.ctorOldStackPtr, 'AudioWorklet stack address has unexpectedly moved');
142+
assert(outputViewsNeeded <= this.outputViews.length, `Too many AudioWorklet outputs (need ${outputViewsNeeded} but have stack space for ${this.outputViews.length})`);
143143
#endif
144144

145145
// Allocate the necessary stack space. All pointer variables are in bytes;
@@ -154,6 +154,10 @@ function createWasmAudioWorkletProcessor() {
154154
var stackMemoryAligned = (stackMemoryStruct + stackMemoryData + 15) & ~15;
155155
var structPtr = stackAlloc(stackMemoryAligned);
156156
var dataPtr = structPtr + (stackMemoryAligned - stackMemoryData);
157+
#if ASSERTIONS
158+
// TODO: look at why stackAlloc isn't tripping the assertions
159+
assert(stackMemoryAligned <= wwParams.stackSize, `Not enough stack allocated to the AudioWorklet (need ${stackMemoryAligned}, got ${wwParams.stackSize})`);
160+
#endif
157161

158162
// Copy input audio descriptor structs and data to Wasm (recall, structs
159163
// first, audio data after). 'inputsPtr' is the start of the C callback's
@@ -221,7 +225,7 @@ function createWasmAudioWorkletProcessor() {
221225
// And that the views' size match the passed in output buffers
222226
for (entry of outputList) {
223227
for (subentry of entry) {
224-
console.assert(subentry.byteLength == this.bytesPerChannel, `AudioWorklet unexpected output buffer size (expected ${this.bytesPerChannel} got ${subentry.byteLength})`);
228+
assert(subentry.byteLength == this.bytesPerChannel, `AudioWorklet unexpected output buffer size (expected ${this.bytesPerChannel} got ${subentry.byteLength})`);
225229
}
226230
}
227231
}

src/lib/libwebaudio.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,7 @@ var LibraryWebAudio = {
7777

7878
// Performs the work of getting the AudioContext's render quantum size.
7979
$emscriptenGetContextQuantumSize: (contextHandle) => {
80-
// TODO: in a future release this will be something like:
81-
// return EmAudio[contextHandle].renderQuantumSize || 128;
82-
// It comes two caveats: it needs the hint when generating the context adding to
83-
// emscripten_create_audio_context(), and altering the quantum requires a secure
84-
// context and fallback implementing. Until then we simply use the 1.0 API value:
85-
return 128;
80+
return EmAudio[contextHandle]['renderQuantumSize'] || 128;
8681
},
8782

8883
// emscripten_create_audio_context() does not itself use the
@@ -100,9 +95,15 @@ var LibraryWebAudio = {
10095
#endif
10196
#endif
10297

98+
// Converts AUDIO_CONTEXT_RENDER_SIZE_* into AudioContextRenderSizeCategory
99+
// enums, otherwise returns a positive int value.
100+
function readRenderSizeHint(val) {
101+
return (val < 0) ? 'hardware' : (val || 'default');
102+
}
103103
var opts = options ? {
104104
latencyHint: UTF8ToString({{{ makeGetValue('options', C_STRUCTS.EmscriptenWebAudioCreateAttributes.latencyHint, '*') }}}) || undefined,
105-
sampleRate: {{{ makeGetValue('options', C_STRUCTS.EmscriptenWebAudioCreateAttributes.sampleRate, 'u32') }}} || undefined
105+
sampleRate: {{{ makeGetValue('options', C_STRUCTS.EmscriptenWebAudioCreateAttributes.sampleRate, 'u32') }}} || undefined,
106+
renderSizeHint: readRenderSizeHint({{{ makeGetValue('options', C_STRUCTS.EmscriptenWebAudioCreateAttributes.renderSizeHint, 'i32') }}})
106107
} : undefined;
107108

108109
#if WEBAUDIO_DEBUG

src/struct_info.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1269,7 +1269,8 @@
12691269
"structs": {
12701270
"EmscriptenWebAudioCreateAttributes": [
12711271
"latencyHint",
1272-
"sampleRate"
1272+
"sampleRate",
1273+
"renderSizeHint"
12731274
],
12741275
"WebAudioParamDescriptor": [
12751276
"defaultValue",

src/struct_info_generated.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,8 +700,9 @@
700700
"visibilityState": 4
701701
},
702702
"EmscriptenWebAudioCreateAttributes": {
703-
"__size__": 8,
703+
"__size__": 12,
704704
"latencyHint": 0,
705+
"renderSizeHint": 8,
705706
"sampleRate": 4
706707
},
707708
"EmscriptenWebGLContextAttributes": {

src/struct_info_generated_wasm64.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,7 @@
702702
"EmscriptenWebAudioCreateAttributes": {
703703
"__size__": 16,
704704
"latencyHint": 0,
705+
"renderSizeHint": 12,
705706
"sampleRate": 8
706707
},
707708
"EmscriptenWebGLContextAttributes": {

system/include/emscripten/webaudio.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@ extern "C" {
1919

2020
typedef int EMSCRIPTEN_WEBAUDIO_T;
2121

22+
// Default render size of 128 frames
23+
#define AUDIO_CONTEXT_RENDER_SIZE_DEFAULT 0
24+
// Let the hardware determine the best render size
25+
#define AUDIO_CONTEXT_RENDER_SIZE_HARDWARE -1
26+
2227
typedef struct EmscriptenWebAudioCreateAttributes
2328
{
2429
const char *latencyHint; // Specify one of "balanced", "interactive" or "playback"
2530
uint32_t sampleRate; // E.g. 44100 or 48000
31+
int32_t renderSizeHint; // AUDIO_CONTEXT_RENDER_SIZE_* or number of samples
2632
} EmscriptenWebAudioCreateAttributes;
2733

2834
// Creates a new Web Audio AudioContext, and returns a handle to it.

test/codesize/audio_worklet_wasm.expected.js

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
var m = globalThis.Module || "undefined" != typeof Module ? Module : {}, r = !!globalThis.AudioWorkletGlobalScope, t = "em-ww" == globalThis.name || r, u, z, I, J, G, E, w, X, F, D, C, Y, A, Z;
1+
var m = globalThis.Module || "undefined" != typeof Module ? Module : {}, p = !!globalThis.AudioWorkletGlobalScope, t = "em-ww" == globalThis.name || p, u, z, I, J, G, E, w, X, F, D, C, Y, A, Z;
22

33
function v(a) {
44
u = a;
@@ -10,12 +10,12 @@ function v(a) {
1010
a.G = a.M = 0;
1111
}
1212

13-
t && !r && (onmessage = a => {
13+
t && !p && (onmessage = a => {
1414
onmessage = null;
1515
v(a.data);
1616
});
1717

18-
if (r) {
18+
if (p) {
1919
function a(b) {
2020
class h extends AudioWorkletProcessor {
2121
constructor(d) {
@@ -36,7 +36,7 @@ if (r) {
3636
return b;
3737
}
3838
process(d, g, e) {
39-
var l = d.length, p = g.length, f, q, k = 12 * (l + p), n = 0;
39+
var l = d.length, q = g.length, f, r, k = 12 * (l + q), n = 0;
4040
for (f of d) n += f.length;
4141
n *= this.s;
4242
var H = 0;
@@ -53,15 +53,15 @@ if (r) {
5353
G[k + 4 >> 2] = this.u;
5454
G[k + 8 >> 2] = n;
5555
k += 12;
56-
for (q of f) E.set(q, n >> 2), n += this.s;
56+
for (r of f) E.set(r, n >> 2), n += this.s;
5757
}
5858
d = k;
59-
for (f = 0; q = e[f++]; ) G[k >> 2] = q.length, G[k + 4 >> 2] = n, k += 8, E.set(q, n >> 2),
60-
n += 4 * q.length;
59+
for (f = 0; r = e[f++]; ) G[k >> 2] = r.length, G[k + 4 >> 2] = n, k += 8, E.set(r, n >> 2),
60+
n += 4 * r.length;
6161
e = k;
6262
for (f of g) G[k >> 2] = f.length, G[k + 4 >> 2] = this.u, G[k + 8 >> 2] = n, k += 12,
6363
n += this.s * f.length;
64-
if (l = this.v(l, B, p, e, N, d, this.A)) for (f of g) for (q of f) q.set(this.B[--H]);
64+
if (l = this.v(l, B, q, e, N, d, this.A)) for (f of g) for (r of f) r.set(this.B[--H]);
6565
F(U);
6666
return !!l;
6767
}
@@ -129,9 +129,12 @@ var K = [], L = a => {
129129
}, T = a => {
130130
if (a) {
131131
var c = G[a >> 2];
132+
c = (c ? S(c) : "") || void 0;
133+
var b = J[a + 8 >> 2];
132134
a = {
133-
latencyHint: (c ? S(c) : "") || void 0,
134-
sampleRate: G[a + 4 >> 2] || void 0
135+
latencyHint: c,
136+
sampleRate: G[a + 4 >> 2] || void 0,
137+
N: 0 > b ? "hardware" : b || "default"
135138
};
136139
} else a = void 0;
137140
a = new AudioContext(a);
@@ -140,10 +143,10 @@ var K = [], L = a => {
140143
}, V = (a, c, b, h, d) => {
141144
var g = b ? J[b + 4 >> 2] : 0;
142145
if (b) {
143-
var e = J[b >> 2], l = G[b + 8 >> 2], p = g;
146+
var e = J[b >> 2], l = G[b + 8 >> 2], q = g;
144147
if (l) {
145148
l >>= 2;
146-
for (var f = []; p--; ) f.push(G[l++]);
149+
for (var f = []; q--; ) f.push(G[l++]);
147150
l = f;
148151
} else l = void 0;
149152
b = {
@@ -156,7 +159,7 @@ var K = [], L = a => {
156159
processorOptions: {
157160
v: h,
158161
A: d,
159-
u: 128
162+
u: O[a].renderQuantumSize || 128
160163
}
161164
};
162165
} else b = void 0;
@@ -191,17 +194,17 @@ var K = [], L = a => {
191194
if (!e) return l();
192195
e.addModule(m.js).then((() => {
193196
e.port || (e.port = {
194-
postMessage: p => {
195-
p._boot ? (e.D = new AudioWorkletNode(g, "em-bootstrap", {
196-
processorOptions: p
197+
postMessage: q => {
198+
q._boot ? (e.D = new AudioWorkletNode(g, "em-bootstrap", {
199+
processorOptions: q
197200
}), e.D.port.onmessage = f => {
198201
e.port.onmessage(f);
199-
}) : e.D.port.postMessage(p);
202+
}) : e.D.port.postMessage(q);
200203
}
201204
});
202205
e.port.postMessage({
203206
_boot: 1,
204-
N: ba++,
207+
O: ba++,
205208
G: m.wasm,
206209
L: w,
207210
J: c,
@@ -243,7 +246,7 @@ function y() {
243246
C = a.n;
244247
Y = a.o;
245248
A = a.k;
246-
t ? (Y(u.J, u.F), r || (removeEventListener("message", M), K = K.forEach(L), addEventListener("message", L))) : a.i();
249+
t ? (Y(u.J, u.F), p || (removeEventListener("message", M), K = K.forEach(L), addEventListener("message", L))) : a.i();
247250
t || X();
248251
}));
249252
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.html": 519,
33
"a.html.gz": 357,
4-
"a.js": 4235,
5-
"a.js.gz": 2170,
4+
"a.js": 4309,
5+
"a.js.gz": 2219,
66
"a.wasm": 1329,
77
"a.wasm.gz": 895,
8-
"total": 6083,
9-
"total_gz": 3422
8+
"total": 6157,
9+
"total_gz": 3471
1010
}

test/webaudio/audioworklet_params_mixing.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,18 @@
99
// create variable parameter data sizes, depending on the browser, it's also the
1010
// ideal to test audio worklets don't corrupt TLS variables.
1111

12+
// Large render size (approx 42ms)
13+
#define RENDER_SIZE_HINT 2048
14+
1215
// This needs to be big enough for the stereo output, 2x inputs, 2x params and
1316
// the worker stack. To note that different browsers have different stack size
1417
// requirement (see notes in process() plus the expansion of the params).
18+
#ifndef RENDER_SIZE_HINT
1519
#define AUDIO_STACK_SIZE 6144
20+
#else
21+
// float bytes * stereo * ins/outs + extra stack
22+
#define AUDIO_STACK_SIZE ((RENDER_SIZE_HINT * 4 * 2 * 5) + 1024)
23+
#endif
1624

1725
// Shared file playback and bootstrap
1826
#include "audioworklet_test_shared.inc"

test/webaudio/audioworklet_test_shared.inc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,12 @@ int main(void) {
9595
assert(workletStack);
9696

9797
// Set at least the latency hint to test the attribute setting
98+
// (The render size we take from the calling code if set)
9899
EmscriptenWebAudioCreateAttributes attrs = {
99-
.latencyHint = "balanced"
100+
.latencyHint = "balanced",
101+
#ifdef RENDER_SIZE_HINT
102+
.renderSizeHint = RENDER_SIZE_HINT
103+
#endif
100104
};
101105
EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(&attrs);
102106
emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, AUDIO_STACK_SIZE, getStartCallback(), NULL);

0 commit comments

Comments
 (0)