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 Multimedia

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. Each format sits behind its own feature, so enable the ones matching the formats you need.

This recipe requires the wav feature flag of symphonia to be enabled in Cargo.toml.

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 Multimedia

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(())
}