use crate::{Application, OpusEncoder};
fn worker_count(requested: usize) -> usize {
if cfg!(all(target_family = "wasm", not(target_feature = "atomics"))) {
return 1;
}
if requested == 0 {
std::thread::available_parallelism().map_or(1, std::num::NonZero::get)
} else {
requested
}
}
#[derive(Clone, Copy)]
pub struct ParallelConfig {
pub sample_rate: i32,
pub channels: usize,
pub application: Application,
pub bitrate_bps: i32,
pub complexity: i32,
pub use_cbr: bool,
pub warmup: usize,
pub threads: usize,
}
impl ParallelConfig {
pub fn new(sample_rate: i32, channels: usize, application: Application) -> Self {
Self {
sample_rate,
channels,
application,
bitrate_bps: 64_000,
complexity: 9,
use_cbr: false,
warmup: 8,
threads: 0,
}
}
}
pub fn encode_parallel(cfg: &ParallelConfig, pcm: &[f32], frame_size: usize) -> Vec<Vec<u8>> {
let step = frame_size * cfg.channels;
if step == 0 {
return Vec::new();
}
let total_frames = pcm.len() / step;
if total_frames == 0 {
return Vec::new();
}
let threads = worker_count(cfg.threads);
let min_chunk = (cfg.warmup * 4).max(1);
let n_workers = threads.max(1).min((total_frames / min_chunk).max(1));
if n_workers <= 1 {
return encode_serial(cfg, pcm, frame_size);
}
let base = total_frames / n_workers;
let rem = total_frames % n_workers;
let mut ranges = Vec::with_capacity(n_workers);
let mut start = 0usize;
for w in 0..n_workers {
let len = base + usize::from(w < rem);
ranges.push((start, start + len));
start += len;
}
let mut chunks: Vec<Vec<Vec<u8>>> = Vec::new();
std::thread::scope(|scope| {
let handles: Vec<_> = ranges
.iter()
.map(|&(cstart, cend)| {
scope.spawn(move || encode_chunk(cfg, pcm, frame_size, cstart, cend))
})
.collect();
for h in handles {
chunks.push(h.join().expect("opus parallel worker panicked"));
}
});
let mut out = Vec::with_capacity(total_frames);
for c in chunks {
out.extend(c);
}
out
}
fn encode_chunk(
cfg: &ParallelConfig,
pcm: &[f32],
frame_size: usize,
cstart: usize,
cend: usize,
) -> Vec<Vec<u8>> {
let step = frame_size * cfg.channels;
let mut enc = new_encoder(cfg);
let warm_start = cstart.saturating_sub(cfg.warmup);
let mut buf = vec![0u8; 4000];
let mut packets = Vec::with_capacity(cend - cstart);
for f in warm_start..cend {
let frame = &pcm[f * step..(f + 1) * step];
let n = enc
.encode(frame, frame_size, &mut buf)
.expect("opus encode");
if f >= cstart {
packets.push(buf[..n].to_vec());
}
}
packets
}
pub fn encode_serial(cfg: &ParallelConfig, pcm: &[f32], frame_size: usize) -> Vec<Vec<u8>> {
let step = frame_size * cfg.channels;
if step == 0 {
return Vec::new();
}
let total_frames = pcm.len() / step;
let mut enc = new_encoder(cfg);
let mut buf = vec![0u8; 4000];
let mut packets = Vec::with_capacity(total_frames);
for f in 0..total_frames {
let frame = &pcm[f * step..(f + 1) * step];
let n = enc
.encode(frame, frame_size, &mut buf)
.expect("opus encode");
packets.push(buf[..n].to_vec());
}
packets
}
pub fn encode_streams(
streams: &[(ParallelConfig, &[f32], usize)],
threads: usize,
) -> Vec<Vec<Vec<u8>>> {
let n = streams.len();
let mut out: Vec<Vec<Vec<u8>>> = (0..n).map(|_| Vec::new()).collect();
if n == 0 {
return out;
}
let workers = worker_count(threads).max(1).min(n);
if workers == 1 {
for ((cfg, pcm, frame_size), dst) in streams.iter().zip(out.iter_mut()) {
*dst = encode_serial(cfg, pcm, *frame_size);
}
return out;
}
let next = std::sync::atomic::AtomicUsize::new(0);
let out_slots: Vec<std::sync::Mutex<Option<Vec<Vec<u8>>>>> =
(0..n).map(|_| std::sync::Mutex::new(None)).collect();
std::thread::scope(|scope| {
for _ in 0..workers {
let next = &next;
let out_slots = &out_slots;
scope.spawn(move || {
loop {
let idx = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if idx >= n {
break;
}
let (cfg, pcm, frame_size) = &streams[idx];
let pkts = encode_serial(cfg, pcm, *frame_size);
*out_slots[idx].lock().unwrap() = Some(pkts);
}
});
}
});
for (slot, dst) in out_slots.into_iter().zip(out.iter_mut()) {
*dst = slot.into_inner().unwrap().unwrap_or_default();
}
out
}
fn new_encoder(cfg: &ParallelConfig) -> OpusEncoder {
let mut enc = OpusEncoder::new(cfg.sample_rate, cfg.channels, cfg.application)
.expect("opus encoder init");
enc.bitrate_bps = cfg.bitrate_bps;
enc.complexity = cfg.complexity.clamp(0, 10);
enc.use_cbr = cfg.use_cbr;
enc
}