Как воспроизводить аудиофайлы в формате RAW?

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

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

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

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


person CollegeStudent    schedule 29.05.2020    source источник
comment
Это для сверхсекретной шпионской программы для прослушивания записанных разговоров?   -  person AnthumChris    schedule 30.05.2020
comment
нет...   -  person CollegeStudent    schedule 31.05.2020


Ответы (2)


Файлы 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>

person AnthumChris    schedule 29.05.2020
comment
Вы знаете, как это сделать на стороне сервера? У меня возникли проблемы с получением файлов с общего сервера, и мой руководитель сказал мне изучить возможность сделать это с помощью aspx и Visual Basic. - person CollegeStudent; 04.06.2020
comment
Deno или Node. js может запускать JS на стороне сервера, но это звучит как вопрос архитектуры, потенциально выходящий за рамки исходного вопроса о звуке RAW. - person AnthumChris; 04.06.2020

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

person Raymond Toy    schedule 02.06.2020
comment
Это гораздо более чистый подход. Может ли AudioBuffer предоставлять функции поиска, аналогичные использованию <audio>? - person AnthumChris; 02.06.2020
comment
Поиск и тому подобное были бы в AudioBufferSourceNode, и это довольно ограничено. Вы можете сделать start(time, offset, duration), чтобы воспроизвести буфер на offset с в буфер и воспроизвести на duration с из буфера. Если вы хотите сделать это динамически, вам придется проделать гораздо больше работы. - person Raymond Toy; 02.06.2020