npm install @native-bindings/libopus
Entirely synchronous API to create .ogg files encoded using libopus library. It supports several sample rates (16000,48000 and more.).
Below it's an example on how to use the opus codec by itself, without interference of the libopusenc. AFAIK, the encoded data cannot be directly placed in a .ogg file for playing. If you need that, see the next example.
import opus from "@native-bindings/libopus";
export function createTestCodec() {
const frameSize = 2880;
const sampleRate = 48000;
const channels = 1;
const enc = new opus.Encoder(
sampleRate,
channels,
opus.constants.OPUS_APPLICATION_RESTRICTED_LOWDELAY
);
const dec = new opus.Decoder(sampleRate, channels);
const pcmOut = new Float32Array(
frameSize * channels * Float32Array.BYTES_PER_ELEMENT
);
const encoded = new Uint8Array(10000);
const maxDataBytes = 1000; // it can be increased up to whatever `encoded` size is
const encodeFloat = (buf: Float32Array) => {
const encodedSampleCount = enc.encodeFloat(
buf,
frameSize,
encoded,
maxDataBytes
);
console.log("encoded samples: %d", encodedSampleCount);
return encodedSampleCount;
};
const decodeFloat = (buf: Uint8Array, samples: number) => {
const decodedSamples = dec.decodeFloat(
buf,
samples,
pcmOut,
frameSize,
0
);
return new Uint8Array(pcmOut.slice(0, decodedSamples).buffer);
};
const encodeAndDecode = (buf: Float32Array) => {
const encodedSampleCount = encodeFloat(buf);
return decodeFloat(encoded, encodedSampleCount);
};
return {
decode,
encode,
encodeAndDecode,
};
}
This package comes with libopusenc embedded in it, so you can create .ogg/.opus files directly with it with any sample rate. It will use speexdsp in case a sample rate that is not supported by libopus is used when creating the encoder. See the example below:
import opus from "@native-bindings/libopus";
const comments = new opus.opusenc.Comments();
const enc = new opus.opusenc.Encoder();
enc.createFile(comments, path.resolve(__dirname, "test1.ogg"), 48000, 1, 0);
const pcm = child_process.spawn("arecord", [
"-f",
"FLOAT_LE",
"-c",
"1",
"-r",
"48000",
"-d",
"4",
]);
pcm.stdout.on("data", (chunk) => {
assert.strict.ok(Buffer.isBuffer(chunk));
const samples = chunk.byteLength / Float32Array.BYTES_PER_ELEMENT;
const buf = new Float32Array(samples);
buf.set(new Float32Array(chunk.buffer, chunk.byteOffset, samples));
enc.writeFloat(buf, samples);
});
return new Promise<void>((resolve) => {
pcm.stdout.on("exit", (code) => {
assert.strict.ok(code === 0);
enc.drain();
resolve();
});
});
import { OpusFile } from "@native-bindings/libopus";
const of = new OpusFile();
of.openFile(path.resolve(__dirname, "sample-3.opus"));
const pcm = new Float32Array(2880);
assert.strict.equal(of.pcmTell(), 0);
assert.strict.deepEqual(of.readFloat(pcm), {
sampleCount: 648,
linkIndex: 0,
});
assert.strict.deepEqual(of.pcmTell(), 648);
assert.strict.deepEqual(of.readFloat(pcm), {
sampleCount: 960,
linkIndex: 0,
});
assert.strict.equal(of.pcmTell(), 1608);
assert.strict.equal(of.pcmSeek(0), undefined);
assert.strict.equal(of.pcmTell(), 0);
assert.strict.deepEqual(of.readFloat(pcm), {
sampleCount: 648,
linkIndex: 0,
});
assert.strict.equal(of.channelCount(0), 2);
assert.strict.equal(of.channelCount(1), 2);
assert.strict.equal(of.linkCount(), 1);
assert.strict.equal(of.rawTotal(0), 705632);
You can also open the file from memory:
import { OpusFile } from "@native-bindings/libopus";
const of = new OpusFile();
of.openMemory(await fs.promises.readFile("./sample.opus"));