Как воспроизводить аудиофайлы RAW? - PullRequest
2 голосов
/ 29 мая 2020

В настоящее время я работаю над проектом, который состоит из диаграммы, показывающей уровни звука, снятые другим устройством. Графики создаются с помощью flot API, и у меня есть возможности масштабирования и выбора, чтобы выбрать временной диапазон на графике и увеличить этот выбранный регион. Мой следующий шаг - дать пользователю возможность слушать звук, соответствующий этой области диаграммы. У меня есть аудиофайлы, хранящиеся на общем сервере, и все файлы находятся в отдельных, поминутно, файлах данных RAW. У меня нет опыта использования звука на веб-странице, и в настоящее время я не могу решить, как выполнить эту задачу. Насколько я понял, тег <audio> HTML не может обрабатывать файлы данных RAW для воспроизведения. Я изучал API веб-аудио, но не понимаю, как он работает и как его реализовать.

Мой первый вопрос: как мне go декодировать аудиофайлы RAW с сервера и отображать их на странице HTML для прослушивания клиентом?

Моя вторая задача - чтобы захватить все аудиофайлы, соответствующие выбранному диапазону, и объединить их в один аудиовыход. Например, если клиент выбрал временной диапазон с 13:00 до 13:50, мне потребуется доступ к 50 аудиофайлам с RAW-данными каждую минуту длиной. Затем я хотел бы объединить их вместе, чтобы получить единый воспроизводимый звук. Поэтому мой второй вопрос: знает ли кто-нибудь способ добиться sh этого плавно.

Спасибо за любую помощь, которую кто-либо может предложить!

Ответы [ 2 ]

2 голосов
/ 30 мая 2020
Файлы

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>
0 голосов
/ 02 июня 2020

Альтернатива, которая может быть немного проще с 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() с правильными значениями времени. Но я надеюсь, что этого достаточно, чтобы вы начали. Если нет, задайте дополнительные вопросы.

...