假设我有1秒的音频数据,标准采样率为44100 Hz,我想进行实时处理,因此我将其连接到分析器节点(audioContext.createAnalyser
)。我知道,如果我执行analyser.fftSize = 2048,则analyser.getFloatTimeDomainData(buffer)
将从原始信号中将2048个float值放入我的Float32Array参数数组缓冲区中,因此是44100个样本中的2048个。如果我通过window.requestAnimationFrame
(R.A.F.)重复进行此操作,则接下来的2048个样本通常会重叠。因为可能是(通常为每秒60帧)1000/60 = 16.66毫秒后,也就是44100/60 = 735个音频样本,所以,如果以后exactly 735个样本,则下一个缓冲区将重复前一个缓冲区的2048-735 = 1313个后续样本,然后给出735个新样本。
我想知道的是,我如何在运行时找出如何将出现的缓冲区序列“缝合在一起”。我当然想避免存储先前的缓冲区,然后在长循环中找出当前缓冲区和先前的缓冲区的重叠区域是什么。理想情况下,我只想在调用audioContext.currentTime
的确切时间查看analyser.getFloatTimeDomainData(buffer)
,并且应该容易地据此计算偏移量(重叠量)(而上一个.currentTime
皇家空军)。我尝试过,但似乎做错了。我正在通过.currentTime
计算的偏移量似乎经常是正确的,但并非总是如此。
我想确切地知道如何将缓冲区“缝合在一起”的原因是,我可能想进行涉及超过2048个样本的实时计算,或者我可能希望将后续缓冲区的结果合并到同一可视化中。] >
var soundFile = 'https://mathheadinclouds.github.io/audio/sounds/la.mp3';
var audioContext = null;
var isPlaying = false;
var sourceNode = null;
var analyser = null;
var theBuffer = null;
var reconstructedBuffer = null;
var soundRequest = null;
var loopCounter = -1;
var FFT_SIZE = 2048;
var rafID = null;
var buffers = [];
var timesSamples = [];
var timeSampleDiffs = [];
var leadingWaste = 0;
window.addEventListener('load', function() {
soundRequest = new XMLHttpRequest();
soundRequest.open("GET", soundFile, true);
soundRequest.responseType = "arraybuffer";
//soundRequest.onload = function(evt) {}
soundRequest.send();
var btn = document.createElement('button');
btn.textContent = 'go';
btn.addEventListener('click', function(evt) {
goButtonClick(this, evt)
});
document.body.appendChild(btn);
});
function goButtonClick(elt, evt) {
initAudioContext(togglePlayback);
elt.parentElement.removeChild(elt);
}
function initAudioContext(callback) {
audioContext = new AudioContext();
audioContext.decodeAudioData(soundRequest.response, function(buffer) {
theBuffer = buffer;
callback();
});
}
function createAnalyser() {
analyser = audioContext.createAnalyser();
analyser.fftSize = FFT_SIZE;
}
function startWithSourceNode() {
sourceNode.connect(analyser);
analyser.connect(audioContext.destination);
sourceNode.start(0);
isPlaying = true;
sourceNode.addEventListener('ended', function(evt) {
sourceNode = null;
analyser = null;
isPlaying = false;
loopCounter = -1;
window.cancelAnimationFrame(rafID);
console.log('buffer length', theBuffer.length);
console.log('reconstructedBuffer length', reconstructedBuffer.length);
console.log('audio callback called counter', buffers.length);
console.log('root mean square error', Math.sqrt(checkResult() / theBuffer.length));
console.log('lengths of time between requestAnimationFrame callbacks, measured in audio samples:');
console.log(timeSampleDiffs);
console.log(
timeSampleDiffs.filter(function(val) {
return val === 384
}).length,
timeSampleDiffs.filter(function(val) {
return val === 512
}).length,
timeSampleDiffs.filter(function(val) {
return val === 640
}).length,
timeSampleDiffs.filter(function(val) {
return val === 768
}).length,
timeSampleDiffs.filter(function(val) {
return val === 896
}).length,
'*',
timeSampleDiffs.filter(function(val) {
return val > 896
}).length,
timeSampleDiffs.filter(function(val) {
return val < 384
}).length
);
console.log(
timeSampleDiffs.filter(function(val) {
return val === 384
}).length +
timeSampleDiffs.filter(function(val) {
return val === 512
}).length +
timeSampleDiffs.filter(function(val) {
return val === 640
}).length +
timeSampleDiffs.filter(function(val) {
return val === 768
}).length +
timeSampleDiffs.filter(function(val) {
return val === 896
}).length
)
});
myAudioCallback();
}
function togglePlayback() {
sourceNode = audioContext.createBufferSource();
sourceNode.buffer = theBuffer;
createAnalyser();
startWithSourceNode();
}
function myAudioCallback(time) {
++loopCounter;
if (!buffers[loopCounter]) {
buffers[loopCounter] = new Float32Array(FFT_SIZE);
}
var buf = buffers[loopCounter];
analyser.getFloatTimeDomainData(buf);
var now = audioContext.currentTime;
var nowSamp = Math.round(audioContext.sampleRate * now);
timesSamples[loopCounter] = nowSamp;
var j, sampDiff;
if (loopCounter === 0) {
console.log('start sample: ', nowSamp);
reconstructedBuffer = new Float32Array(theBuffer.length + FFT_SIZE + nowSamp);
leadingWaste = nowSamp;
for (j = 0; j < FFT_SIZE; j++) {
reconstructedBuffer[nowSamp + j] = buf[j];
}
} else {
sampDiff = nowSamp - timesSamples[loopCounter - 1];
timeSampleDiffs.push(sampDiff);
var expectedEqual = FFT_SIZE - sampDiff;
for (j = 0; j < expectedEqual; j++) {
if (reconstructedBuffer[nowSamp + j] !== buf[j]) {
console.error('unexpected error', loopCounter, j);
// debugger;
}
}
for (j = expectedEqual; j < FFT_SIZE; j++) {
reconstructedBuffer[nowSamp + j] = buf[j];
}
//console.log(loopCounter, nowSamp, sampDiff);
}
rafID = window.requestAnimationFrame(myAudioCallback);
}
function checkResult() {
var ch0 = theBuffer.getChannelData(0);
var ch1 = theBuffer.getChannelData(1);
var sum = 0;
var idxDelta = leadingWaste + FFT_SIZE;
for (var i = 0; i < theBuffer.length; i++) {
var samp0 = ch0[i];
var samp1 = ch1[i];
var samp = (samp0 + samp1) / 2;
var check = reconstructedBuffer[i + idxDelta];
var diff = samp - check;
var sqDiff = diff * diff;
sum += sqDiff;
}
return sum;
}
在上面的代码段中,我执行以下操作。我从github.io页面上以XMLHttpRequest
加载了一个1秒的mp3音频文件(我唱'la'1秒)。加载后,显示一个按钮,说“ go”,按下该按钮后,将音频放入bufferSource节点中,然后对其进行.start
来播放音频。 bufferSource被馈送到我们的分析器,该分析器又连接到扬声器。然后,我用RAF反复调用myAudioCallback
。在myAudioCallback
中,我查看了2048个“当前”样本。我试图从每个长度为2048的重叠片段中重新组装原始的长缓冲区(少了44100个样本,因为声音略大于1秒),然后将其称为数组reconstructedBuffer
。逻辑告诉我Math.round(audioContext.sampleRate * audioContext.currentTime)
的结果应该告诉我“样本中测量的时间”,两个后续值“样本中测量的时间”(后续=后续的RAF调用)之差应给出重叠量(而不是,则“新”样本的数量,然后重叠为2048减去“新”。)
或者上面的“逻辑”有问题,或者我做错了;因为在Firefox中,我经常会遇到“意外错误”,这意味着计算出的重叠不存在。在Chrome中,逻辑似乎适用。
接下来,在checkResult
中,我将重建的缓冲区与原始声音缓冲区进行比较。它应该完全相同,但是几乎相同。 “四舍五入误差”相当大。特别是在Firefox中。在Chrome浏览器中可以正常工作。均方根误差仍然比预期的大一些。
是否有更简单的方法来找出如何将缓冲区“缝合在一起”? Firefox中发生了什么?这是.currentTime
中的错误,还是我的逻辑有缺陷?这个巨大的rms错误是关于什么的(尤其是在Firefox中)? .getFloatTimeDomainData
是否应该是原始数据的简单副本?为什么新样本的数量(平均735个)如此“跳跃”?我会期望很多768 = 6 * 128和一些640 = 5 * 128。相反,我们得到了更多的差异。
我也有代码段on my github.io page-使阅读控制台更容易。
假设我有1秒的音频数据,标准采样率为44100 Hz,我想进行实时处理,因此我将其连接到分析器节点(audioContext.createAnalyser)。我...
[遗憾的是,无法找到捕获由AnalyserNode
返回的数据的确切时间点。但是您可能会沿着目前的方法走上正确的轨道。