下调PCM音频从44100到8000

时间:2015-08-04 19:54:59

标签: javascript audio pcm downsampling

我一直在研究音频识别演示,而api需要我传递一个.wav文件,其采样率 8000 16000 所以我必须对它进行下采样。我尝试了2种算法如下。虽然它们都没有按我的意愿解决问题,但结果存在一些差异,我希望这会使其更加清晰。

这是我的第一次尝试,当 sampleRate%outputSampleRate = 0 时它可以正常工作,但是当 outputSampleRate = 8000或1600 时,结果音频文件 silent (表示输出数组的每个元素的值都是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和其他朋友〜虽然我想到了myslf,但他们让我更了解原始的东西~~~:)

3 个答案:

答案 0 :(得分:7)

采样率转换还有很多,而不仅仅是丢弃样品或插入样品。

让我们采用简单的下采样情况2倍(例如44100-> 22050)。一种天真的方法就是扔掉所有其他样本。但想象一下,在原始的44.1kHz文件中,有一个正弦波存在于20khz。对于该采样率,它在奈奎斯特(fs / 2 = 22050)内。在你扔掉所有其他样品之后,它仍然会在10kHz处存在,但现在它将高于nyquist(fs / 2 = 11025)并且它将混叠到输出信号中。最后的结果是你将有一个大的正弦波,频率为8975赫兹!

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

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

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

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

答案 1 :(得分:0)

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

对数字进行舍入,以便得到最接近的整数索引:

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;
}

答案 2 :(得分:0)

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

  1. 如果您不是特别关注wav未压缩的格式并且会耗尽您的带宽,您可以尝试使用small utility我写的录制为mp3文件,只需修改{ {1}}

    scripts/recorder.js

     config: {
        sampleRate: this.context.sampleRate
      }
    
  2. 另一种选择是,如果您已经在进行某种音频处理后端,并且不介意将ffmpeg添加到堆栈中,您可以发送wav文件(未压缩格式)/ ogg文件(压缩文件)格式,code)到服务器,在那里你可以使用ffmpeg将其改为你喜欢的任何格式,然后再进行其余的处理。