Búsqueda de sitios web

javascript - Guardar AudioBuffer SIN usar MediaRecorder


Concateno dos AudioBuffers en uno.
Me parece que esto debería ser posible sin usar el MediaRecorder, ya que no necesito grabar en tiempo real.

Esto es lo que hago ahora:

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);
}

En recordAudio() alimento el flujo en un MediaRecorder.

Grabar el nuevo stream ahora lleva tanto tiempo como la duración de los dos Audiobuffer reproducidos en secuencia...
¿Hay otra manera?

Gracias.

Respuesta #1

Bueno, hay una forma [experimental y rudimentaria] de grabar buffers en paralelo y unir fragmentos. Puedes dividir un buffer de audio en tantos trozos como necesites, limitado por la longitud mínima de trozo. Eso funciona en Chrome, no en FF.

No uses eso bajo ninguna circunstancia. (Del resumen).

// 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()
}