通过Web audio API以分块音频进行断续/听不见的播放



我在上一篇文章中提到了这个问题,但由于它与最初的问题偏离了主题,我将单独发布。我很难通过网络音频播放传输的音频,就像在媒体播放器中播放一样。我尝试了两种不同的传输协议,binaryjs和socketio,在尝试通过Web音频播放时,两者都没有区别。为了排除音频数据的传输问题,我创建了一个示例,在从客户端接收到数据后,将数据发送回服务器,并将返回的数据转储到stdout。将其导入VLC会带来您期望听到的聆听体验。

要在播放vlc时听到结果(听起来应该是这样),请在https://github.com/grkblood13/web-audio-stream/tree/master/vlc使用以下命令:

$ node webaudio_vlc_svr.js | vlc -

无论出于什么原因,当我试图通过网络音频播放相同的音频数据时,它都失败了。结果是随机噪声,其间存在较大的静音间隙。

下面的代码出了什么问题,导致播放声音如此糟糕?

window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var delayTime = 0;
var init = 0;
var audioStack = [];
client.on('stream', function(stream, meta){
    stream.on('data', function(data) {
        context.decodeAudioData(data, function(buffer) {
            audioStack.push(buffer);
            if (audioStack.length > 10 && init == 0) { init++; playBuffer(); }
        }, function(err) {
            console.log("err(decodeAudioData): "+err);
        });
    });
});
function playBuffer() {
    var buffer = audioStack.shift();
    setTimeout( function() {
            var source    = context.createBufferSource();
            source.buffer = buffer;
            source.connect(context.destination);
            source.start(context.currentTime);
            delayTime=source.buffer.duration*1000; // Make the next buffer wait the length of the last buffer before being played
            playBuffer();
    }, delayTime);
}

完整来源:https://github.com/grkblood13/web-audio-stream/tree/master/binaryjs

你真的不能像那样只调用source.start(audioContext.currentTime)。

setTimeout()有一个长而不精确的延迟-其他主线程的事情可能正在进行,所以您的setTimeout)调用可能会延迟几毫秒,甚至是几十毫秒(通过垃圾收集、JS执行、布局…)您的代码正试图在一个有几十毫秒不精确性的计时器上立即播放音频——需要在大约0.02毫秒的精度内启动才能不会出现故障。

网络音频系统的全部意义在于,音频调度程序在一个单独的高优先级线程中工作,您可以以非常高的精度预先调度音频(开始、停止和音频参数更改)。您应该将系统重写为:

1) 跟踪第一个块是在音频上下文时间安排的——不要立即安排第一个块,给一些延迟,这样你的网络就有望跟上。

2) 基于其"下一个块"定时来调度将来接收的每个连续块。

例如(注意,我还没有测试过这个代码,这是我的想法):

window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var delayTime = 0;
var init = 0;
var audioStack = [];
var nextTime = 0;
client.on('stream', function(stream, meta){
    stream.on('data', function(data) {
        context.decodeAudioData(data, function(buffer) {
            audioStack.push(buffer);
            if ((init!=0) || (audioStack.length > 10)) { // make sure we put at least 10 chunks in the buffer before starting
                init++;
                scheduleBuffers();
            }
        }, function(err) {
            console.log("err(decodeAudioData): "+err);
        });
    });
});
function scheduleBuffers() {
    while ( audioStack.length) {
        var buffer = audioStack.shift();
        var source    = context.createBufferSource();
        source.buffer = buffer;
        source.connect(context.destination);
        if (nextTime == 0)
            nextTime = context.currentTime + 0.05;  /// add 50ms latency to work well across systems - tune this if you like
        source.start(nextTime);
        nextTime+=source.buffer.duration; // Make the next buffer wait the length of the last buffer before being played
    };
}

最新更新