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

Web Audio API - Javascript-created WAV file incorrect length and silent

本文关键字:文件 不正确 无声 WAV API Audio Javascript 创建 Web      更新时间:2023-09-26

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


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

该界面允许以自己的音量加载和播放多达 10 种声音。您单击一个按钮将它们一起制作采样器 WAV,它会动态创建一个链接来下载它。我也有办法查看结果文件的十六进制转储,它在底部显示一堆 00 甚至一些 NaN,所以显然我的算法有缺陷,但我只是不知道如何。

当您单击"制作采样器!"按钮时,它将运行以下函数(使用包含音频缓冲区的自定义 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  ................

如果有人能帮助我看到我的方式的错误,我将不胜感激。很抱歉这篇文章很长,但我想提前发布所有相关细节和代码。

谢谢!

您忽略了在写出波次数据时增加偏移量。

试试这个:

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

如果不移动写入位置,您只会继续覆盖文件中的相同偏移量,直到最终它被最后一个样本的值覆盖。

另外,我看到您已经发布了_writePCMSamples代码,但调用者被注释掉了,您显然只是想内联做同样的事情。内联代码具有相同的错误。