如何播放RAW音频文件?

时间:2020-05-29 19:36:36

标签: javascript audio html5-audio web-audio-api raw-data

我目前正在一个项目中,该项目包含一个图表,该图表显示了另一台设备拾取的音频电平。图表是通过flot API制作的,我具有缩放和选择功能,以便在图表上选择时间范围并放大到所选区域。我的下一步是允许用户收听与图表的该区域相对应的音频。我将音频文件存储在共享服务器上,并且所有文件都单独,逐分钟地存储在RAW数据文件中。我没有在网页中使用音频的经验,目前正在努力完成此任务。据我所知,<audio> HTML标签无法处理RAW数据文件进行播放。我一直在研究Web Audio API,但对它的工作方式和实现方式感到困惑。

我的第一个问题是如何从服务器解码RAW音频文件并将其显示在HTML页面上以供客户端收听?

我的第二个任务是获取与选定范围相对应的所有音频文件,并将它们组合为一个音频输出。例如,如果客户端选择的时间范围为1:00 pm-1:50 pm,则我需要每分钟长度访问50个RAW数据音频文件。然后,我想将它们组合在一起以产生一个单一的播放声音。因此,我的第二个问题是,是否有人知道一种顺利完成此操作的方法。

感谢您提供任何帮助!

2 个答案:

答案 0 :(得分:4)

RAW文件已经被解码为PCM音频,但是Audio元素不能直接播放PCM。您首先需要将RIFF / WAV标头附加到PCM字节。可以合并多个RAW文件,在标题中设置总样本/帧长度。 50分钟的解码音频将在浏览器中占用大量内存,因此请密切注意并相应地进行测量/优化。

initAudio()

async function initAudio() {
  // specify your file and its audio properties
  const url = 'https://dev.anthum.com/audio-worklet/audio/decoded-left.raw'
  const sampleRate = 48000
  const numChannels = 1 // mono or stereo
  const isFloat = true  // integer or floating point

  const buffer = await (await fetch(url)).arrayBuffer()

  // create WAV header
  const [type, format] = isFloat ? [Float32Array, 3] : [Uint8Array, 1] 
  const wavHeader = new Uint8Array(buildWaveHeader({
    numFrames: buffer.byteLength / type.BYTES_PER_ELEMENT,
    bytesPerSample: type.BYTES_PER_ELEMENT,
    sampleRate,
    numChannels,
    format
  }))

  // create WAV file with header and downloaded PCM audio
  const wavBytes = new Uint8Array(wavHeader.length + buffer.byteLength)
  wavBytes.set(wavHeader, 0)
  wavBytes.set(new Uint8Array(buffer), wavHeader.length)

  // show audio player
  const audio = document.querySelector('audio')
  const blob = new Blob([wavBytes], { type: 'audio/wav' })
  audio.src = URL.createObjectURL(blob)

  document.querySelector('#loading').hidden = true
  audio.hidden = false
}


// adapted from https://gist.github.com/also/900023
function buildWaveHeader(opts) {
  const numFrames =      opts.numFrames;
  const numChannels =    opts.numChannels || 2;
  const sampleRate =     opts.sampleRate || 44100;
  const bytesPerSample = opts.bytesPerSample || 2;
  const format =         opts.format

  const blockAlign = numChannels * bytesPerSample;
  const byteRate = sampleRate * blockAlign;
  const dataSize = numFrames * blockAlign;

  const buffer = new ArrayBuffer(44);
  const dv = new DataView(buffer);

  let p = 0;

  function writeString(s) {
    for (let i = 0; i < s.length; i++) {
      dv.setUint8(p + i, s.charCodeAt(i));
    }
    p += s.length;
}

  function writeUint32(d) {
    dv.setUint32(p, d, true);
    p += 4;
  }

  function writeUint16(d) {
    dv.setUint16(p, d, true);
    p += 2;
  }

  writeString('RIFF');              // ChunkID
  writeUint32(dataSize + 36);       // ChunkSize
  writeString('WAVE');              // Format
  writeString('fmt ');              // Subchunk1ID
  writeUint32(16);                  // Subchunk1Size
  writeUint16(format);              // AudioFormat
  writeUint16(numChannels);         // NumChannels
  writeUint32(sampleRate);          // SampleRate
  writeUint32(byteRate);            // ByteRate
  writeUint16(blockAlign);          // BlockAlign
  writeUint16(bytesPerSample * 8);  // BitsPerSample
  writeString('data');              // Subchunk2ID
  writeUint32(dataSize);            // Subchunk2Size

  return buffer;
}
body {
  text-align: center;
  padding-top: 1rem;
}
[hidden] {
  display: none;
}
audio {
  display: inline-block;
}
<div id="loading">Loading...</div>
<audio hidden controls></audio>

答案 1 :(得分:0)

使用Web Audio可能更容易一些,您基本上可以执行与上述相同的操作,但不要使用Audio元素。如有必要,将原始音频数据转换为浮点数组,例如f,然后执行以下操作:

// Only need to do this once when setting up the page
let c = new AudioContext();

// Do this for each clip:
let b = new AudioBuffer({length: f.length, sampleRate: c.sampleRate});
b.copyToChannel(f, 0);
let s = new AudioBufferSourceNode(c, {buffer: b});
s.connect(c.destination);
s.start();

这是如何使用Web Audio进行播放的粗略草图。可以对其进行优化以重用AudioBuffers。而且,您必须注意以正确的时间值调用s.start()。但我希望这足以使您入门。如果没有,请提出更多问题。