TinyAudio

repository·main·Indexed 42 days ago

https://github.com/mrdimas/tinyaudio

A lightweight, cross-platform Rust library for low-level audio output. TinyAudio provides a unified interface for playing f32 audio samples across Windows, Linux, macOS, Android, iOS, and WebAssembly, prioritizing ease of use and guaranteed buffer sizes. Version 2.0.0 supports automatic format conversion and platform-specific backends including ALSA, PulseAudio, and AAudio.

Tokens
3.3K
Snippets
11
Records
13
Agent score
53%

What's inside TinyAudio

  1. Overview of TinyAudio

    main

    TinyAudio is a cross-platform audio output library designed to provide unified, easy access to the default sound output device of an operating system. It is intended for users who need to play prepared audio samples with minimal complexity.

    Key Capabilities

    • Unified Access: Supports Windows, Linux, macOS, Android, iOS, and WebAssembly.
    • Automatic Format Conversion: Accepts floating-point audio samples (f32) and automatically converts them to the closest supported platform-dependent format.
    • Guaranteed Buffer Size: The crate guarantees that the intermediate data buffer will always match the requested size, which is useful for specific algorithms like HRTF.

    Limitations

    • No Format Loading: Does not load audio files (e.g., MP3, WAV).
    • No DSP: Does not provide Digital Signal Processing, spatialization, or effects.
    • No Device Management: Does not support device enumeration, selection, or querying supported formats.
    • No Input: Does not support audio input capturing (e.g., microphones).
  2. Build and run the Android example

    main

    To build and run the TinyAudio Android example on a physical device or emulator, follow these steps:

    1. Install the Rust target: Ensure you have the armv7-linux-androideabi target installed via rustup.
    2. Install NDK: Ensure the Android NDK is installed on your system.
    3. Configure Environment Variables: Ensure your Android environment variables (like ANDROID_HOME) are correctly set.
    4. Install cargo-apk: Install the cargo-apk tool to package and run the Rust application on Android.
    5. Connect Device: Connect an Android device via USB or start an Android Emulator.
    6. Run: Execute the build and run command targeting the armv7 architecture.

    Upon successful execution, a test app will run and play a 5-second sine wave.

    # Install the target
    rustup target add armv7-linux-androideabi
    
    # Install cargo-apk
    cargo install cargo-apk
    
    # Run the app on the device
    cargo-apk apk run --target=armv7-linux-androideabi
  3. Build and run WebAssembly examples

    main

    To build and run the WebAssembly examples for TinyAudio, you need the wasm32-unknown-unknown Rust target, wasm-pack, and basic-http-server. Follow these steps:

    1. Install Rust target: Ensure wasm32-unknown-unknown is installed via rustup.
    2. Install wasm-pack: Use cargo to install wasm-pack.
    3. Build the example: Run the build command with the web target and release profile.
    4. Install HTTP server: Use cargo to install basic-http-server.
    5. Serve the files: Execute basic-http-server from within the wasm-examples directory.

    Once the server is running, navigate to http://localhost:4000/ in your browser and click the "Start" button to hear a sine wave.

    # 1. Add the wasm target
    rustup target add wasm32-unknown-unknown
    
    # 2. Install wasm-pack
    cargo install wasm-pack
    
    # 3. Build the example
    wasm-pack build --target web --release
    
    # 4. Install basic-http-server
    cargo install basic-http-server
    
    # 5. Run the server in the wasm-examples directory
    # (Navigate to the directory first)
    cd wasm-examples
    basic-http-server
  4. Platform-specific requirements for Android and WebAssembly

    main

    Android

    • API Level: Requires API Level 26+ (Android 8.1 Oreo) as it uses the AAudio API.
    • Initialization Timing: You must initialize the audio device only after your application has gained focus (e.g., receiving the GainedFocus event in the android-activity crate). Failure to do so will cause device creation to fail.

    WebAssembly

    • User Interaction: Browsers require a user gesture (like a button click) to allow audio playback. You must initialize the audio device only after an action on the web page has occurred.
  5. Set up the TinyAudio iOS example project

    main

    To use the TinyAudio iOS example, you must first install cargo-lipo to enable building universal libraries for iOS.

    Additionally, the iOS project requires specific linking configuration in Xcode:

    1. Link the header file(s) generated from the Rust project.
    2. Link the following libraries/frameworks:
      • AudioToolbox.framework
      • libresolv.tbd
      • libtinyaudioexample.a
    cargo install cargo-lipo
  6. Build the TinyAudio iOS example

    main

    The iOS project is configured to automatically rebuild the Rust library during the build process. You can build and launch the project normally through Xcode.

    The build process utilizes two custom 'Run Script Phases' in the 'Build Phase' settings:

    1. Build static library: Uses cargo lipo with --xcode-integ to build the universal library from the Rust manifest.
    2. Cleanup: Deletes the generated static library from the Rust target directory to keep the workspace clean.
    cargo lipo --xcode-integ --manifest-path $(PROJECT_DIR)../Rust-TinyAudioExample/Cargo.toml
    
    # Cleanup script executed after build:
    rm -fv $(PROJECT_DIR)/../Rust-TinyAudioExample/target/universal/*/*.a
  7. Configure Linux audio backends (ALSA vs PulseAudio)

    main

    On Linux, TinyAudio supports two backends: ALSA (default) and PulseAudio.

    Prerequisites

    To compile on Linux, you must install the following development libraries:

    sudo apt-get install libasound2-dev libudev-dev pkg-config

    If you wish to use the PulseAudio backend, you must also install libpulse-dev:

    sudo apt-get install libpulse-dev

    Enabling PulseAudio

    To use PulseAudio instead of the default ALSA, configure your Cargo.toml to disable default features and enable the pulse feature:

    tinyaudio = { version = "2", default-features = false, features = ["pulse"] }
  8. Play a sine wave using TinyAudio

    main

    This example demonstrates how to generate a 440 Hz sine wave within the audio callback. The callback iterates through chunks of data corresponding to the number of channels and calculates the sine value based on a running clock.

    # use tinyaudio::prelude::*;
    let params = OutputDeviceParameters {
        channels_count: 2,
        sample_rate: 44100,
        channel_sample_count: 4410,
    };
    
    let _device = run_output_device(params, {
        let mut clock = 0f32;
        move |data| {
            for samples in data.chunks_mut(params.channels_count) {
                clock = (clock + 1.0) % params.sample_rate as f32;
                let value =
                    (clock * 440.0 * 2.0 * std::f32::consts::PI / params.sample_rate as f32).sin();
                for sample in samples {
                    *sample = value;
                }
            }
        }
    })
    .unwrap();
    
    std::thread::sleep(std::time::Duration::from_secs(5));
  9. Initialize an audio output device with run_output_device

    main

    To start audio playback, use the run_output_device function. This function creates an audio output context and uses a user-defined callback to supply samples. The callback is called periodically to generate new data as long as the device instance is alive.

    Usage Pattern

    1. Define OutputDeviceParameters specifying channels_count, sample_rate, and channel_sample_count.
    2. Provide a closure (callback) that receives a mutable slice of f32 samples (data).
    3. The callback must fill the provided data buffer with audio samples.

    Note: The device instance must be kept in scope (e.g., assigned to a variable) to keep the audio stream alive.

    use tinyaudio::prelude::*;
    
    let _device = run_output_device(
        OutputDeviceParameters {
            channels_count: 2,
            sample_rate: 44100,
            channel_sample_count: 4410,
        },
        move |_| {
            // Output silence
        },
    )
    .unwrap();
  10. Configure audio output with OutputDeviceParameters

    main

    Use the OutputDeviceParameters struct to define the audio format for your output device.

    Key fields:

    • sample_rate: The frequency of your audio data in Hz (e.g., 44100, 48000).
    • channels_count: The number of audio channels (e.g., 1 for mono, 2 for stereo). Data is provided in an interleaved format (LRLRLR...).
    • channel_sample_count: The number of samples per channel per callback. This controls audio latency. Higher values increase latency but reduce the risk of audio glitches. To target a specific duration in seconds, use: channel_sample_count = sample_rate * time_in_seconds.
    let params = OutputDeviceParameters {
        channels_count: 2,
        sample_rate: 44100,
        channel_sample_count: 4410,
    };
  11. Example: Playing a 440 Hz sine wave

    main

    This example demonstrates how to implement a data_callback to generate a continuous 440 Hz sine wave for 5 seconds using run_output_device.

    # use tinyaudio::prelude::*;
    let params = OutputDeviceParameters {
        channels_count: 2,
        sample_rate: 44100,
        channel_sample_count: 4410,
    };
    
    let _device = run_output_device(params, {
        let mut clock = 0f32;
        move |data| {
            for samples in data.chunks_mut(params.channels_count) {
                clock = (clock + 1.0) % params.sample_rate as f32;
                let value =
                    (clock * 440.0 * 2.0 * std::f32::consts::PI / params.sample_rate as f32).sin();
                for sample in samples {
                    *sample = value;
                }
            }
        }
    })
    .unwrap();
    
    std::thread::sleep(std::time::Duration::from_secs(5));
  12. Manage the lifecycle of an OutputDevice

    main

    The OutputDevice struct acts as an opaque handle to the active platform-dependent audio device. To release all system resources occupied by the device and stop playback, call the .close() method.

    let mut device = run_output_device(params, callback).unwrap();
    // ... play audio ...
    device.close();