I’d like to convert an AudioBuffer
to a Blob
so that I can create an ObjectURL from it and then download the audio file.
let rec = new Recorder(async(chunks) => { var blob = new Blob(chunks, { type: 'audio/mp3' }); var arrayBuffer = await blob.arrayBuffer(); const audioContext = new AudioContext() await audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => { // How to I now convert the AudioBuffer into an ArrayBuffer => Blob ? }
Advertisement
Answer
An AudioBuffer contains non-interleaved Float32Array
PCM samples for each decoded audio channel. For a stereo AudioBuffer, it will contain 2 channels. Those channels need to be interleaved first, and then the interleaved PCM must have a WAV header appended to it so you can download and play as a WAV.
// Float32Array samples const [left, right] = [audioBuffer.getChannelData(0), audioBuffer.getChannelData(1)] // interleaved const interleaved = new Float32Array(left.length + right.length) for (let src=0, dst=0; src < left.length; src++, dst+=2) { interleaved[dst] = left[src] interleaved[dst+1] = right[src] } // get WAV file bytes and audio params of your audio source const wavBytes = getWavBytes(interleaved.buffer, { isFloat: true, // floating point or 16-bit integer numChannels: 2, sampleRate: 48000, }) const wav = new Blob([wavBytes], { type: 'audio/wav' }) // create download link and append to Dom const downloadLink = document.createElement('a') downloadLink.href = URL.createObjectURL(wav) downloadLink.setAttribute('download', 'my-audio.wav') // name file
supporting functions below:
// Returns Uint8Array of WAV bytes function getWavBytes(buffer, options) { const type = options.isFloat ? Float32Array : Uint16Array const numFrames = buffer.byteLength / type.BYTES_PER_ELEMENT const headerBytes = getWavHeader(Object.assign({}, options, { numFrames })) const wavBytes = new Uint8Array(headerBytes.length + buffer.byteLength); // prepend header, then add pcmBytes wavBytes.set(headerBytes, 0) wavBytes.set(new Uint8Array(buffer), headerBytes.length) return wavBytes } // adapted from https://gist.github.com/also/900023 // returns Uint8Array of WAV header bytes function getWavHeader(options) { const numFrames = options.numFrames const numChannels = options.numChannels || 2 const sampleRate = options.sampleRate || 44100 const bytesPerSample = options.isFloat? 4 : 2 const format = options.isFloat? 3 : 1 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 https://i.stack.imgur.com/BuSmb.png writeUint16(numChannels) // NumChannels writeUint32(sampleRate) // SampleRate writeUint32(byteRate) // ByteRate writeUint16(blockAlign) // BlockAlign writeUint16(bytesPerSample * 8) // BitsPerSample writeString('data') // Subchunk2ID writeUint32(dataSize) // Subchunk2Size return new Uint8Array(buffer) }