Overview
The FormatReader trait defines the interface for container demuxers. It provides methods to probe media containers, enumerate tracks, read packets, perform seeks, and access metadata and cue points.
Definition
pub trait FormatReader: Send + Sync {
fn try_new(source: MediaSourceStream, options: &FormatOptions) -> Result<Self>
where
Self: Sized;
fn cues(&self) -> &[Cue];
fn metadata(&mut self) -> Metadata<'_>;
fn seek(&mut self, mode: SeekMode, to: SeekTo) -> Result<SeekedTo>;
fn tracks(&self) -> &[Track];
fn default_track(&self) -> Option<&Track>;
fn next_packet(&mut self) -> Result<Packet>;
fn into_inner(self: Box<Self>) -> MediaSourceStream;
}
A container demuxer that reads packetized, interleaved codec bitstreams from media containers.
Methods
try_new
fn try_new(source: MediaSourceStream, options: &FormatOptions) -> Result<Self>
where
Self: Sized
Attempt to instantiate a FormatReader using the provided options and media source stream. The reader probes the container to verify format support, determine tracks, and read initial metadata.
The media source stream to read from
Options for the format reader
The instantiated format reader or an error
tracks
fn tracks(&self) -> &[Track]
Get a list of all tracks in the container.
Slice of tracks in the media container
Example:
for track in format.tracks() {
println!("Track {}: codec={:?}, language={:?}",
track.id,
track.codec_params.codec,
track.language
);
}
default_track
fn default_track(&self) -> Option<&Track>
Get the default track. If the format has a method of determining the default track, this returns it. Otherwise, returns the first track, or None if no tracks are present.
The default track if available
Example:
if let Some(track) = format.default_track() {
let decoder = codec_registry.make(&track.codec_params, &decoder_opts)?;
}
next_packet
fn next_packet(&mut self) -> Result<Packet>
Get the next packet from the container.
The next packet, or an error. Returns ResetRequired if the track list must be re-examined.
Example:
loop {
let packet = match format.next_packet() {
Ok(packet) => packet,
Err(Error::IoError(err)) if err.kind() == std::io::ErrorKind::UnexpectedEof => {
break;
}
Err(err) => return Err(err),
};
// Process packet...
}
seek
fn seek(&mut self, mode: SeekMode, to: SeekTo) -> Result<SeekedTo>
Seek to the specified time or timestamp. Returns the requested and actual timestamps seeked to.
The seek precision mode (Coarse or Accurate)
The position to seek to (time or timestamp)
Information about the seek result including actual position
After a seek, all decoders consuming packets from this reader should be reset using Decoder::reset().
The FormatReader can only seek to the nearest packet, not to an exact frame. For sample-accurate seeking, the decoder must decode packets until the requested position is reached. With SeekMode::Accurate, the seeked position is always before the requested position.
Example:
use symphonia::core::formats::{SeekMode, SeekTo};
use symphonia::core::units::Time;
// Seek to 30 seconds
let seeked = format.seek(
SeekMode::Accurate,
SeekTo::Time {
time: Time::new(30, 0.0),
track_id: None
}
)?;
println!("Seeked to timestamp: {}", seeked.actual_ts);
// Reset decoder after seek
decoder.reset();
fn metadata(&mut self) -> Metadata<'_>
Get the metadata revision log.
Example:
let metadata = format.metadata();
if let Some(rev) = metadata.current() {
for tag in rev.tags() {
println!("{}: {}", tag.std_key, tag.value);
}
}
cues
Get all cue points (chapters, timestamps, etc.) in the media.
Example:
for cue in format.cues() {
println!("Cue {}: timestamp={}", cue.index, cue.start_ts);
for tag in &cue.tags {
println!(" {}: {}", tag.std_key, tag.value);
}
}
into_inner
fn into_inner(self: Box<Self>) -> MediaSourceStream
Destroy the FormatReader and return the underlying media source stream.
The underlying media source stream
Supporting Types
Track
pub struct Track {
pub id: u32,
pub codec_params: CodecParameters,
pub language: Option<String>,
}
Represents an independently coded media bitstream within a container.
Unique identifier for the track
Codec parameters for the track
Language of the track (may be unknown)
new
pub fn new(id: u32, codec_params: CodecParameters) -> Self
Create a new track.
Packet
pub struct Packet {
track_id: u32,
pub ts: u64,
pub dur: u64,
pub trim_start: u32,
pub trim_end: u32,
pub data: Box<[u8]>,
}
Contains a discrete amount of encoded data for a single codec bitstream.
The track this packet belongs to
Timestamp in TimeBase units (relative to end of encoder delay if gapless is enabled)
Duration in TimeBase units (excluding delay/padding if gapless is enabled)
Number of frames to trim from start (encoder delay)
Number of frames to trim from end (encoder padding)
Methods
Construction:
pub fn new_from_slice(track_id: u32, ts: u64, dur: u64, buf: &[u8]) -> Self
pub fn new_from_boxed_slice(track_id: u32, ts: u64, dur: u64, data: Box<[u8]>) -> Self
pub fn new_trimmed_from_slice(
track_id: u32, ts: u64, dur: u64,
trim_start: u32, trim_end: u32,
buf: &[u8]
) -> Self
pub fn new_trimmed_from_boxed_slice(
track_id: u32, ts: u64, dur: u64,
trim_start: u32, trim_end: u32,
data: Box<[u8]>
) -> Self
Accessors:
pub fn track_id(&self) -> u32
pub fn ts(&self) -> u64
pub fn dur(&self) -> u64
pub fn block_dur(&self) -> u64 // Duration without trimming
pub fn trim_start(&self) -> u32
pub fn trim_end(&self) -> u32
pub fn buf(&self) -> &[u8]
pub fn as_buf_reader(&self) -> BufReader<'_>
Cue
pub struct Cue {
pub index: u32,
pub start_ts: u64,
pub tags: Vec<Tag>,
pub points: Vec<CuePoint>,
}
A designated point of time within a media stream (chapter, cuesheet entry, etc.).
Starting timestamp in frames from the start of the stream
Tags associated with the cue (title, artist, etc.)
Sub-points within this cue
CuePoint
pub struct CuePoint {
pub start_offset_ts: u64,
pub tags: Vec<Tag>,
}
A point within a parent cue, providing more precise indexing.
Offset of the first frame relative to the parent cue’s start
Tags associated with this cue point
Seeking Types
SeekTo
pub enum SeekTo {
Time {
time: Time,
track_id: Option<u32>,
},
TimeStamp {
ts: TimeStamp,
track_id: u32,
},
}
Specifies a position to seek to.
Seek to a time in regular time units. If track_id is None, uses the default track.
Seek to a timestamp in a specific track’s timebase units
SeekedTo
pub struct SeekedTo {
pub track_id: u32,
pub required_ts: TimeStamp,
pub actual_ts: TimeStamp,
}
The result of a seek operation.
The track the seek was relative to
The timestamp that was requested
The timestamp that was actually seeked to
SeekMode
pub enum SeekMode {
Coarse,
Accurate,
}
Selects the precision of a seek.
Best-effort seek that may land before or after the requested position. Optional performance enhancement; may fall back to accurate seeking.
Sample-accurate seek that always lands before the requested position
pub struct FormatOptions {
pub prebuild_seek_index: bool,
pub seek_index_fill_rate: u16,
pub enable_gapless: bool,
}
Common options for all demuxers.
If a seek index is required but not provided by the container, build it during instantiation instead of progressively. Default: false.
How often in seconds of decoded content to add a seek index entry. Default: 20. Lower values use more memory but require less I/O during seeks.
Enable gapless playback support. Default: false. When enabled, provides trim information and adjusts timestamps/durations to exclude encoder delay and padding.
Complete Example
use symphonia::core::formats::{FormatOptions, SeekMode, SeekTo};
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;
use symphonia::core::io::MediaSourceStream;
use symphonia::core::units::Time;
use std::fs::File;
fn read_file(path: &str) -> Result<(), Box<dyn std::error::Error>> {
// Open and probe the file
let file = File::open(path)?;
let mss = MediaSourceStream::new(Box::new(file), Default::default());
let mut hint = Hint::new();
hint.with_extension("flac");
let probe = symphonia::default::get_probe();
let mut format_opts = FormatOptions::default();
format_opts.enable_gapless = true;
let probed = probe.format(&hint, mss, &format_opts, &MetadataOptions::default())?;
let mut format = probed.format;
// Print tracks
println!("Tracks:");
for track in format.tracks() {
println!(" Track {}: {:?}", track.id, track.codec_params.codec);
}
// Print cues/chapters
println!("\nChapters:");
for cue in format.cues() {
println!(" Cue {}: start={}", cue.index, cue.start_ts);
}
// Select default track
let track = format.default_track().unwrap();
let codec_registry = symphonia::default::get_codecs();
let mut decoder = codec_registry.make(
&track.codec_params,
&Default::default()
)?;
// Seek to 30 seconds
let seeked = format.seek(
SeekMode::Accurate,
SeekTo::Time {
time: Time::new(30, 0.0),
track_id: Some(track.id)
}
)?;
decoder.reset();
// Read and decode packets
loop {
let packet = match format.next_packet() {
Ok(packet) => packet,
Err(_) => break,
};
if packet.track_id() != track.id {
continue;
}
let decoded = decoder.decode(&packet)?;
// Process decoded audio...
}
Ok(())
}
See Also