Save AudioBuffer WITHOUT using Mediarecorder

Viewed 209

I concatenate two AudioBuffers into one.
It seems to me this should be possible without using the MediaRecorder, for I don't need to record in realtime.

This is what I do now:

concatAudio(buffers: AudioBuffer[]): void {
    const totalLength = buffers.reduce((acc, buffer) => acc + buffer.length, 0);
    const audioContext = new AudioContext();
    const audioBuffer = audioContext.createBuffer(1, totalLength, 48000);
    const src = audioContext.createBufferSource();
    const dst = audioContext.createMediaStreamDestination();

    let offset = 0;
    buffers.map((buffer) => {
        audioBuffer.getChannelData(0).set(buffer.getChannelData(0), offset);
        offset += buffer.length;
    });

    src.buffer = audioBuffer;
    src.connect(dst);
    src.start();

    this.recordAudio(dst.stream, audioBuffer.duration);
}

In recordAudio() I feed the stream into a MediaRecorder.

Recording the new stream now takes as long as the duration of the two Audiobuffers played in sequence...
Is there another way?

Thanks.

1 Answers

Well there's an [experimental hacky] way to record buffers in parallel and glue up blobs. You can split audio buffer into as many chunks as you need, limited by min chunk length. That works in Chrome, doesn't in FF.

Don't use that under any circumstances. (From gist).

// try splitting buffer to N parts, recording in parallel, generating blob
async function recordParallel() {
  const audioContext = new AudioContext();

  const bufs = [], all = []
  for (let i = 0; i < 10; i++ ) {
    // 2705 - min chunk length for opus encoder in Chrome, so we increase block size to 4096 plus silent header
    let buf = new AudioBuffer({length: 4096, sampleRate: audioContext.sampleRate})
    bufs.push(buf)
    let data = buf.getChannelData(0)
    for (let j = 0; j < 4096; j++) data[j] = Math.sin(j / ((i + 1) * 2))

    // create recorders
    const source = audioContext.createBufferSource();
    source.buffer = buf;


    const chunks = []
    all.push(new Promise(r => {
      const dest = audioContext.createMediaStreamDestination();
      const recorder = new MediaRecorder(dest.stream);
      source.connect(dest)

      recorder.start(10)
      // delay is needed to shift encodingblocks
      source.start(0)

      recorder.ondataavailable = (e) => {
        const blob = e.data
        if (blob.size) chunks.push(blob)
      }
      recorder.onstop = e => {
        r(chunks)
      }
      source.onended = e => {
        recorder.stop()
      }
    }))
  }

  const blobs = await Promise.all(all);

  // combine multiple recorders back
  console.log(blobs)
  var blob = new Blob([...blobs[0], ...blobs.slice(1).map(b => b.slice(1)).flat()], { 'type' : 'audio/ogg; codecs=opus' });
  let audio = document.createElement('audio')
  audio.src = URL.createObjectURL(blob);
  audio.play()
}
Related