Web Audio API - Javascript创建的WAV文件长度不正确且无声

时间:2014-12-22 22:49:20

标签: javascript audio wav web-audio

问题
Javascript创建的WAV文件长度不正确且无声。

详情
我一直在使用JavaScript Web Audio API来创建一个Web应用程序,它可以获取多个声音文件,抓取每个声音文件的随机块,然后"混合"它们一起成为一个采样器(连续地,即file1 + file2 + ... + fileN),就像各种混搭一样。声音可以成功加载到我的自定义SoundPlayer对象中,并且可以播放它们。但是,当您实际将它们混合在一起时,生成的WAV文件长度错误并且完全无声。

界面允许最多10个声音加载并以自己的音量播放。您单击一个按钮将它们的采样器WAV组合在一起,并动态创建一个下载它的链接。我还有一种方法可以看到生成的文件的十六进制转储,并且它在底部显示了一堆00甚至一些NaN,所以显然我的算法存在缺陷,但我无法弄清楚如何。< / p>

当您点击&#34; Make Sampler!&#34;按钮,它运行以下函数(带有一个自定义SoundPlayer对象数组,其中包含音频缓冲区作为其参数):

function createSampler(sndArr) {
  var numberOfChannels = _getSoundChannelsMin(sndArr);
  var sndLengthSum = (function() {
    var lng = 0;
    for (var i = 0; i < sndArr.length; i++) {
      lng += sndArr[i].audioBuffer.length;
    }
    return lng;
  })();

  var samplerBuffer = getAudioContext().createBuffer(
    numberOfChannels,
    sndLengthSum,
    sndArr[0].audioBuffer.sampleRate
  );

  for (var i = 0; i < numberOfChannels; i++) {
    var channel = samplerBuffer.getChannelData(i);
    channel.set(sndArr[0].audioBuffer.getChannelData(i), 0);
    for (var j = 1; j < sndArr.length; j++) {
       channel.set(sndArr[j].audioBuffer.getChannelData(i), sndArr[j-1].audioBuffer.length);
    }
  }

  // encode our newly made audio blob into a wav file
  var dataView = _encodeWavFile(samplerBuffer, samplerBuffer.sampleRate);
  var audioBlob = new Blob([dataView], { type : 'audio/wav' });

 // post new wav file to download link
 _enableDownload(audioBlob);
}

使用此功能获取通道数(单声道/立体声/等):

function _getSoundChannelsMin(sndArr) {
  var sndChannelsArr = [];
  sndArr.forEach(function(snd) {
    sndChannelsArr.push(snd.audioBuffer.numberOfChannels);
  });
  return Math.min.apply(Math, sndChannelsArr);
}

WAV使用此功能进行编码:

function _encodeWavFile(samples, sampleRate) {
  var buffer = new ArrayBuffer(44 + samples.length * 2);
  var view = new DataView(buffer);

  // RIFF identifier
  _writeString(view, 0, 'RIFF');
  // file length
  view.setUint32(4, 36 + samples.length * 2, true);
  // RIFF type
  _writeString(view, 8, 'WAVE');
  // format chunk identifier
  _writeString(view, 12, 'fmt ');
  // format chunk length
  view.setUint32(16, 16, true);
  // sample format (raw)
  view.setUint16(20, 1, true);
  // stereo (2 channels)
  view.setUint16(22, 2, true);
  // sample rate
  view.setUint32(24, sampleRate, true);
  // byte rate (sample rate * block align)
  view.setUint32(28, sampleRate * 4, true);
  // block align (channels * bytes/sample)
  view.setUint16(32, 4, true);
  // bits/sample
  view.setUint16(34, 16, true);
  // data chunk identifier
  _writeString(view, 36, 'data');
  // data chunk length
  view.setUint32(40, samples.length * 2, true);
  // write the PCM samples
  _writePCMSamples(view, 44, samples);

  return view;
}

WAV文件中的字符串由此处理:

function _writeString(view, offset, string) {
  for (var i = 0; i < string.length; i++){
    view.setUint8(offset + i, string.charCodeAt(i));
  }
}

PCM样本交付:

function _writePCMSamples(output, offset, input) {
  for (var i = 0; i < input.length; i++, offset+=2){
    var s = Math.max(-1, Math.min(1, input[i]));
    output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
  }
}

最后,WAV文件变成了一个链接:

function _enableDownload(blob, givenFilename) {
  var url = (window.URL || window.webkitURL).createObjectURL(blob);
  var link = document.getElementById("linkDownloadSampler");
  var d = new Date();
  var defaultFilename = "sampler" + d.curDateTime() + ".wav";
  link.style.display = "inline";
  link.href = url;
  link.download = givenFilename || defaultFilename;
}

这是我得到的十六进制转储的片段:

52 49 46 46 FFFD FFFD 02 00  57 41 56 45 66 6D 74 20  RIFF....WAVEfmt 
10 00 00 00 01 00 02 00  FFFD FFFD 00 00 00 FFFD 02 00  ................
04 00 10 00 64 61 74 61  FFFD FFFD 02 00 00 00 00 00  ....data........
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................

如果有人能帮助我看到我的方式错误,我会很感激。很抱歉这篇文章很长,但我想预先发布所有相关的详细信息和代码。

谢谢!

1 个答案:

答案 0 :(得分:1)

在写出波形数据时,你忽略了增加偏移量。

试试这个:

output.setInt16(offset + i, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
                      ^^^^

在不移动写入位置的情况下,您只需在文件中覆盖相同的偏移量,直到最后它将被最后一个样本的值覆盖。

另外,我发现你发布了_writePCMSamples代码,但调用者被注释掉了,你显然只是想尝试内联同样的事情。内联代码具有相同的错误。