Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Driver: Implement audio scheduler #179

Merged
merged 28 commits into from
May 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b75427e
Sketch out some initial structs.
FelixMcFelix Mar 30, 2023
fe1d84d
WIP while working out details.
FelixMcFelix Apr 10, 2023
67bfba9
Almost all there.
FelixMcFelix Apr 29, 2023
82c514a
Works on single-calls now.
FelixMcFelix Apr 30, 2023
871d24d
Test harness works again
FelixMcFelix May 1, 2023
35eedeb
Fixes to docgen
FelixMcFelix May 1, 2023
778bed1
Benchmarks working again!
FelixMcFelix May 1, 2023
871460b
Cargo toml changes to support benchmark baselines
FelixMcFelix May 4, 2023
f9d3685
Convert mixer removals to memcpys
FelixMcFelix May 4, 2023
75070bd
Refactor benchmarks, add packet packet swap_remove tests
FelixMcFelix May 8, 2023
21456e0
Trim some unnecessary timeouts
FelixMcFelix May 8, 2023
dc42557
Make mixer removal safe
FelixMcFelix May 8, 2023
56101a5
Periodic memory reclamation for individual worker threads.
FelixMcFelix May 9, 2023
177af31
Initial impl of thread cleanup.
FelixMcFelix May 10, 2023
9aae905
Test thread removal, fix a bad recv instead of recv_async
FelixMcFelix May 15, 2023
e3483c6
Add tests for thread scaleup
FelixMcFelix May 15, 2023
0a655cb
The big break-out/refactor
FelixMcFelix May 16, 2023
da02640
Minor message handling cleanups
FelixMcFelix May 16, 2023
5038ab7
Fix benchmarks!
FelixMcFelix May 16, 2023
fd6ded8
Additional documentation, add min-size terminal pkt blocks
FelixMcFelix May 18, 2023
1241a55
Comments to help the next soul understand.
FelixMcFelix May 19, 2023
f6e0cca
Allow for task offloading to a new thread.
FelixMcFelix May 19, 2023
6cbd7ee
Cost-based mixer offloading.
FelixMcFelix May 19, 2023
af4c457
Global clippy appeasement
FelixMcFelix May 20, 2023
0683969
Allow mixer movement to be disabled via config.
FelixMcFelix May 20, 2023
3769c86
Final unhandled errors... handled.
FelixMcFelix May 20, 2023
fb9e5fb
Update architecture document to account for scheduler.
FelixMcFelix May 21, 2023
c415f6b
Convert to manual indexing from flat map of fixed size chunks
FelixMcFelix May 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Audio processing remains synchronous for the following reasons:
Songbird subdivides voice connection handling into several long- and short-lived tasks.

* **Core**: Handles and directs commands received from the driver. Responsible for connection/reconnection, and creates network tasks.
* **Mixer**: Combines audio sources together, Opus encodes the result, and encrypts the built packets every 20ms. Responsible for handling track commands/state, and transmitting completed voice packets and keepalive messages. ***Synchronous***.
* **Mixer**: Combines audio sources together, Opus encodes the result, and encrypts the built packets every 20ms. Responsible for handling track commands/state, and transmitting completed voice packets and keepalive messages. ***Synchronous when live***.
* **Thread Pool**: A dynamically sized thread-pool for I/O tasks. Creates lazy tracks using `Compose` if sync creation is needed, otherwise spawns a tokio task. Seek operations always go to the thread pool. ***Synchronous***.
* **Disposer**: Used by mixer thread to dispose of data with potentially long/blocking `Drop` implementations (i.e., audio sources). ***Synchronous***.
* **Events**: Stores and runs event handlers, tracks event timing, and handles
Expand All @@ -46,6 +46,33 @@ Songbird subdivides voice connection handling into several long- and short-lived

![](images/driver.png)

## Scheduler
To save threads and memory (e.g., packet buffer allocations), Songbird parks Mixer tasks which do not have any live Tracks.
These are all co-located on a single async 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, 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.
Audio threads have a budget of 20ms to complete all message handling, mixing, encoding, and encryption.
*These threads are synchronous, as explained above: the bulk costs (i.e., encoding) are compute-bound work and would block the Tokio executor.*
Mixer logic is handled in this order to minimise deadline variance:
```
handle idle->live messages
handle all driver->mixer messages
cleanup idle/dead mixers
mix + encode + encrypt all mixers into packet buffer
check for excess packet blocks
sleep 'til next 20ms boundary

send all packets, adjust RTP fields
handle per-track messages
```
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 20ms tick.

![](images/scheduler.png)

```
src/driver/*
```
Expand Down
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ derivative = "2"
discortp = { default-features = false, features = ["discord", "pnet", "rtp"], optional = true, version = "0.5" }
flume = { optional = true, version = "0.10" }
futures = "0.3"
nohash-hasher = { optional = true, version = "0.2.0" }
once_cell = { optional = true, version = "1" }
parking_lot = { optional = true, version = "0.12" }
pin-project = "1"
Expand Down Expand Up @@ -64,11 +65,11 @@ git = "https://github.com/serenity-rs/serenity"
optional = true

[dev-dependencies]
byteorder = "1"
criterion = "0.4"
ntest = "0.9"
symphonia = { version = "0.5.2", features = ["aac", "isomp4", "mp3"] }
utils = { path = "utils" }
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
tokio = { version = "1", features = ["rt", "rt-multi-thread", "test-util"] }

[features]
# Core features
Expand All @@ -95,6 +96,7 @@ driver = [
"dep:discortp",
"dep:reqwest",
"dep:flume",
"dep:nohash-hasher",
"dep:once_cell",
"dep:parking_lot",
"dep:rand",
Expand Down Expand Up @@ -143,7 +145,10 @@ receive = ["dep:bytes", "discortp?/demux", "discortp?/rtcp"]

# Used for docgen/testing/benchmarking.
full-doc = ["default", "twilight", "builtin-queue", "receive"]
internals = []
internals = ["dep:byteorder"]

[lib]
bench = false

[[bench]]
name = "base-mixing"
Expand Down
1 change: 1 addition & 0 deletions benches/base-mixing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use songbird::{
MixMode,
},
input::{codecs::*, Input, LiveInput, Parsed},
test_utils as utils,
};
use std::io::Cursor;
use symphonia_core::audio::{AudioBuffer, Layout, SampleBuffer, Signal, SignalSpec};
Expand Down
Loading