songbird

repository·current·Indexed 19 days ago

https://github.com/serenity-rs/songbird

An asynchronous, cross-library compatible voice system for Discord written in Rust. Songbird provides high-level features such as audio queues and seeking, alongside low-level drivers for voice connection and packet handling. It supports standalone gateway frontend (compatible with serenity and twilight), standalone voice driver, and voice receive capabilities. The library utilizes Symphonia for extended codec support (MP3, AAC, FLAC) and requires the Opus audio codec as a system dependency.

Tokens
12.2K
Snippets
37
Records
60
Agent score
65%

What's inside songbird

  1. Overview of Songbird features and capabilities

    current

    Songbird is an async, cross-library compatible voice system for Discord written in Rust. It can be used in several modes:

    • Standalone Gateway Frontend: Compatible with serenity and twilight using the gateway and [serenity/twilight] features. Can be run 'driverless' to manage Lavalink sessions.
    • Standalone Voice Driver: Using the driver feature, you can run the voice driver if you can provide a ConnectionInfo from any other gateway or language.
    • Voice Receive: Supports voice receive and RT(C)P packet handling via the receive feature.
    • Core Features: Includes event handling, queues, seeking on compatible streams, shared multithreaded audio stream caches, and direct Opus data passthrough from DCA files.
  2. Understanding the Driver's task architecture

    current

    The Driver subdivides voice connection handling into several specialized tasks to balance performance and responsiveness:

    • Core: Manages connection/reconnection and directs commands to network tasks.
    • Mixer (Synchronous): Combines audio sources, performs Opus encoding, and encrypts packets every 20ms. It handles track commands and transmits voice packets.
    • Thread Pool (Synchronous): A dynamic pool for I/O tasks, such as Seek operations or creating lazy tracks via Compose.
    • Disposer (Synchronous): Used by the mixer thread to drop audio sources that might have long or blocking Drop implementations.
    • Events: Manages event handlers and tracks event timing.
    • Websocket (Network Task): Sends speaking status/keepalives to Discord and receives connection events.
    • UDP Rx (Optional Network Task): Decrypts and decodes incoming voice packets and statistics.

    All tasks communicate via interconnecting channels.

  3. How the Driver Scheduler manages Mixer tasks

    current

    To optimize memory and thread usage, Songbird uses a specialized scheduler for Mixer tasks:

    • Parking: Mixer tasks without live tracks are "parked" in a single async task to save resources. They are moved to a live thread only when a Track is added.
    • Live Threads: When a mixer is active, it runs on a live thread. Each thread can run multiple mixers (defaulting to 16 per thread, but user-configurable) in a 20ms tick.
    • Execution Budget: Audio threads aim to complete mixing, encoding, and encryption within an 18ms budget. If work exceeds this, the scheduler offloads the highest-cost task to stay within the 20ms deadline.
    • Mixing Order: To minimize latency variance, the mixer follows a strict order: handle idle/live messages $\rightarrow$ handle driver/mixer messages $\rightarrow$ cleanup $\rightarrow$ mix/encode/encrypt $\rightarrow$ check packet blocks $\rightarrow$ sleep $\rightarrow$ send packets $\rightarrow$ handle per-track messages.
  4. How Songbird's Gateway and Driver systems work together

    current

    Songbird is composed of two primary systems that handle different aspects of a Discord voice connection:

    1. The Gateway: An async system (typically managed by a Songbird struct) that communicates with Discord via a client library. It manages voice state updates, handles joining voice channels, and produces ConnectionInfo (session information).
    2. The Driver: A mixed sync/async system that uses the ConnectionInfo from the Gateway to establish RTP (audio) and WebSocket (signalling) connections. The Driver is responsible for audio mixing, source management, event tracking, and receiving voice packets.

    Depending on your use case, you may use one or both. For example, a bot might use a Gateway to collect connection info to send to an external service like Lavalink, or use both to handle audio locally within Songbird.

  5. How audio inputs and tracks are handled

    current

    Songbird manages audio through two main abstractions:

    Inputs

    Inputs are audio sources that support lazy initialization:

    • Lazy Inputs: Trait objects that allow instructions to store a way to create an audio source cheaply. They are initialized only when needed.
    • Live Inputs: Objects implementing MediaSource: Read + Seek.

    Internally, the mixer uses floating-point audio to prevent clipping. Songbird uses Symphonia to decode various formats. If a source is already in Opus format and is the only source playing, Songbird can bypass mixing and re-encoding to save CPU.

    Tracks

    Tracks manage the state of a playing source, including position, play state, and modifiers (like volume).

    • Modification: While tracks are defined in user code, once passed to the driver, all changes must be requested via a TrackHandle. This ensures the audio thread never locks or blocks while the user modifies track properties.
  6. How event handling works in Songbird

    current

    Event handlers in Songbird are implemented as boxed trait objects subscribed to specific event types. They can be registered on a per-track basis or a global basis.

    • Subscription: Handlers are generic, allowing the same handler to be reused across different subscriptions via Arc.
    • Timing: Timed events are driven by "tick" messages from the mixer to ensure the handler's view of the track state is synchronized with the actual audio state.
    • Triggering: Global events are triggered by various driver tasks or the main system "tick".
  7. Install system dependencies for Songbird

    current

    Songbird requires several system-level dependencies to function correctly.

    1. Opus (Required)

    Songbird cannot work without the Opus audio codec.

    • Linux (Ubuntu/Debian): sudo apt install libopus-dev
    • Linux (Arch): sudo pacman -S opus
    • macOS/Linux (via pkgconf): It will attempt to use installed libopus binaries.
    • Windows: You must have cmake installed to build opus from source.
    • Fallback: If binaries are not found, Songbird will attempt to build from source using a C compiler and GNU autotools (build-essential, autoconf, automake, libtool, m4 on Ubuntu; base-devel on Arch).

    2. yt-dlp / youtube-dl (Optional for users, Required for dev-dependencies)

    Required if you want Songbird to download audio/video from the internet (e.g., YouTube) and convert it to Opus.

    • via pip: pip install youtube_dl
    • Ubuntu: sudo apt install youtube-dl
    • Arch: sudo pacman -S youtube-dl
  8. Install Songbird and configure codec support

    current

    To use Songbird, add it to your Cargo.toml. By default, Songbird only supports Opus via the DCA file format. If you need support for other audio formats (like MP3, AAC, FLAC, etc.), you must also add symphonia as a dependency in your project and enable the required features.

    Default Setup (DCA/Opus only)

    [dependencies.songbird]
    version = "0.5"
    features = ["builtin-queue"]

    Extended Codec Setup (MP3, AAC, etc.)

    To enable additional codecs, include symphonia with the desired features:

    [dependencies.songbird]
    version = "0.5"
    features = ["builtin-queue"]
    
    [dependencies.symphonia]
    version = "0.5"
    features = ["aac", "mp3", "isomp4", "alac"]
    # Including songbird alone gives you support for Opus via the DCA file format.
    [dependencies.songbird]
    version = "0.5"
    features = ["builtin-queue"]
    
    # To get additional codecs, you *must* add Symphonia yourself.
    [dependencies.symphonia]
    version = "0.5"
    features = ["aac", "mp3", "isomp4", "alac"]
  9. Overview of Songbird features and modules

    current

    Songbird is an async, cross-library compatible voice system for Discord. Its functionality is divided into several feature-gated modules:

    • Gateway Frontend: Compatible with serenity and twilight (via gateway and [serenity/twilight] features). Can run driverless to manage Lavalink sessions.
    • Voice Driver: A standalone driver for voice calls (via driver feature). Requires a ConnectionInfo to operate.
    • Voice Receive: Handles RT(C)P packet handling and voice receive (via receive feature).
    • Core Features: Includes event handling, seeking on compatible streams, shared multithreaded audio stream caches, and direct Opus data passthrough from DCA files.
  10. Configure RTP packet decoding with DecodeMode

    current

    When receiving audio from Discord, you can specify how RTP packets are handled using the DecodeMode enum. This determines the CPU overhead and the state of the audio data passed to your event handlers.

    • DecodeMode::Pass: No changes are applied to the packets. This involves no CPU work but provides raw, encrypted packets.
    • DecodeMode::Decrypt: Decrypts the body of each received packet. This has a small per-packet CPU cost.
    • DecodeMode::Decode(DecodeConfig): Decrypts and decodes each packet, accounting for packet loss. This has the highest per-packet CPU cost but provides usable audio samples.

    You can check the capabilities of a mode using should_decode() and should_decrypt().

    use songbird::driver::decode_mode::{DecodeMode, DecodeConfig, Channels, SampleRate};
    
    // Example: Using Decode mode with specific audio settings
    let config = DecodeConfig::new(Channels::Stereo, SampleRate::Hz48000);
    let mode = DecodeMode::Decode(config);
    
    if mode.should_decode() {
        // Handle decoded audio
    }
  11. Use TrackCallback to await driver operations

    current

    When calling methods like seek or make_playable, Songbird returns a TrackCallback<T>. This object represents an asynchronous reply from the driver.

    This object does not need to be .awaited for the driver to perform the action; the command is sent immediately. You can drop the callback if you don't care about the result.

    Methods

    • is_hung_up(): Returns true if the operation failed immediately because the target track was removed/discarded.
    • result(): Consumes the handle and blocks the current thread until the driver replies.
    • result_async(): Consumes the handle and asynchronously awaits the driver's reply.