I have a stream of raw PCM buffers I need to convert to playable WAV buffers.
ffmpeg wasm can convert an individual buffer in the stream well, but it’s limited to executing 1 command at a time, so it won’t work in a real-life streaming scenario (streams x concurrent users). We can use it as a reference to a conversion that outputs a good playable wav.
import { FFmpeg, createFFmpeg, fetchFile } from '@ffmpeg.wasm/main';
async pcmToWavFFMPEG(buffer: Buffer) {
// bitDepth - PCM signed 16-bit big-endian
const options = { sampleRate: '24k', channels: '1', bitDepth: 's16le' };
this.ffmpeg.FS('writeFile', 'input.pcm', await fetchFile(buffer));
await this.ffmpeg.run(
'-f',
options.bitDepth,
'-ar',
options.sampleRate,
'-ac',
options.channels,
'-i',
'input.pcm',
'output.wav',
);
const wavBuffer = this.ffmpeg.FS('readFile', 'output.wav');
this.ffmpeg.FS('unlink', `input.pcm`);
this.ffmpeg.FS('unlink', `output.wav`);
return Buffer.from(wavBuffer);
}
In order to get over the command execution limit, I’ve tried fluent ffmpeg. I couldn’t find a way to convert a single buffer, so I’m just passing the whole readable stream so that ffmpeg can convert all of its buffers to wav. The buffers I’m getting in on(‘data’) aren’t playable wav. The same is true for concatting the accumulated buffers in on(‘complete’) – the result is not a playable wav.
import ffmpeg from 'fluent-ffmpeg';
async pcmToWavFluentFFMPEG(
readable: internal.Readable,
callback: (chunk: Buffer) => void,
) {
const options = { sampleRate: 24000, channels: 1, bitDepth: 's16le' };
ffmpeg(readable)
.inputFormat(options.bitDepth)
.audioFrequency(options.sampleRate)
.audioChannels(options.channels)
.outputFormat('wav')
.pipe()
.on('data', callback);
}
I’ve also tried using node-wav to convert each buffer individually. It manages to convert everything to playable wavs that sound close to the desired result, however for some reason they’re extremely loud and sound a bit weird.
import wav from 'node-wav';
pcmToWavBad(buffer: Buffer) {
const pcmData = new Int16Array(
buffer.buffer,
buffer.byteOffset,
buffer.byteLength / Int16Array.BYTES_PER_ELEMENT,
);
const channelData = [pcmData]; // assuming mono channel
return wav.encode(channelData, { sampleRate: 24000, bitDepth: 16 });
}