use std::{fs, num::NonZero, sync::LazyLock};
pub use symphonia::core::errors::Error as SymphoniaError;
use symphonia::{
core::{
audio::GenericAudioBufferRef,
codecs::{
audio::{AudioDecoder, AudioDecoderOptions},
registry::CodecRegistry,
},
errors::SeekErrorKind,
formats::{FormatOptions, FormatReader, SeekMode, SeekTo, TrackType},
io::{MediaSourceStream, MediaSourceStreamOptions},
units::{Time, TimeBase},
},
default::formats::{AiffReader, FlacReader, IsoMp4Reader, MpaReader, OggReader, WavReader},
};
use thiserror::Error;
use crate::media_container::{ContainerError, ContainerFormat, MediaContainer, Stream};
#[derive(Debug, Error)]
pub enum DecodingError {
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
Symphonia(#[from] SymphoniaError),
#[error("{0}")]
Container(#[from] ContainerError),
#[error("Player attempted to play an unsupported container: '{0:?}'")]
UnsupportedContainer(ContainerFormat),
}
pub struct Decoder {
pub stream: Stream,
pub format_reader: Box<dyn FormatReader>,
pub decoder: Box<dyn AudioDecoder>,
pub current_frame: usize,
pub decoded_frames: usize,
pub at_eof: bool,
}
static CODEC_REGISTRY: LazyLock<CodecRegistry> = LazyLock::new(|| {
let mut codec_registry = CodecRegistry::new();
symphonia::default::register_enabled_codecs(&mut codec_registry);
#[cfg(feature = "opus")]
codec_registry.register_audio_decoder::<symphonia_adapter_libopus::OpusDecoder>();
codec_registry
});
impl Decoder {
pub fn from_container(
container: &MediaContainer,
buffer_len: usize,
) -> Result<Self, DecodingError> {
let file = fs::File::open(container.path())?;
let options = MediaSourceStreamOptions { buffer_len };
let mss = MediaSourceStream::new(Box::new(file), options);
let fmt_opts = FormatOptions::default();
let format_reader: Box<dyn FormatReader> = match container.format() {
ContainerFormat::Flac => Box::new(FlacReader::try_new(mss, fmt_opts)?),
ContainerFormat::Mp3 => Box::new(MpaReader::try_new(mss, fmt_opts)?),
ContainerFormat::Ogg => Box::new(OggReader::try_new(mss, fmt_opts)?),
ContainerFormat::Wav => Box::new(WavReader::try_new(mss, fmt_opts)?),
ContainerFormat::Aiff => Box::new(AiffReader::try_new(mss, fmt_opts)?),
ContainerFormat::Mp4 => Box::new(IsoMp4Reader::try_new(mss, fmt_opts)?),
other => return Err(DecodingError::UnsupportedContainer(*other)),
};
let track = format_reader
.default_track(TrackType::Audio)
.expect("Playable file does not have any supported codecs");
let dec_opts: AudioDecoderOptions = Default::default();
let params = track
.codec_params
.as_ref()
.expect("Track is not playable")
.audio()
.unwrap();
let decoder = CODEC_REGISTRY.make_audio_decoder(params, &dec_opts)?;
Ok(Self {
stream: Stream::try_from(track)?,
format_reader,
decoder,
current_frame: 0,
decoded_frames: 0,
at_eof: false,
})
}
pub fn decode_next_packet(
&mut self,
) -> Result<Option<GenericAudioBufferRef<'_>>, DecodingError> {
loop {
let packet = match self.format_reader.next_packet() {
Ok(Some(packet)) => packet,
Err(SymphoniaError::ResetRequired) => {
self.decoder.reset();
continue;
}
Err(SymphoniaError::IoError(err))
if matches!(err.kind(), std::io::ErrorKind::UnexpectedEof) =>
{
self.at_eof = true;
return Ok(None);
}
Ok(None) => {
self.at_eof = true;
return Ok(None);
}
Err(err) => return Err(DecodingError::Symphonia(err)),
};
while !self.format_reader.metadata().is_latest() {
self.format_reader.metadata().pop();
}
if packet.track_id == self.stream.id {
return match self.decoder.decode(&packet) {
Ok(decoded) => {
self.current_frame += decoded.frames();
self.decoded_frames += decoded.frames();
Ok(Some(decoded))
}
Err(SymphoniaError::DecodeError(_)) => Ok(None),
Err(SymphoniaError::IoError(_)) => Ok(None),
Err(err) => Err(DecodingError::Symphonia(err)),
};
}
}
}
}
impl Decoder {
#[must_use]
pub fn time(&self) -> f64 {
let sample_rate = f64::from(self.stream.codec_params.sample_rate);
self.current_frame as f64 / sample_rate
}
#[must_use]
pub fn decoded_time(&self) -> f64 {
let sample_rate = f64::from(self.stream.codec_params.sample_rate);
self.decoded_frames as f64 / sample_rate
}
pub fn seek(&mut self, seconds: f64, increment: bool) -> Result<f64, SymphoniaError> {
let seconds = if increment {
(self.time() + seconds).max(0.0)
} else {
seconds
}
.max(0.0);
let timestamp = Time::try_from_secs_f64(seconds).unwrap();
let seeked_to = match self.format_reader.seek(
SeekMode::Accurate,
SeekTo::Time {
time: timestamp,
track_id: Some(self.stream.id),
},
) {
Ok(seeked_to) => seeked_to,
Err(SymphoniaError::SeekError(SeekErrorKind::OutOfRange)) => {
self.at_eof = true;
return Ok(self.stream.duration());
}
Err(other) => return Err(other),
};
self.decoder.reset();
self.at_eof = false;
let time_base = self
.stream
.time_base
.map(|(n, d)| TimeBase {
numer: NonZero::new(n).unwrap(),
denom: NonZero::new(d).unwrap(),
})
.unwrap();
let raw_time = time_base.calc_time(seeked_to.actual_ts).unwrap();
let time = raw_time.as_secs_f64();
let sample_rate = self.stream.codec_params.sample_rate;
self.current_frame = (time * f64::from(sample_rate)) as usize;
Ok(time)
}
}