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
Ability to append to AudioBuffer #1825
Comments
My current implementation is using /**
Class to calculate amount of buffer for constant feeding rate while playing
On every tick() it calculates how much time was passed, and how much frames
it should read to compensate.
A second with 48000Hz sample rate contains 48k samples per second
*/
class PlaybackBuffer {
constructor(sampleRate, channels, bufferSize) {
this.fraction = RENDER_QUANTUM * channels;
if(bufferSize % this.fraction !== 0) {
throw new Error("Buffer size should be even to the fraction size: " + bufferSize);
}
this.bufferSize = bufferSize * channels;
this.samplesPerMs = sampleRate * channels / 1000;
}
start() {
this.lastTick = performance.now();
this.remainder = this.bufferSize;
return this.tick();
}
tick() {
const now = performance.now();
const samplesToFeed = (now - this.lastTick) * this.samplesPerMs + this.remainder;
const samplesToRead = Math.round(samplesToFeed / this.fraction) * this.fraction;
this.remainder = samplesToFeed - samplesToRead;
this.lastTick = now;
return samplesToRead;
}
}
this.playback = new PlaybackBuffer(this.context.sampleRate, channels, this.bufferSize);
this._readAndFeed(this.playback.start());
this.interval = setInterval(() => {
this._readAndFeed(this.playback.tick());
}, Math.floor(RENDER_QUANTUM / this.context.sampleRate / 1000 ));
function _readAndFeed(amount) {
if(!amount) return;
let buffer = new Float32Array(amount);
fillBufferInSomeWay(buffer);
this.node.port.postMessage({message: 'data', data: buffer});
} It calls You can see, how complex and fragile implementation is. |
Why having this in a Web Audio API implementation would be any different from doing it in JavaScript ? The proper solution to this problem is to write an SPSC lock-free ring-buffer, which is perfectly doable in JavaScript and WASM. We've specced |
Actually, you might be right. A couple of notes
Bottom line: the current situation seems ok, and instead of extending the specification, just a good documentation/proof of concept/example might be a better solution. Thanks @padenot |
I'm closing this, but the Audio Working Group is having a look at writing some documentation or other things to facilitate this pattern, we discussed this during a call yesterday. |
I found another benefit to having the ability to feed dynamic data to But when I feed data to AudioWorklet using SPSC lock-free ring-buffer, I should resample it by myself using custom code (slow and buggy). AFAIK, there is no way to feed data to AudioWorkletNode in original sample rate and use a native node to do resampling without custom code, is there? |
Use a library that works, compiled to WASM, then. Another approach in theory would be to create an |
that's exactly what I do
|
No. Just use an |
While implementing a ring buffer in JS is not a tremendous undertaking, the fact that we can't actually use one given the single-serving nature of the audio API's buffers makes it utterly pointless to do so as we have to continuously allocate and garbage collect new buffers in a single-threaded environment no matter what. AudioWorklets might solve for something someday when some browser vendor other than Google actually implements them, but right now there's pretty much no good solution to streaming incoming PCM data from a web socket to the audio API that works everywhere. That seems like a pretty massive oversight and there's certainly no shortage of complaints about this. If you have some kind of incredibly simple solution to this then you should absolutely document it somewhere rather than dismissively throwing "use a ring buffer" out there like that's some kind of answer. It's not, and none of this person's findings have really changed or improved that I can see: https://blog.mecheye.net/2017/09/i-dont-know-who-the-web-audio-api-is-designed-for/ |
Since then, we've been working on providing a good solution to solve this problem, but things take time and look easier than they are (we started working on this in #113, quite a long time ago). As far as using ring buffers and such with |
This comment was marked as off-topic.
This comment was marked as off-topic.
I second the reddit thread when it comes to
It would be interesting to see whether those AudioWorklets are actually used. As a side question, why not simply expose |
Seems like #2423 would solve most of the problems, at least in my case |
This comment was marked as off-topic.
This comment was marked as off-topic.
@guest271314 your example works only in chrome. |
If anyone is running into this issue and needs to stream audio binary in chunks and append them to a play buffer: I found a good example thanks to this stackoverflow question: The example: |
Describe the feature
Currently, if I have a source of PCM, the most suitable way to feed them into WebAudio pipe is via
AudioBufferSourceNode
andAudioBuffer
.While it works well if I have the whole buffer upfront, there is no way (or I didn't find) to feed PCM data as soon as I receive it. It's useful for low-latency streaming. Once a buffer is assigned to
AudioBufferSourceNode
, it can't be changed (https://stackoverflow.com/questions/20134384/web-audio-api-how-to-play-a-stream-of-mp3-chunks/20136399). I suspect, when I'll replace a buffer, it will occur glitches in sound.In streaming applications there are no full PCM upfront, but they receive portions via communication channel (WebSocket or so), so there should be a way to feed PCM by portions.
Is there a prototype?
It'd be cool if there was a method like
AudioBuffer.append
to append a new portion of PCM.Describe the feature in more detail
The current way to feed data dynamically is to use either
createScriptProcessor
or pass buffers toAudioWorkletProcessor
viaMessagePort
. Both ways require a subtle and well-tuned speed of feeding:The text was updated successfully, but these errors were encountered: