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

Posted

技术标签:

【中文标题】Web Audio API - Javascript 创建的 WAV 文件长度不正确且无声【英文标题】:Web Audio API - Javascript-created WAV file incorrect length and silent 【发布时间】:2014-12-22 22:49:19 【问题描述】:

问题 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;

这是我得到的十六进制转储的 sn-p:

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】:

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

试试这个:

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

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

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

【讨论】:

我更新了我的代码并删除了注释掉的_writePCMSamples,因为我忘记了我后来又添加了它。另外,我已经在_writePCMSamples 函数中更新了offset:它在i++ 之后。无论如何,我尝试删除它并添加您的想法,但我得到了相同的结果。

以上是关于Web Audio API - Javascript 创建的 WAV 文件长度不正确且无声的主要内容,如果未能解决你的问题,请参考以下文章

Web RTC + audio API 实现录音,并压缩

暂停 Web Audio API 声音播放

使用 Web Audio API 为声音的开头添加静音 [关闭]

Web Audio Api 与 Web Speech Api 集成 - 将扬声器/声卡输出流式传输到语音识别 api

Web Audio API 如何命名声音

如何在 Web Audio API 中将 SoundManager2 定义为媒体元素源