You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Choose modifying startRendering as the preferred approach (#1332)
This PR updates the progressive OfflineAudioContext explainer to reflect
the latest discussions that happened in the Audio WG (discussion thread
[here](WebAudio/web-audio-api#2445 (comment))).
The main outcome is that now the preferred approach is to modify the
behavior of `OfflineAudioContext.startRendering` in a
backward-compatible manner so that it now renders audio in chunks by
default.
[WebAudio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) provides a powerful and versatile API for performing audio-processing workflows in the browser. It supports complex node-based audio graphs that can be piped to system out (speakers) or an in-memory AudioBuffer for further processing, such as writing to a file. WebAudio can be used for many different workloads in the browser. An example relevant to this discussion is web-based video editors, like [clipchamp.com](https://clipchamp.com), which can use WebAudio to build up complex audio graphs based on multiple input files. These input files are composed, trimmed and processed according to a linear project timeline. The project can be previewed at realtime in the browser or exported faster-than-realtime as an .mp4.
16
+
[WebAudio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) provides a powerful and versatile API for performing audio-processing workflows in the browser. It supports complex node-based audio graphs that can be piped to system out (speakers) or an in-memory AudioBuffer for further processing, such as writing to a file. WebAudio can be used for many different workloads in the browser. An example relevant to this discussion is web-based video editors, like [clipchamp.com](https://clipchamp.com), which can use WebAudio to build up complex audio graphs based on multiple input files. These input files are composed, trimmed and processed according to a linear project timeline. The project can be previewed in realtime in the browser or exported faster-than-realtime as an .mp4.
17
17
18
18
WebAudio works well in a realtime playback context but it is not suitable for offline context (faster-than-realtime) processing due to a limitation in the design of WebAudio's [OfflineAudioContext API](https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext). The design of the API requires allocating memory to render the whole audio graph's memory up-front which can reach gigabytes of AudioBuffer data.
19
19
20
-
This document will propose adding a streaming offline context rendering function so that the audio graph data can be incrementally processed rather than allocating the whole audio buffer up-front.
20
+
This document proposes expanding the functionality of the offline context rendering function so that the audio graph data can be incrementally processed rather than allocating the whole audio buffer up-front.
21
21
22
22
## User-Facing Problem
23
23
@@ -31,15 +31,74 @@ A workaround to these limitations is for developers to build custom WASM audio-p
31
31
32
32
### Goals
33
33
34
-
- Allow streaming data out of a WebAudio in an offline context for rendering large audio graphs
34
+
- Allow incrementally rendering data out of a OfflineAudioContext for rendering large audio graphs
35
35
36
36
### Non-goals
37
37
38
38
- Change the existing `startRendering()` behavior, this API change is additive
39
39
40
-
## Proposed Approach - Add `startRenderingStream()` function
40
+
## Proposed Approach
41
41
42
-
The preferred approach is adding a new method `startRenderingStream()` that yields buffers of interleaved audio samples in a Float32Array, or another format as outlined in Open Questions. In this scenario, the user can read chunks as they arrive and consume them for storage, transcoding via WebCodecs, sending to a server, etc.
42
+
We propose modifying the behavior of `startRendering()` in a backwards-compatible manner so that it always renders incrementally in chunks. With this, the current one-shot render scenario becomes a special case of the new behavior where the new `chunkSize` parameter is set to `OfflineAudioContextOptions.length`.
43
+
44
+
To enable this, we will need to modify `startRendering()` to accept an optional `long chunkSize` argument and `OfflineAudioContextOptions.length` will be allowed to be set to `Infinity`. With this, every call to `startRendering` will now return an `AudioBuffer` that has a maximum number of samples given by `chunkSize`. If `chunkSize` is not provided to `startRendering`, it defaults to:
45
+
- The [render quantum size](https://webaudio.github.io/web-audio-api/#render-quantum-size) if `OfflineAudioContextOptions.length` is `Infinity`.
46
+
- The `OfflineAudioContextOptions.length` otherwise.
47
+
48
+
With this proposal, all offline audio rendering is incremental by definition:
49
+
- The current one-shot rendering scenario becomes a special case where `OfflineAudioContextOptions.length` is not `Infinity` and `chunkSize` is not specified (defaults to OfflineAudioContextOptions.length).
50
+
- Unknown duration rendering is supported by making `OfflineAudioContextOptions.length` equal to `Infinity`.
51
+
- Incremental rendering can be done by calling startRendering multiple times.
52
+
53
+
For the cases where there is a long ongoing one-shot render or an `Infinity`-length render that needs to stop, we propose adding a new `OfflineAudioContext.close()` that users can call to stop the rendering. Just like regular `AudioContexts`, the audio context cannot be resumed after `close` is called. Moreover, for the defined-length render case, the context will automatically transition to the `closed` state when all the audio data has been rendered.
54
+
55
+
Proposed interface:
56
+
57
+
```js
58
+
partial interfaceOfflineAudioContext {
59
+
Promise<void>close();
60
+
Promise<AudioBuffer>startRendering(optional unsigned long chunkSize);
- Simple to reason about and implement. Callers just need to request chunks whenever they are ready to process them.
89
+
- It's possible to feature detect by checking for the presence of the `close` method.
90
+
- Supports unknown duration rendering.
91
+
- Doesn't require integration with the Streams API.
92
+
93
+
### Cons
94
+
- Feature detection relies on checking the presence of an adjacent method (`close`) instead of checking directly the method that renders the chunks.
95
+
- Evolves the mental model for `startRendering`.
96
+
97
+
## Alternatives considered
98
+
99
+
### Alternative 1 - Add `startRenderingStream()` function
100
+
101
+
This alternative adds a new method `startRenderingStream()` that yields buffers of interleaved audio samples in a Float32Array, or another format as outlined in Open Questions. In this scenario, the user can read chunks as they arrive and consume them for storage, transcoding via WebCodecs, sending to a server, etc.
- The new capability is feature detectable because it is a new function. Compared to Alternative 1 which cannot be easily detected
105
-
- Aligns well with other web streaming APIs, similar to [WebCodecs](https://streams.spec.whatwg.org/#readablestream)
106
-
- Works with very large durations, no upper limit to WebAudio graph duration
163
+
- The new capability is feature detectable because it is a new function. Compared to the proposed approach and alternative 2 which cannot be easily detected.
164
+
- Aligns well with other web streaming APIs.
165
+
- Works with very large durations, no upper limit to WebAudio graph duration.
107
166
108
-
### Cons
167
+
####Cons
109
168
110
-
-None of note
169
+
-`ReadableStreams` are quite complex both for spec writers and web developers. WebCodecs has decided to decouple their specification from it (more info [here](https://docs.google.com/document/d/10S-p3Ob5snRMjBqpBf5oWn6eYij1vos7cujHoOCCCAw/edit?tab=t.0)).
111
170
112
-
### Output format
171
+
####Output format
113
172
114
173
There is an open question of what data format `startRenderingStream()` should return. The options under consideration are `AudioBuffer`, `Float32Array` planar or `Float32Array` interleaved.
115
174
116
-
#### `AudioBuffer`
175
+
#####`AudioBuffer`
117
176
118
177
**Pros**
119
178
@@ -123,7 +182,7 @@ There is an open question of what data format `startRenderingStream()` should re
123
182
124
183
- does not allow developers to BYOB (bring your own buffer) and BYOB helps developers manage memory usage, so `AudioBuffer` removes a bit of control
125
184
126
-
#### Planar Float32Array
185
+
#####Planar Float32Array
127
186
128
187
**Pros**
129
188
@@ -134,7 +193,7 @@ There is an open question of what data format `startRenderingStream()` should re
134
193
- requires the output of `startStreamingRendering()` to return an array of `Float32Array` in planar format for each output channel
135
194
- this leaves a question of what to do if only one channel is read by the consumer, i.e. what should happen to the other channel's data?
136
195
137
-
#### Interleaved Float32Array
196
+
#####Interleaved Float32Array
138
197
139
198
**Pros**
140
199
@@ -145,9 +204,9 @@ There is an open question of what data format `startRenderingStream()` should re
145
204
146
205
- None of note
147
206
148
-
## Alternative 1 - Modify existing `startRendering` method to allow streaming output
An alternative approach is to add options to the existing `startRendering()` to configure its operating mode. The mode can be set to `stream` to achieve streaming output. This is similar to the proposed approach but rather than adding a new function, it re-uses an existing function.
209
+
An alternative approach is to add options to the existing `startRendering()` to configure its operating mode. The mode can be set to `stream` to achieve streaming output. This is similar to alternative 1 but rather than adding a new function, it re-uses an existing function.
-It is not feature detectable, as compared to the Proposed Approach, because it only adds options dictionary to an existing function
211
-
- Less explicit than the proposed approach as it overloads an existing public API function. It is safer and simpler to add a new function and not change the behaviour of an existing function
268
+
- The same cons as Alternative 1
269
+
-Unlike Alternative 1, it is not feature detectablebecause it modifies an existing function.
270
+
- Less explicit than Alternative 1 as it overloads an existing public API function. It is safer and simpler to add a new function and not change the behaviour of an existing function
212
271
213
-
## Alternative 2 - emit `ondataavailable` events
272
+
###Alternative 3 - emit `ondataavailable` events
214
273
215
274
Keep current `startRendering()` API but do not allocate the full `AudioBuffer`. After starting, periodically emit events on the context or a new interface such as `ondataavailable(chunk: AudioBuffer)`.
216
275
217
276
The user can subscribe and collect chunks for processing.
218
277
219
278
At the end, the API may optionally still provide a full `AudioBuffer`.
220
279
221
-
### Pros
280
+
####Pros
222
281
223
282
- Simple to integrate with existing event-driven patterns
224
283
225
-
### Cons
284
+
####Cons
226
285
227
286
- None of note but lacking support in the community discussion
228
287
229
288
## Stakeholder Feedback / Opposition
230
289
231
290
- Web community : Positive
232
291
233
-
The participants on the [GitHub discussion](https://github.com/WebAudio/web-audio-api/issues/2445) agree that incremental delivery of data is necessary. Either streaming chunks of rendered audio or dispatching data in bits rather a single AudioBuffer so that memory usage is bounded and the data can be processed/consumed as it is produced.
292
+
The participants on the [GitHub discussion](https://github.com/WebAudio/web-audio-api/issues/2445) agree that incremental delivery of data is necessary. Either streaming chunks of rendered audio or dispatching data in bits rather than a single AudioBuffer so that memory usage is bounded and the data can be processed/consumed as it is produced.
0 commit comments