-
Notifications
You must be signed in to change notification settings - Fork 168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[audioworklet] AudioWorkletProcessor ondata should just be a callback rather than an EventHandler #1194
Comments
Yeah, these were loose ends that we'll have to figure out. I assume its interface should be loosely defined by a prose, not in a form of IDL? How about |
@domenic should probably be involved here to some extent if you're going to redesign |
There two classes: AudioWorkletNode (main thread) and AudioWorkletProcessor (worklet thread). When user creates a AWN object, an instance of AWP is also created in the worklet global scope. They are tightly coupled from the construction and communicate each other using If we can send serialized data between them, that's good enough for our purpose. So making Please let me know if you need more clarification! |
I see, the goal is not exposing
|
By 2, do you mean by "same-origin" that the worklet script from a different origin cannot be used? Hmm. That actually sounds bad for our use cases. Can you explain what kind of 'security story' we need? |
@hoch I guess it depends on what the origin in charge is of that worklet script. I noticed in the worklet specification that the worklets are loaded with CORS, so I guess they end up being CORS-same-origin in which case we probably don't need anything special. Is that correct? |
Note also that StructuredDeserialize can fail. You need to consider that and either use a different error callback or some other way to signal failure on the receiver end. See also whatwg/html#936. |
cc @bakulf |
Okay, here's my conclusion from this thread:
|
It's possible it makes sense to do this for web audio; I am not sure. But I just wanted to note that for HTML and postMessage(), we will not tell the originator about deserialization failures, but instead we will tell the target that they were supposed to be getting a message, but did not. (Via a new "messageerror" event.) |
@domenic So you mean we want to signal the error to AudioWorkletGlobalScope, not the main scope. I believe that's logically correct, but how can developers see/monitor the error message in that case? |
Using (Or using the developer tools, which should show all such errors.) |
Why would that be ? |
As long as there is CORS, which there is per the worklet loading pipeline, it should be fine. |
In that case, we can simply refer what the worklet does. We don't want to repeat how CORS works on the worklet in the WebAudio spec. |
PTAL @rtoy, @padenot and @joeberkovitz: First, we need to take interface AudioWorkletNode : AudioNode {
readonly attribute AudioParamMap parameters;
void sendData(any data);
}
interface AudioWorkletProcessor {
readonly attribute AudioContextInfo contextInfo;
void sendData(any data);
} Secondly, we need a prose that describes how
I guess this also covers: #1193. |
@domenic @bfgeek WorkletGlobalScope does not inherit |
I see, that is a bit of a blocker. I am curious what @bfgeek thinks then. It seems better to have such events than to have each type of worklet have its own bespoke way of receiving errors from the main thread. |
For audio worklets in particular, access to shared memory seems key to being able to allow low latency audio from game engines, audio codecs, or speech synthesizers (just spoke to folks that do that one today). A typical pattern would likely be to use a ShardArrayBuffer + audio worket + a web worker running wasm code to mirror something like what I understand AudioWorkers to have originally looked like. Where JS running on an audio thread would have danger of not being able to keep up with hard deadlines, Wasm code can avoid GC pauses, and presumably deliver reliable audio rates. I believe blocking operations like Atomics.wait can be excluded similar to on the main ui thread, as typically these kinds of applications will just want to keep a lock free queue full. I understand you guys want to avoid a pattern where audio data is posted, With SABs, there would be a single post, and then audio data is read directly from the buffer, so should be fast assuming the sender can keep up. My general sense is that as more of the Web API surface is hardened to handle shared memory, more surfaces will make sense in general to support SharedArrayBuffer. But audio in particular is an area where Unity and others have raised concern the current solution isn't performant (as with current WebAudio you simply can't both generate your own audio and know you won't skip). By the way, I had spoke to @rtoy a bit back about this and had the impression you guys were approaching v1 completeness, so I certainly didn't mean to derail that. Rather, I'm suggesting this is likely to be something folks will want to try this year, so worth not planning out in some general way. |
I'd prefer we write it down now as we think it should behave going forward. In particular I'd rather not revise the HTML Standard several times on this. (The HTML Standard is in "charge" of making sure Agent / Agent Cluster actually have grounding in browsers.) (Aside: please don't use guys as a gender-neutral term; folks works if I can make a suggestion.) |
Audio Worklets will have to support shared memory at some point to be competitive, that's for sure, for doing something like:
However, simply having transferables (for now), until a couple vendors ship an implementation, while making sure there is nothing blocking worklets to use shared memory in the future (since it's opt-in) seems like a reasonable path to take. Transferables, while generating some GC pressure, avoid copies (doing what is essentially a pointer swap), and the event loop is used as a synchronization mechanism, this can go a long way. |
Shared memory is actually not as much opt-in specification-wise. If your variant of |
I've been told that API need to opt-in to be able to receive a SharedArrayBuffer, which is the reason why the Web Audio API don't use them, because we haven't prioritized speccing/implementing it. I can't really find an authoritative source at the moment, but this line has been authored by the person who did If this is not the case, then we don't have much to do. |
That makes sense to me. It's a pretty different programming model than workers, but it seems internally consistent, and the motivation of trying to make it per-node and avoid global state in every way is a good one. |
@annevk re: SABs. This discussion was clarifying, initially I was only primarily thinking about the Houdini specs, and Audio wasn't top of mind. So I think it makes sense to allow SABs for worklets initially, (this means that worklets are in the same group(?) as window and workers?). Allowing them for all worklets should be fine as we won't be providing any thread-to-thread communciation channel for the css-{layout,paint,animation}-apis. I.e. there isn't any "sendData" analog in those specs, everything is controlled by the engine. Punting on transferrables still seems reasonable still i think in this specific case? @hoch One thing that we should be clear about in the spec is not allowing futexes on the audio thread, I'm assuming this would be "bad thing"? :P. |
Finally, after thinking about this a little, I'm personally 60/40 on if this should be callback or an eventhandler. I think a simple callback is slightly nicer than having an event attached to an object? I.e. My personal default model for worklets has been use callbacks by default, but really curious what others think there. Also as a historical node I think this feature was initially called |
Note that if you're talking about a real event handler, the syntax |
Ah thanks. |
Couple notes:
|
Random idea, haven't thought it through: could each worklet class get a MessagePort instance? One on the "inside" (via the base class presumably), and one on the outside (via the worklet class)? |
Thanks all for talking thru SAB use case + impact. FWIW, coming to this particular topic without all the context, here's one more thing to consider with Events vs simple callbacks. I've heard concern expressed that for audio worklets, in practice, anything that could cause a garbage collect has the potential to pop the audio stream (as collects could delay timely audio delivery). I assume more extensive measures would be required to avoid generating garbage for each Event? With general JS running in the worklet, there'd be other things that might trigger GC, but Wasm at least has the potential to avoid it if care is applied, assuming the worklet plumbing / event loop itself doesn't generate garbage.
Yes indeed, quite so. |
@bfgeek Yes, @domenic pointed out a while ago that we'd have to use callback not the event handler. I couldn't find that comment now, but I forgot to fix our IDL to reflect the discussion. @annevk Since AudioWorklet is the first to implement the instance-to-instance messaging mechanism, I understand that you want to explore every option possible. However, the constraints we have is very different from other Worker or Worklet variants. The audio rendering thread will run JS VM so the GC triggered by user-supplied code will stop the entire audio rendering operation, which will eventually cause the glitch. (At least this is unavoidable in Chrome) If exposing Event/EventTarget/postMessage increases the chance of GC in that thread, then it already defeats the purpose of the AudioWorklet project. It needs to provide the simplest way of sending message, and that's why @bfgeek and I came up with |
GC may be triggered anyhow, whether or not Events are used. If you really want to optimize out possible GCs, the passed data must not be any new object, but some mutating array containing raw data or something, something which would change any time new data is received. (In Gecko the wrappers for Events are allocated from nursery heap to try to avoid major GCs. Minor GC should be fast.) |
Just to reiterate, MessageChannel already does this. And yeah, if you really want to avoid GC, you should have just a SharedArrayBuffer object and some kind of signal for when it's updated (per @wanderview). |
Perhaps I have not been clear about this, but the primary purpose of Having two different layers for the different rate of signal (a-rate and k-rate) has been a successful pattern for computer music programming. (e.g. SuperCollider or Max/MSP) From my perspective, // From the main global scope to the associated processor.
myNode.sendData({ volume: 0.6, type: 'noise' }); Supposedly you can send whatever you want (and it'll be serialized at the receiving end), but sending thousands of audio samples over the thread is definitely not the use case here. If it were our intention to build this API, we would have chosen SAB or Transferable for sure. It might makes sense to have the more generalized messaging mechanism across the web platform, but I would really like to argue that the light-weight messaging is super important for audio. @padenot @rtoy @joeberkovitz WDYT? |
myNode.sendData({ volume: 0.6, type: 'noise' }); already creates garbage, not much different to postMessage(). Are you saying the object created by sendData is somehow ok, but the Event object created by postMessage isn't? Sure, the data with postMessage would be event.data |
Yes, if the argument is about lightweightness or GC, I don't see any reason to avoid postMessage/MessageChannels. If you need to avoid GCs, then as @annevk said, you should be using shared memory. If you just want to have small lightweight messages, postMessage/MessageChannels are fine for that purpose; the additional Event object is not a significant overhead. |
I would like to take stab at using "the implicit MessageChannel" for AudioWorkletNode and AudioWorkletProcessor. @domenic suggested the worker interface is already using this pattern, and I believe this could work for the AudioWorklet case. // From the main scope.
myNode.port.postMessage(...);
// In AudioWorkletGlobalScope
class extends AudioWorkletProcessor {
constructor() {
this.port.onmessage = () => { ... };
}
...
} @domenic also suggested to expose Two issues on my mind:
|
I believe we very much do want to avoid GC. Any garbage generated on the
JS instance for the WebAudio thread will eventually cause the thread to
pause to do GC, glitching the audio. While we can't do anything about user
code, I think we really need to design it so that the
messages/callbacks/whatever don't themselves cause garbage to be generated.
And since WebAudio supports sample rates of 96 kHz (Chrome supports 384
kHz), any pause that is a significant fraction of 1.33 ms (128/96000) (0.33
ms for Chrome) will cause a glitch.
…On Thu, Apr 13, 2017 at 10:58 AM, Domenic Denicola ***@***.*** > wrote:
Yes, if the argument is about lightweightness or GC, I don't see any
reason to avoid postMessage/MessageChannels.
If you need to avoid GCs, then as @annevk <https://github.com/annevk>
said, you should be using shared memory. If you just want to have small
lightweight messages, postMessage/MessageChannels are fine for that
purpose; the additional Event object is not a significant overhead.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1194 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAofPE_GYwPidH9LS6-_CKj1UDdBL0frks5rvmJUgaJpZM4M0x-l>
.
--
Ray
|
tl;dr, we should use Long version: Big GC pauses are to be avoided, so being able to use In any case, minimizing the GC churn is a worthwhile goal (and not only for audio, webgl, webvr, and the new vulkan-like API that's shaping up have those constraints, although it's a bit less critical for anything but audio, considering the latency we're dealing with, here). If we really want to pick something that has good performance characteristics for our use case, we should be doing some hands-on analysis to quantify the impact of various APIs that require creating js objects. For example, we should measure, for a single IPC transmission of an empty object:
For all those absolute metrics, we should compute their standard deviation (because we're interested in the worst case, the Web Audio API being a real-time system). Then we can compare APIs styles (normal However, my guess is that any communication API that exists today on the web platform allocates memory and causes GC churn, and we need to pick an API allows minimizing this. Going with our custom API might make some of this easier, and might even allow GC-less processing on the worklet side, but is probably more complicated, and it's unclear that it would make things faster in the long run. Indeed, allowing In conclusion, I like @hoch's and @domenic view, summarized in #1194 (comment), for the following reasons:
|
It is technically true that on some OSes you can open an audio stream with a buffer size of 128 frames (or less, but Web Audio API has a fixed block size of 128 frames), at a very high sample-rate, which would mean having a callback each 0.33ms in the worst case (this is not specific to Chrome, it mostly depends on the capability of the sound card and the OS and the system configuration). Depending on the OS, you then have a buffer queue, or buffer ping-ponging. However, in practice, the measured loopback latency of a modern browser is around 30ms on OSX (this is from a paper that is probably going to be published at the next Web Audio Conference, it's not public yet. This figure is on OSX, measures the input->output latency, and is true for Firefox and Chrome). This is because browsers are using conservative buffer sizes to allow for higher callback load (i.e., being able to do more expensive processing without glitching) and to work around device-specific bugs, and also because modern browsers are using a multi-process and multi-thread architecture, bouncing the audio between multiple threads and processes, that might or might not be high priority/real-time thread. This allows for some slack, but I clearly agree we should design this API in a way that we does not prevent optimizing things in the future. |
So if we go down that road we need to:
And presumably only audio worklets. I can help with the specification-side of that, but to land changes in those standards we'll also need to have web-platform-tests that I'd prefer someone else to take on. |
Technically no need for MessageChannel. |
I've been following this silently so far and I'm all for investigating the use of MessagePort. I just want to throw out an opinion that in terms of frequency, this kind of messaging will typically occur only when the application wants to change an attribute or invoke an operation on a node. It doesn't happen on every render quantum. I am not sure if the concerns about GC are taking this usage profile into account, but maybe it's a legit concern even if the messaging is not so frequent. |
If something is possible, it will be abused. In any case, we have been chatting about this in w3c/css-houdini-drafts#380, and it validates the plan laid out in #1194 (comment). |
From WG call today: @hoch is going to pursue the MessagePort/postMessage approach since it leverages existing spec work. If there are performance problems from GC, they can emerge and be measured. |
I believe the intention here was to specifically deliver any ondata's sent from the main thread directly before the process method is called for that node?
If so AudioWorkletProcessor shouldn't have
ondata
being aEventHandler
just a callback similar toprocess
.The text was updated successfully, but these errors were encountered: