8

我从事音频识别演示已经有一段时间了,api 需要我传递一个采样率为800016000的 .wav 文件,所以我必须对其进行下采样。我尝试了以下两种算法。尽管它们都没有像我希望的那样解决问题,但结果存在一些差异,我希望这会让它更清楚。

这是我的第一次尝试,当sampleRate % outputSampleRate = 0时它工作正常,但是当outputSampleRate = 8000 或 1600时,输出音频文件是无声的(这意味着输出数组的每个元素的值都是 0):

function interleave(inputL){
  var compression = sampleRate / outputSampleRate;
  var length = inputL.length / compression;
  var result = new Float32Array(length);

  var index = 0,
  inputIndex = 0;

  while (index < length){
    result[index++] = inputL[inputIndex];
    inputIndex += compression;
  }
  return result;
}

所以这是我的第二次尝试,它来自一家大公司,但它也不起作用。更重要的是,当我设置sampleRate % outputSampleRate = 0它仍然输出一个静默文件:

function interleave(e){
  var t = e.length;
  var n = new Float32Array(t),
    r = 0,
    i;
  for (i = 0; i < e.length; i++){
    n[r] = e[i];
    r += e[i].length;
  }
  sampleRate += 0.0;
  outputSampleRate += 0.0;
  var s = 0,
  o = sampleRate / outputSampleRate,
  u = Math.ceil(t * outputSampleRate / sampleRate),
  a = new Float32Array(u);
  for (i = 0; i < u; i++) {
    a[i] = n[Math.floor(s)];
    s += o;
  }

  return a
}

如果我的设置有误,这里是encodeWAV函数:

function encodeWAV(samples){
  var sampleBits = 16;
  var dataLength = samples.length*(sampleBits/8);

  var buffer = new ArrayBuffer(44 + dataLength);
  var view = new DataView(buffer);

  var offset = 0;

  /* RIFF identifier */
  writeString(view, offset, 'RIFF'); offset += 4;
  /* file length */
  view.setUint32(offset, 32 + dataLength, true); offset += 4;
  /* RIFF type */
  writeString(view, offset, 'WAVE'); offset += 4;
  /* format chunk identifier */
  writeString(view, offset, 'fmt '); offset += 4;
  /* format chunk length */
  view.setUint32(offset, 16, true); offset += 4;
  /* sample format (raw) */
  view.setUint16(offset, 1, true); offset += 2;
  /* channel count */
  view.setUint16(offset, outputChannels, true); offset += 2;
  /* sample rate */
  view.setUint32(offset, outputSampleRate, true); offset += 4;
  /* byte rate (sample rate * block align) */
  view.setUint32(offset, outputSampleRate*outputChannels*(sampleBits/8), true); offset += 4;
  /* block align (channel count * bytes per sample) */
  view.setUint16(offset, outputChannels*(sampleBits/8), true); offset += 2;
  /* bits per sample */
  view.setUint16(offset, sampleBits, true); offset += 2;
  /* data chunk identifier */
  writeString(view, offset, 'data'); offset += 4;
  /* data chunk length */
  view.setUint32(offset, dataLength, true); offset += 4;

  floatTo16BitPCM(view, offset, samples);

  return view;
}

困扰了我很长时间,请告诉我我错过了什么...

------------------解决后-------- --------------

我很高兴它现在运行良好,这是函数interleave()的正确版本:

    function interleave(e){
      var t = e.length;
      sampleRate += 0.0;
      outputSampleRate += 0.0;
      var s = 0,
      o = sampleRate / outputSampleRate,
      u = Math.ceil(t * outputSampleRate / sampleRate),
      a = new Float32Array(u);
      for (i = 0; i < u; i++) {
        a[i] = e[Math.floor(s)];
        s += o;
      }

      return a;
    }

所以你可以看到这是我传递给它的变量类型不正确~再次感谢亲爱的@jaket和其他朋友~虽然我自己想通了,但他们让我更好地了解了原来的东西~~~: )

4

3 回答 3

12

采样率转换不仅仅是简单地丢弃或插入样本。

让我们以 2 倍的下采样为例(例如 44100->22050)。一种天真的方法是丢弃所有其他样本。但是想象一下,在原始的 44.1kHz 文件中,有一个 20khz 的正弦波。该采样率在奈奎斯特 (fs/2=22050) 范围内。在您丢弃所有其他样本后,它仍然会以 10kHz 的频率存在,但现在它将高于 nyquist (fs/2=11025),并且它会混叠到您的输出信号中。最终结果是您将获得一个位于 8975 Hz 的大正弦波!

为了避免在下采样期间出现这种混叠,您需要首先设计一个低通滤波器,并根据您的抽取率选择截止。对于上面的示例,您将首先切断 11025 以上的所有内容,然后再抽取。

硬币的另一面称为上采样和插值。假设您想将采样率提高 2 倍。首先,您在每个输入样本之间插入零,然后运行插值滤波器来计算值以使用周围样本替换零。

速率变化通常涉及抽取和插值的某种组合 - 因为两者都通过整数个样本工作。以 48000->32000 为例。输出/输入比为 32000/48000 或 2/3。所以你可以将 48000 上采样 2 得到 96000,然后再下采样 3 到 32000。另一件事是你可以将这些过程链接在一起。因此,如果您想从 48000->16000 上升,则上升 3,下降 2,下降 2。此外,44100 特别困难。例如,要从 48000->44100 移动,您需要上升 147,下降 160,并且不能将其分解为更小的术语。

我建议您找到一些代码或库来为您执行此操作。您需要寻找的是多相滤波器或采样率转换器。

于 2015-08-04T23:49:23.617 回答
0

问题是您正在尝试使用浮点数访问数组。当您访问inputL[5.5125]它时,它与 相同input['5.5125'],即您将尝试5.5125从数组对象中读取一个名为的属性,而不是从数组数据中读取一个项目。

将数字四舍五入以获得最接近的整数索引:

function interleave(inputL){
  var compression = sampleRate / outputSampleRate;
  var length = inputL.length / compression;
  var result = new Float32Array(length);

  var index = 0,
  inputIndex = 0;

  while (index < length){
    result[index++] = inputL[Math.round(inputIndex)];
    inputIndex += compression;
  }
  return result;
}
于 2015-08-05T00:01:01.930 回答
0

@jacket 说的是真的,你不能仅仅通过减少数字来对音频进行下采样。数组中的项目,我能想到的两种方法是:

  1. 如果您不特别关注wav哪种是未压缩格式并且会耗尽您的带宽,您可以尝试我为录制为 mp3 文件而编写的这个小实用程序,只需修改中的行scripts/recorder.js

     config: {
        sampleRate: this.context.sampleRate
      }
    

      config: {
        sampleRate: 16000 // or any other sampling rate
      }
    
  2. 另一种选择是,如果您已经在做某种音频处理后端,并且不介意将 ffmpeg 添加到堆栈中,您可以将 wav 文件(未压缩格式)/ogg 文件(压缩格式,代码)发送到服务器,在那里你可以在进行其余处理之前使用 ffmpeg 将其更改为你喜欢的任何格式,使用你想要的任何采样率。

于 2015-08-05T02:11:00.723 回答