播放 queue 的 audioBufferSourceNodes 时出现奇怪 micro-delays

Strange micro-delays while playing queue of audioBufferSourceNodes

我将从一个想法开始。我想建立一种机制,允许我加载每个 6144 字节的音频块。然后,我想播放存储所有这些文件块的数组中的所有块。

当播放 audioBufferSourceNodes 时,我有一些奇怪的延迟,不知道如何解决。


我从我的 websocket 服务器获得的那些音频文件块,用 Python、Django-Channels.

编写

我的变量:

const chatSocket = new WebSocket(...);
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var source;
var play = document.querySelector('#play');
var audioQueue = [];

接下来,当我从服务器接受带有块的消息时,我将其解码并放入 queue:

chatSocket.onmessage = function(e) {
    e.data.arrayBuffer().then(buffer => {
        audioCtx.decodeAudioData(buffer, (x)=>{
            source = audioCtx.createBufferSource();
            source.buffer = x;
            source.connect(audioCtx.destination);
            audioQueue.push(source);
        })
    })
}

最后一件事是,播放我收到的所有块。为此,我使用了这一部分:

play.onclick = function() {
    var whenStart = 0;
    for (let audioBufferSourceNode of audioQueue) {
        whenStart = audioBufferSourceNode.buffer.duration + whenStart;
        audioBufferSourceNode.start(when=whenStart);
    }
}

就是这样。上面的代码启动音频,它很棒,但是,正如我在标题中所写:奇怪 micro-delays 不要给我任何和平。

This audio

很遗憾,您无法通过这种方式进行流式传输。有损音频编解码器并不总是保证它们将在特定边界开始和结束。甚至 MP3(看起来您正在使用)也使用 a bit reservoir,它将特定帧的数据传播到其他帧中未使用的 space。你不能以这种方式使用 decodeAudioData 除非你使用的编解码器可以保证样本的准确性。我知道您可以使用 in-browser 执行此操作的唯一编解码器是常规 PCM,或一些无损编解码器,如 FLAC。

假设您确实进行了 sample-accurate 解码,您仍然有播放调度问题。 duration 以秒为单位,但这并不总是很好地划分为适当的采样率。 44.1 kHz 的单个样本持续时间为 0.022675736961... 毫秒。没有样本准确性,您将无法正确计时播放块。在这里和那里放置样本可以听到。

那么,怎么办?有几种方法可以解决这个问题...

方法 1:使用 HTTP 和 <audio> 元素

浏览器能够很好地处理解码音频数据、适当缓冲、重采样以及流播放所需的一切。它非常有效地完成了这一切。与其尝试重新发明整个堆栈,不如让浏览器来处理它。

将您的 Python 服务器设置为提供 HTTP 而不是 WebSocket。然后,在您的客户端上,这很简单:

<audio src="https://example.com/your-python-script/perhaps-some-stream-id" preload="none"></audio>

在您的 MP3 示例中,您所要做的就是发送具有 audio/mpegContent-Type header 的 MP3 数据。您甚至不必从帧边界开始...MPEG 流是 self-syncing.

方法 2:使用 MediaSource 扩展

如果您必须通过 WebSocket 发送数据,您仍然可以让浏览器进行解码和播放。您可以使用的编解码器和容器格式有很多限制,但它确实适合您的用例。

这没有简单的例子,所以我会 link 你看一些文档:https://developer.mozilla.org/en-US/docs/Web/API/MediaSource

方法 3:使用 [一些解码器] 和 ScriptProcessingNode 或 AudioWorkletNode

ScriptProcessingNode or AudioWorkletNode 可用于在不丢失样本的情况下在正确的时间播放音频。如果您可以将音频解码为 float32 PCM 样本,并重新采样为当前图形播放采样率,那么您可以让此脚本节点使用 audioprocess 事件处理程序输出下一个块。

解码和重采样并非易事 client-side,并且可以处理 CPU。所以一般不推荐这种方式。

除了这些方式,还有更适合语音通话的WebRTC。由于您正在播放流媒体音乐,因此不太推荐这样做,因为它会在 realtime-ness 和质量之间做出权衡。