Files
songbird/benches/base-mixing.rs
Kyle Simpson 3daf11f5d1 Driver: Implement audio scheduler (#179)
This PR implements a custom scheduler for audio threads, which reduces thread use and (often) memory consumption.

To save threads and memory (e.g., packet buffer allocations), Songbird parks Mixer tasks which do not have any live Tracks.
These are now all co-located on a single async 'Idle' task.
This task is responsible for managing UDP keepalive messages for each task, maintaining event state, and executing any Mixer task messages.
Whenever any message arrives which adds a `Track`, the mixer task is moved to a live thread.
The Idle task inspects task counts and execution time on each thread, choosing the first live thread with room, and creating a new one if needed.

Each live thread is responsible for running as many live mixers as it can in a single tick every 20ms: this currently defaults to 16 mixers per thread, but is user-configurable.
A live thread also stores RTP packet blocks to be written into by each sub-task.
Each live thread has a conservative limit of 18ms that it will aim to stay under: if all work takes longer than this, it will offload the task with the highest mixing cost once per tick onto another (possibly new) live worker thread.
2023-11-20 00:02:57 +00:00

105 lines
3.5 KiB
Rust

use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use songbird::{
constants::*,
driver::{
bench_internals::mixer::{mix_logic, state::DecodeState},
MixMode,
},
input::{codecs::*, Input, LiveInput, Parsed},
test_utils as utils,
};
use std::io::Cursor;
use symphonia_core::audio::{AudioBuffer, Layout, SampleBuffer, Signal, SignalSpec};
pub fn mix_one_frame(c: &mut Criterion) {
let floats = utils::make_sine(1 * STEREO_FRAME_SIZE, true);
let symph_layout = MixMode::Stereo.into();
let mut symph_mix = AudioBuffer::<f32>::new(
MONO_FRAME_SIZE as u64,
symphonia_core::audio::SignalSpec::new_with_layout(SAMPLE_RATE_RAW as u32, symph_layout),
);
let mut resample_scratch = AudioBuffer::<f32>::new(
MONO_FRAME_SIZE as u64,
SignalSpec::new_with_layout(SAMPLE_RATE_RAW as u32, Layout::Stereo),
);
let mut group = c.benchmark_group("Stereo Target");
for (pres, hz) in [("", 48_000), (" (Resample)", 44_100)] {
group.bench_with_input(
BenchmarkId::new(format!("Stereo Source{}", pres), hz),
&hz,
|b, i| {
b.iter_batched_ref(
|| black_box(make_src(&floats, 2, *i)),
|(ref mut input, ref mut local_input)| {
symph_mix.clear();
symph_mix.render_reserved(Some(MONO_FRAME_SIZE));
resample_scratch.clear();
black_box(mix_logic::mix_symph_indiv(
&mut symph_mix,
&mut resample_scratch,
input,
local_input,
black_box(1.0),
None,
));
},
BatchSize::SmallInput,
)
},
);
group.bench_with_input(
BenchmarkId::new(format!("Mono Source{}", pres), hz),
&hz,
|b, i| {
b.iter_batched_ref(
|| black_box(make_src(&floats, 1, *i)),
|(ref mut input, ref mut local_input)| {
symph_mix.clear();
symph_mix.render_reserved(Some(MONO_FRAME_SIZE));
resample_scratch.clear();
black_box(mix_logic::mix_symph_indiv(
&mut symph_mix,
&mut resample_scratch,
input,
local_input,
black_box(1.0),
None,
));
},
BatchSize::SmallInput,
)
},
);
}
group.finish();
}
fn make_src(src: &Vec<u8>, chans: u32, hz: u32) -> (Parsed, DecodeState) {
let local_input = Default::default();
let adapted: Input =
songbird::input::RawAdapter::new(Cursor::new(src.clone()), hz, chans).into();
let promoted = match adapted {
Input::Live(l, _) => l.promote(&CODEC_REGISTRY, &PROBE),
_ => panic!("Failed to create a guaranteed source."),
};
let parsed = match promoted {
Ok(LiveInput::Parsed(parsed)) => parsed,
Err(e) => panic!("AR {:?}", e),
_ => panic!("Failed to create a guaranteed source."),
};
(parsed, local_input)
}
criterion_group!(benches, mix_one_frame);
criterion_main!(benches);