Skip to content

Commit

Permalink
chore: add @evan/opus benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
twlite committed Sep 9, 2023
1 parent c641da4 commit 9f9d588
Show file tree
Hide file tree
Showing 13 changed files with 343 additions and 18 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,6 @@ Cargo.lock
!.yarn/versions

*.node
dev/
dev/
./src/dasp/*
./src/decoder/*
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ version = "0.0.0"
crate-type = ["cdylib"]

[dependencies]
# dasp = { version = "0.11.0" }
napi = { git = "https://github.com/napi-rs/napi-rs", default-features = false, features = [
"napi4",
] }
Expand Down
37 changes: 23 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,46 +92,55 @@ You can use `OpusEncoder` to encode pcm data to opus and decode opus data to pcm

#### Opus Benchmarks

Mediaplex includes benchmarks for the Opus encoder/decoder. Here are the results of the benchmarks on a Windows 11 machine with an i7-8700 3.2GHz processor:
Mediaplex includes benchmarks for the opus encoder/decoder. Here are the results of the benchmarks on a Windows 11 machine with an i7-8700 3.2GHz processor:

```js
$ yarn benchmark

Running "OpusEncoder Benchmark" suite...
Progress: 100%

mediaplex:
3 502 ops/s, ±0.84% | 16.04% slower
3 575 ops/s, ±0.75% | 14.82% slower

@discordjs/opus:
3 185 ops/s, ±0.17% | 23.64% slower
3 169 ops/s, ±0.43% | 24.49% slower

@evan/opus:
3 310 ops/s, ±0.18% | 21.13% slower

@evan/opus (wasm):
2 259 ops/s, ±0.17% | 46.18% slower

opusscript:
4 171 ops/s, ±0.34% | fastest
4 197 ops/s, ±0.52% | fastest

opusscript (no wasm):
261 ops/s, ±0.85% | slowest, 93.74% slower
266 ops/s, ±0.55% | slowest, 93.66% slower

Finished 4 cases!
Finished 6 cases!
Fastest: opusscript
Slowest: opusscript (no wasm)

Running "OpusDecoder Benchmark" suite...
Progress: 100%

mediaplex:
9 838 ops/s, ±0.38% | 16.96% slower
9 951 ops/s, ±0.42% | 16.12% slower

@discordjs/opus:
11 848 ops/s, ±0.40% | fastest
11 864 ops/s, ±0.49% | fastest

@evan/opus:
11 470 ops/s, ±0.39% | 3.32% slower

@evan/opus (wasm):
7 436 ops/s, ±0.35% | 37.32% slower

opusscript:
6 100 ops/s, ±0.23% | 48.51% slower
6 101 ops/s, ±0.31% | 48.58% slower

opusscript (no wasm):
2 589 ops/s, ±0.20% | slowest, 78.15% slower
2 261 ops/s, ±0.24% | slowest, 80.94% slower

Finished 4 cases!
Finished 6 cases!
Fastest: @discordjs/opus
Slowest: opusscript (no wasm)
```
Expand Down
20 changes: 20 additions & 0 deletions benchmark/common.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Buffer } from 'node:buffer';
import djs from '@discordjs/opus';
import opusscript from 'opusscript';
import mediaplex from '../index.js';
import * as evanOpus from '@evan/opus';
import * as evanOpusWasm from '@evan/opus/wasm/index.mjs';

export const generatePCMSample = (sampleSize) => {
const buffer = Buffer.alloc(sampleSize);
Expand Down Expand Up @@ -57,4 +59,22 @@ export const createDjsEncoder = (config) => new djs.OpusEncoder(config.SAMPLE_RA
export const createOpusScriptWasmEncoder = (config) => new opusscript(config.SAMPLE_RATE, config.CHANNELS, opusscript.Application.AUDIO);
export const createOpusScriptAsmEncoder = (config) => new opusscript(config.SAMPLE_RATE, config.CHANNELS, opusscript.Application.AUDIO, {
wasm: false
});
export const createEvanOpusEncoder = (config) => new evanOpus.Encoder({
application: 'voip',
channels: config.CHANNELS,
sample_rate: config.SAMPLE_RATE
});
export const createEvanOpusDecoder = (config) => new evanOpus.Decoder({
channels: config.CHANNELS,
sample_rate: config.SAMPLE_RATE
});
export const createEvanOpusEncoderWasm = (config) => new evanOpusWasm.Encoder({
application: 'voip',
channels: config.CHANNELS,
sample_rate: config.SAMPLE_RATE
});
export const createEvanOpusDecoderWasm = (config) => new evanOpusWasm.Decoder({
channels: config.CHANNELS,
sample_rate: config.SAMPLE_RATE
});
10 changes: 9 additions & 1 deletion benchmark/decoder.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import b from 'benny';
import { createDjsEncoder, createMediaplexEncoder, createOpusScriptAsmEncoder, createOpusScriptWasmEncoder, generateOpusSample } from './common.mjs';
import { createDjsEncoder, createMediaplexEncoder, createOpusScriptAsmEncoder, createOpusScriptWasmEncoder, generateOpusSample, createEvanOpusDecoder, createEvanOpusDecoderWasm } from './common.mjs';

const config = {
FRAME_SIZE: 960,
Expand All @@ -11,6 +11,8 @@ const mediaplexEncoder = createMediaplexEncoder(config);
const nativeEncoder = createDjsEncoder(config);
const wasmEncoder = createOpusScriptWasmEncoder(config);
const asmEncoder = createOpusScriptAsmEncoder(config);
const evanOpus = createEvanOpusDecoder(config);
const evanOpusWasm = createEvanOpusDecoderWasm(config);

const SAMPLE = generateOpusSample();

Expand All @@ -22,6 +24,12 @@ b.suite(
b.add('@discordjs/opus', () => {
nativeEncoder.decode(SAMPLE);
}),
b.add('@evan/opus', () => {
evanOpus.decode(SAMPLE);
}),
b.add('@evan/opus (wasm)', () => {
evanOpusWasm.decode(SAMPLE);
}),
b.add('opusscript', () => {
wasmEncoder.decode(SAMPLE);
}),
Expand Down
10 changes: 9 additions & 1 deletion benchmark/encoder.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import b from 'benny';
import { createDjsEncoder, createMediaplexEncoder, createOpusScriptAsmEncoder, createOpusScriptWasmEncoder, generatePCMSample } from './common.mjs';
import { createDjsEncoder, createMediaplexEncoder, createOpusScriptAsmEncoder, createOpusScriptWasmEncoder, generatePCMSample, createEvanOpusEncoder, createEvanOpusEncoderWasm } from './common.mjs';

const config = {
FRAME_SIZE: 960,
Expand All @@ -11,6 +11,8 @@ const mediaplexEncoder = createMediaplexEncoder(config);
const nativeEncoder = createDjsEncoder(config);
const wasmEncoder = createOpusScriptWasmEncoder(config);
const asmEncoder = createOpusScriptAsmEncoder(config);
const evanOpus = createEvanOpusEncoder(config);
const evanOpusWasm = createEvanOpusEncoderWasm(config);

const SAMPLE = generatePCMSample(config.FRAME_SIZE * config.CHANNELS * 6);

Expand All @@ -22,6 +24,12 @@ b.suite(
b.add('@discordjs/opus', () => {
nativeEncoder.encode(SAMPLE);
}),
b.add('@evan/opus', () => {
evanOpus.encode(SAMPLE);
}),
b.add('@evan/opus (wasm)', () => {
evanOpusWasm.encode(SAMPLE);
}),
b.add('opusscript', () => {
wasmEncoder.encode(SAMPLE, config.FRAME_SIZE);
}),
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"license": "MIT",
"devDependencies": {
"@discordjs/opus": "^0.9.0",
"@evan/opus": "^1.0.2",
"@napi-rs/cli": "^2.16.1",
"@types/node": "^20.4.8",
"ava": "^5.1.1",
Expand Down
23 changes: 23 additions & 0 deletions src/dasp/equalizer/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::*;

#[napi(object)]
pub struct EqualizerCoefficient {
pub beta: i32,
pub alpha: i32,
pub gamma: i32,
}

#[napi]
pub struct Equalizer {
coefficients: Vec<EqualizerCoefficient>,
format: PCMFormat,
channels: AudioChannel,
}

#[napi]
impl Equalizer {
#[napi(constructor)]
pub fn new() -> Self {

}
}
18 changes: 18 additions & 0 deletions src/dasp/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use napi::bindgen_prelude::*;

#[napi]
pub enum PCMFormat {
S16LE,
S16BE,
S32LE,
S32BE,
}

#[napi]
pub enum AudioChannel {
Mono,
Stereo,
}

mod equalizer;
mod volume;
165 changes: 165 additions & 0 deletions src/dasp/volume/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use super::*;

#[napi]
pub struct VolumeTransformer {
volume: i32,
format: PCMFormat,
channels: AudioChannel,
}

// TODO: investigate SIMD optimizations

#[napi]
impl VolumeTransformer {
#[napi(constructor)]
pub fn new(volume: i32, format: PCMFormat, channels: AudioChannel) -> Self {
Self {
volume,
format,
channels,
}
}

#[napi(setter)]
pub fn set_volume(&mut self, volume: i32) {
if volume < 0 {
self.volume = 0;
} else {
self.volume = volume;
}
}

#[napi(getter)]
pub fn get_volume(&self) -> i32 {
self.volume
}

#[napi(setter)]
pub fn set_format(&mut self, format: PCMFormat) {
self.format = format;
}

#[napi(getter)]
pub fn get_format(&self) -> PCMFormat {
self.format
}

#[napi(setter)]
pub fn set_channels(&mut self, channels: AudioChannel) {
self.channels = channels;
}

#[napi(getter)]
pub fn get_channels(&self) -> AudioChannel {
self.channels
}

#[napi]
pub fn process(&self, input: Buffer) -> Buffer {
// avoid computation if volume is 1, aka default volume
if self.volume == 1 {
return input;
}

match self.channels {
AudioChannel::Mono => self.process_mono_inner(input),
AudioChannel::Stereo => self.process_stereo_inner(input),
}
}

fn process_stereo_inner(&self, input: Buffer) -> Buffer {
match self.format {
PCMFormat::S16LE => {
let mut output = Vec::with_capacity(input.len());
for i in (0..input.len()).step_by(4) {
let mut sample = i16::from_le_bytes([input[i], input[i + 1]]);
sample = (sample as i32 * self.volume) as i16;
output.extend_from_slice(&sample.to_le_bytes());
let mut sample = i16::from_le_bytes([input[i + 2], input[i + 3]]);
sample = (sample as i32 * self.volume) as i16;
output.extend_from_slice(&sample.to_le_bytes());
}
output.into()
}
PCMFormat::S16BE => {
let mut output = Vec::with_capacity(input.len());
for i in (0..input.len()).step_by(4) {
let mut sample = i16::from_be_bytes([input[i], input[i + 1]]);
sample = (sample as i32 * self.volume) as i16;
output.extend_from_slice(&sample.to_be_bytes());
let mut sample = i16::from_be_bytes([input[i + 2], input[i + 3]]);
sample = (sample as i32 * self.volume) as i16;
output.extend_from_slice(&sample.to_be_bytes());
}
output.into()
}
PCMFormat::S32LE => {
let mut output = Vec::with_capacity(input.len());
for i in (0..input.len()).step_by(8) {
let mut sample = i32::from_le_bytes([input[i], input[i + 1], input[i + 2], input[i + 3]]);
sample = (sample as i32 * self.volume) as i32;
output.extend_from_slice(&sample.to_le_bytes());
let mut sample =
i32::from_le_bytes([input[i + 4], input[i + 5], input[i + 6], input[i + 7]]);
sample = (sample as i32 * self.volume) as i32;
output.extend_from_slice(&sample.to_le_bytes());
}
output.into()
}
PCMFormat::S32BE => {
let mut output = Vec::with_capacity(input.len());
for i in (0..input.len()).step_by(8) {
let mut sample = i32::from_be_bytes([input[i], input[i + 1], input[i + 2], input[i + 3]]);
sample = (sample as i32 * self.volume) as i32;
output.extend_from_slice(&sample.to_be_bytes());
let mut sample =
i32::from_be_bytes([input[i + 4], input[i + 5], input[i + 6], input[i + 7]]);
sample = (sample as i32 * self.volume) as i32;
output.extend_from_slice(&sample.to_be_bytes());
}
output.into()
}
}
}

fn process_mono_inner(&self, input: Buffer) -> Buffer {
match self.format {
PCMFormat::S16LE => {
let mut output = Vec::with_capacity(input.len());
for i in (0..input.len()).step_by(2) {
let mut sample = i16::from_le_bytes([input[i], input[i + 1]]);
sample = (sample as i32 * self.volume) as i16;
output.extend_from_slice(&sample.to_le_bytes());
}
output.into()
}
PCMFormat::S16BE => {
let mut output = Vec::with_capacity(input.len());
for i in (0..input.len()).step_by(2) {
let mut sample = i16::from_be_bytes([input[i], input[i + 1]]);
sample = (sample as i32 * self.volume) as i16;
output.extend_from_slice(&sample.to_be_bytes());
}
output.into()
}
PCMFormat::S32LE => {
let mut output = Vec::with_capacity(input.len());
for i in (0..input.len()).step_by(4) {
let mut sample = i32::from_le_bytes([input[i], input[i + 1], input[i + 2], input[i + 3]]);
sample = (sample as i32 * self.volume) as i32;
output.extend_from_slice(&sample.to_le_bytes());
}
output.into()
}
PCMFormat::S32BE => {
let mut output = Vec::with_capacity(input.len());
for i in (0..input.len()).step_by(4) {
let mut sample = i32::from_be_bytes([input[i], input[i + 1], input[i + 2], input[i + 3]]);
sample = (sample as i32 * self.volume) as i32;
output.extend_from_slice(&sample.to_be_bytes());
}
output.into()
}
}
}
}
Loading

0 comments on commit 9f9d588

Please sign in to comment.