Decode audio samples from a file
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(¶ms, &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
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(())
}