Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Decode audio samples from a file

symphonia-badge cat-multimedia-badge

Measuring a clip’s length or feeding it to an analyzer means decoding its samples, whatever the container. symphonia probes the format, then turns packets into audio buffers. get_probe identifies the container from a Hint and a MediaSourceStream, returning a FormatReader. default_track picks the audio track and get_codecs builds a decoder for its CodecParameters. Each packet from next_packet goes to Decoder::decode, whose buffer reports its frame count. The wav feature compiles in only the WAV reader; enable the features matching the formats you need.

use std::fs::File;
use std::path::Path;

use anyhow::{Result, anyhow};
use symphonia::core::codecs::CodecParameters;
use symphonia::core::codecs::audio::AudioDecoderOptions;
use symphonia::core::formats::probe::Hint;
use symphonia::core::formats::{FormatOptions, TrackType};
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;

struct Decoded {
    sample_rate: u32,
    channels: usize,
    frames: u64,
}

fn decode(path: &Path) -> Result<Decoded> {
    let file = File::open(path)?;
    let stream = MediaSourceStream::new(Box::new(file), Default::default());

    let mut hint = Hint::new();
    hint.with_extension("wav");
    let mut format = symphonia::default::get_probe().probe(
        &hint,
        stream,
        FormatOptions::default(),
        MetadataOptions::default(),
    )?;

    let track = format
        .default_track(TrackType::Audio)
        .ok_or_else(|| anyhow!("no audio track"))?;
    let track_id = track.id;
    let params = match track.codec_params.as_ref() {
        Some(CodecParameters::Audio(params)) => params.clone(),
        _ => return Err(anyhow!("track is not audio")),
    };
    let sample_rate = params.sample_rate.unwrap_or(0);
    let channels = params.channels.as_ref().map(|c| c.count()).unwrap_or(0);

    let mut decoder = symphonia::default::get_codecs()
        .make_audio_decoder(&params, &AudioDecoderOptions::default())?;

    let mut frames = 0u64;
    while let Some(packet) = format.next_packet()? {
        if packet.track_id != track_id {
            continue;
        }
        let buffer = decoder.decode(&packet)?;
        frames += buffer.frames() as u64;
    }

    Ok(Decoded {
        sample_rate,
        channels,
        frames,
    })
}

fn main() -> Result<()> {
    let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/tone.wav");
    let audio = decode(&path)?;

    let seconds = audio.frames as f64 / audio.sample_rate as f64;
    println!(
        "{} channels at {} Hz, {} frames ({:.2}s)",
        audio.channels, audio.sample_rate, audio.frames, seconds
    );
    Ok(())
}

Play an audio file

rodio-badge cat-multimedia-badge

Playing a notification sound means opening an output device and handing it a decoded source. rodio does both. DeviceSinkBuilder::open_default_sink opens the system’s default output, play decodes the file and starts it on the device’s mixer, and Player::sleep_until_end blocks until playback finishes. Dropping the sink stops anything still playing, so rodio warns about it on drop; MixerDeviceSink::log_on_drop turns the warning off once the wait has returned.

use std::fs::File;
use std::io::BufReader;
use std::path::Path;

use anyhow::Result;

fn main() -> Result<()> {
    let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/tone.wav");

    let mut stream = rodio::DeviceSinkBuilder::open_default_sink()?;
    let file = BufReader::new(File::open(&path)?);

    let player = rodio::play(stream.mixer(), file)?;
    player.sleep_until_end();

    stream.log_on_drop(false);
    Ok(())
}