Web音频分析器的getFloatTimeDomainData缓冲区偏移量,其他时间为wrt缓冲区,“完整文件”的wrt缓冲区为

问题描述 投票:0回答:1

假设我有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。相反,我们得到了更多的差异。

related question

我也有代码段on my github.io page-使阅读控制台更容易。

假设我有1秒的音频数据,标准采样率为44100 Hz,我想进行实时处理,因此我将其连接到分析器节点(audioContext.createAnalyser)。我...

javascript html5-audio audio-streaming audio-recording web-audio-api
1个回答
0
投票

[遗憾的是,无法找到捕获由AnalyserNode返回的数据的确切时间点。但是您可能会沿着目前的方法走上正确的轨道。

© www.soinside.com 2019 - 2024. All rights reserved.