原始音频数据流式传输期间网络音频播放出现裂纹
Cracks in webaudio playback during streaming of raw audio data
我有一个服务器通过 websocket 发送大块的原始音频。我们的想法是检索它们并以尽可能流畅的方式播放它们。
这是最重要的一段代码:
ws.onmessage = function (event) {
var view = new Int16Array(event.data);
var viewf = new Float32Array(view.length);
audioBuffer = audioCtx.createBuffer(1, viewf.length, 22050);
audioBuffer.getChannelData(0).set(viewf);
source = audioCtx.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioCtx.destination);
source.start(0);
};
这很好用,但播放时有一些问题:网络延迟并不总是恒定的,所以最新的数据块不会恰好在前一个正在播放的结束时到达,所以我最终可能是两个缓冲区一起播放很短的时间,或者 none 根本不播放。
我试过了:
- 挂钩
source.onended
播放下一个,但它不是无缝的:每个块的末尾都有裂缝,每个接缝都在整体上累积,所以播放越来越晚流。
- 将新数据附加到当前播放的缓冲区,但这似乎是被禁止的:缓冲区的大小是固定的。
是否有适当的解决方案来解决播放问题?唯一的要求是播放来自 websocket 的未压缩音频。
编辑:解决方案: 如果我知道我的缓冲区长度,我可以这样安排播放:
if(nextStartTime == 0) nextStartTime = audioCtx.currentTime + (audioBuffer.length / audioBuffer.sampleRate)/2;
source.start(nextStartTime);
nextStartTime += audioBuffer.length / audioBuffer.sampleRate;
第一次,我将回放的开始时间安排在半个缓冲区之后,以允许最大的意外延迟。然后,我将下一个缓冲区开始时间存储在缓冲区结束的最后。
您可能应该从 https://www.html5rocks.com/en/tutorials/audio/scheduling/ 开始,它很好地解释了如何在 WebAudio 中安排事情。
对于您的用例,您还应该利用这样一个事实,即您知道 PCM 样本的采样率并且知道您读取了多少样本。这决定了播放缓冲区需要多长时间。用它来确定何时安排下一个缓冲区。
(但请注意,如果 PCM 采样率与 audioCtx.sampleRate
不同,数据将被重新采样,这可能会打乱您的时间。
现在有更好的方法来处理这个问题...考虑使用媒体源扩展。
- 规格:https://www.w3.org/TR/media-source/
- MDN:https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
无需安排缓冲区并自行完成所有操作,您基本上将接收到的数据转储到缓冲区中,让浏览器担心缓冲播放,就好像它是 HTTP 上的文件一样。
Chrome支持播放WAV文件。由于您的数据是原始 PCM 格式,因此您需要伪装 WAV 文件头。幸运的是,这并不难:http://soundfile.sapp.org/doc/WaveFormat/
我在回答中解决了这个问题 。请参阅它以获取更多信息和大量代码。
快速总结:
You have to create a new AudioBuffer and AudioBufferSourceNode both (or at least the latter) for every piece of data that you want to buffer... I tried looping the same AudioBuffer, but once you set .audioBuffer
on the AudioContext, any modifications you make to the AudioBuffer become irrelevant.
(NOTE: These classes have base/parent classes you should look at as well (referenced in the docs).)
我有一个服务器通过 websocket 发送大块的原始音频。我们的想法是检索它们并以尽可能流畅的方式播放它们。
这是最重要的一段代码:
ws.onmessage = function (event) {
var view = new Int16Array(event.data);
var viewf = new Float32Array(view.length);
audioBuffer = audioCtx.createBuffer(1, viewf.length, 22050);
audioBuffer.getChannelData(0).set(viewf);
source = audioCtx.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioCtx.destination);
source.start(0);
};
这很好用,但播放时有一些问题:网络延迟并不总是恒定的,所以最新的数据块不会恰好在前一个正在播放的结束时到达,所以我最终可能是两个缓冲区一起播放很短的时间,或者 none 根本不播放。
我试过了:
- 挂钩
source.onended
播放下一个,但它不是无缝的:每个块的末尾都有裂缝,每个接缝都在整体上累积,所以播放越来越晚流。 - 将新数据附加到当前播放的缓冲区,但这似乎是被禁止的:缓冲区的大小是固定的。
是否有适当的解决方案来解决播放问题?唯一的要求是播放来自 websocket 的未压缩音频。
编辑:解决方案: 如果我知道我的缓冲区长度,我可以这样安排播放:
if(nextStartTime == 0) nextStartTime = audioCtx.currentTime + (audioBuffer.length / audioBuffer.sampleRate)/2;
source.start(nextStartTime);
nextStartTime += audioBuffer.length / audioBuffer.sampleRate;
第一次,我将回放的开始时间安排在半个缓冲区之后,以允许最大的意外延迟。然后,我将下一个缓冲区开始时间存储在缓冲区结束的最后。
您可能应该从 https://www.html5rocks.com/en/tutorials/audio/scheduling/ 开始,它很好地解释了如何在 WebAudio 中安排事情。
对于您的用例,您还应该利用这样一个事实,即您知道 PCM 样本的采样率并且知道您读取了多少样本。这决定了播放缓冲区需要多长时间。用它来确定何时安排下一个缓冲区。
(但请注意,如果 PCM 采样率与 audioCtx.sampleRate
不同,数据将被重新采样,这可能会打乱您的时间。
现在有更好的方法来处理这个问题...考虑使用媒体源扩展。
- 规格:https://www.w3.org/TR/media-source/
- MDN:https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
无需安排缓冲区并自行完成所有操作,您基本上将接收到的数据转储到缓冲区中,让浏览器担心缓冲播放,就好像它是 HTTP 上的文件一样。
Chrome支持播放WAV文件。由于您的数据是原始 PCM 格式,因此您需要伪装 WAV 文件头。幸运的是,这并不难:http://soundfile.sapp.org/doc/WaveFormat/
我在回答中解决了这个问题
快速总结:
You have to create a new
AudioBuffer andAudioBufferSourceNodeboth (or at least the latter)for every piece of data that you want to buffer... I tried looping the same AudioBuffer, but once you set.audioBuffer
on the AudioContext, any modifications you make to the AudioBuffer become irrelevant.(NOTE: These classes have base/parent classes you should look at as well (referenced in the docs).)