Yanick Rochon
Yanick Rochon

Reputation: 53616

How to use AudioEncoder to compress PCM audio data?

The following code reaches the flush promise, however the created Blob does not play because the HTMLAudioElement fails with NotSupportedError: Failed to load because no supported source was found..

What am I missing?

function audioBufferToF32Planar(input: AudioBuffer): Float32Array {
  const channels = input.numberOfChannels;
  const result = new Float32Array(input.length * channels);

  let offset = 0;
  for (let channel = 0; channel < channels; channel++) {
    const data = input.getChannelData(channel);
    result.set(data, offset);
    offset = offset + data.length;
  }

  return result;
}
const outputData = new ArrayBuffer(1024, { maxByteLength: 100_000_000 });
let outputOffset = 0;
const encoder = new AudioEncoder({
  output: (e: EncodedAudioChunk) => {
    const chunkLength = e.byteLength;

    if (outputOffset + chunkLength > outputData.byteLength) {
      outputData.resize(
        Math.min(
          outputData.byteLength + chunkLength * 10,
          outputData.maxByteLength
        )
      );
    }

    const outputView = new Uint8Array(outputData);
    const chunkData = new Uint8Array(chunkLength);
    e.copyTo(chunkData);
    outputView.set(chunkData, outputOffset);

    outputOffset = outputOffset + chunkLength;
  },
  error: console.error,
});
// buffer is an AudioBuffer containing PCM audio data
const data = new AudioData({
  format: "f32-planar",
  sampleRate: buffer.sampleRate,
  numberOfChannels: buffer.numberOfChannels,
  numberOfFrames: buffer.length,
  timestamp: 0,
  data: audioBufferToF32Planar(buffer),
});
encoder.configure({
  codec: "opus",
  sampleRate: buffer.sampleRate,
  numberOfChannels: buffer.numberOfChannels,
  bitrate: 128_000,
});
encoder.encode(data);
encoder.flush().then(() => {
  const data = outputData.transferToFixedLength();
  const blob = new Blob([data], {
    type: "audio/webm;codecs=opus",
  });

  // testing audio...
  const player = new Audio();
  player.src = URL.createObjectURL(blob);
  player.play().then(
    () => {
      console.log("playing!");
    },
    (err) => {
      console.error(err);  // <-- error here
    }
  );
  player.onended = () => {
    URL.revokeObjectURL(player.src);
  };
});

Upvotes: 2

Views: 61

Answers (0)

Related Questions