如何播放 RAW 音频文件?

Col*_*ent 3 javascript audio html5-audio raw-data web-audio-api

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

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

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

感谢您提供任何帮助!

Ant*_*ris 5

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;
}
Run Code Online (Sandbox Code Playgroud)
body {
  text-align: center;
  padding-top: 1rem;
}
[hidden] {
  display: none;
}
audio {
  display: inline-block;
}
Run Code Online (Sandbox Code Playgroud)
<div id="loading">Loading...</div>
<audio hidden controls></audio>
Run Code Online (Sandbox Code Playgroud)