use std::{io, time::Duration};
use symphonia::core::{
audio::SampleBuffer,
codecs::{Decoder, DecoderOptions},
errors::Error,
formats::{FormatOptions, SeekMode, SeekTo},
io::{MediaSource, MediaSourceStream, MediaSourceStreamOptions},
meta::{MetadataOptions, StandardTagKey, Value},
probe::{Hint, ProbeResult},
};
use super::{AudioDecoder, AudioPacket, AudioPacketPosition, DecoderError, DecoderResult};
use crate::{NUM_CHANNELS, PAGES_PER_MS, SAMPLE_RATE, player::NormalisationData, symphonia_util};
pub struct SymphoniaDecoder {
probe_result: ProbeResult,
decoder: Box<dyn Decoder>,
sample_buffer: Option<SampleBuffer<f64>>,
}
#[derive(Default)]
pub(crate) struct LocalFileMetadata {
pub name: Option<String>,
pub language: Option<String>,
pub album: Option<String>,
pub artists: Option<String>,
pub album_artists: Option<String>,
pub number: Option<u32>,
pub disc_number: Option<u32>,
}
impl SymphoniaDecoder {
pub fn new<R>(input: R, hint: Hint) -> DecoderResult<Self>
where
R: MediaSource + 'static,
{
let mss_opts = MediaSourceStreamOptions {
buffer_len: librespot_audio::AudioFetchParams::get().minimum_download_size,
};
let mss = MediaSourceStream::new(Box::new(input), mss_opts);
let format_opts = FormatOptions {
enable_gapless: true,
..Default::default()
};
let metadata_opts: MetadataOptions = Default::default();
let probe_result =
symphonia::default::get_probe().format(&hint, mss, &format_opts, &metadata_opts)?;
let format = &probe_result.format;
let track = format.default_track().ok_or_else(|| {
DecoderError::SymphoniaDecoder("Could not retrieve default track".into())
})?;
let decoder_opts: DecoderOptions = Default::default();
let decoder = symphonia::default::get_codecs().make(&track.codec_params, &decoder_opts)?;
let rate = decoder.codec_params().sample_rate.ok_or_else(|| {
DecoderError::SymphoniaDecoder("Could not retrieve sample rate".into())
})?;
if rate != SAMPLE_RATE {
return Err(DecoderError::SymphoniaDecoder(format!(
"Unsupported sample rate: {rate}"
)));
}
let channels = decoder.codec_params().channels.ok_or_else(|| {
DecoderError::SymphoniaDecoder("Could not retrieve channel configuration".into())
})?;
if channels.count() != NUM_CHANNELS as usize {
return Err(DecoderError::SymphoniaDecoder(format!(
"Unsupported number of channels: {channels}"
)));
}
Ok(Self {
probe_result,
decoder,
sample_buffer: None,
})
}
pub fn normalisation_data(&mut self) -> Option<NormalisationData> {
let metadata = symphonia_util::get_latest_metadata(&mut self.probe_result)?;
let tags = metadata.current()?.tags();
if tags.is_empty() {
None
} else {
let mut data = NormalisationData::default();
for tag in tags {
if let Value::Float(value) = tag.value {
match tag.std_key {
Some(StandardTagKey::ReplayGainAlbumGain) => data.album_gain_db = value,
Some(StandardTagKey::ReplayGainAlbumPeak) => data.album_peak = value,
Some(StandardTagKey::ReplayGainTrackGain) => data.track_gain_db = value,
Some(StandardTagKey::ReplayGainTrackPeak) => data.track_peak = value,
_ => (),
}
}
}
Some(data)
}
}
pub(crate) fn local_file_metadata(&mut self) -> Option<LocalFileMetadata> {
let metadata = symphonia_util::get_latest_metadata(&mut self.probe_result)?;
let tags = metadata.current()?.tags();
let mut metadata = LocalFileMetadata::default();
for tag in tags {
if let Value::String(value) = &tag.value {
match tag.std_key {
Some(StandardTagKey::TrackTitle) => metadata.name = Some(value.clone()),
Some(StandardTagKey::Language) => metadata.language = Some(value.clone()),
Some(StandardTagKey::Artist) => metadata.artists = Some(value.clone()),
Some(StandardTagKey::AlbumArtist) => {
metadata.album_artists = Some(value.clone())
}
Some(StandardTagKey::Album) => metadata.album = Some(value.clone()),
Some(StandardTagKey::TrackNumber) => {
metadata.number = match value.parse::<u32>() {
Ok(value) => Some(value),
Err(e) => {
warn!(
"Failed to parse local file's track number tag '{value}': {e}"
);
None
}
}
}
Some(StandardTagKey::DiscNumber) => {
metadata.disc_number = match value.parse::<u32>() {
Ok(value) => Some(value),
Err(e) => {
warn!(
"Failed to parse local file's disc number tag '{value}': {e}"
);
None
}
}
}
_ => (),
}
} else if let Value::UnsignedInt(value) = &tag.value {
match tag.std_key {
Some(StandardTagKey::TrackNumber) => metadata.number = Some(*value as u32),
Some(StandardTagKey::DiscNumber) => metadata.disc_number = Some(*value as u32),
_ => (),
}
} else if let Value::SignedInt(value) = &tag.value {
match tag.std_key {
Some(StandardTagKey::TrackNumber) => metadata.number = Some(*value as u32),
Some(StandardTagKey::DiscNumber) => metadata.disc_number = Some(*value as u32),
_ => (),
}
}
}
Some(metadata)
}
#[inline]
fn ts_to_ms(&self, ts: u64) -> u32 {
match self.decoder.codec_params().time_base {
Some(time_base) => {
let time = Duration::from(time_base.calc_time(ts));
time.as_millis() as u32
}
None => (ts as f64 * PAGES_PER_MS) as u32,
}
}
}
impl AudioDecoder for SymphoniaDecoder {
fn seek(&mut self, position_ms: u32) -> Result<u32, DecoderError> {
let mut target = Duration::from_millis(position_ms.into());
let codec_params = self.decoder.codec_params();
if let (Some(time_base), Some(n_frames)) = (codec_params.time_base, codec_params.n_frames) {
let duration = Duration::from(time_base.calc_time(n_frames));
if target > duration {
target = duration;
}
}
let seeked_to_ts = self.probe_result.format.seek(
SeekMode::Accurate,
SeekTo::Time {
time: target.into(),
track_id: None,
},
)?;
self.decoder.reset();
Ok(self.ts_to_ms(seeked_to_ts.actual_ts))
}
fn next_packet(&mut self) -> DecoderResult<Option<(AudioPacketPosition, AudioPacket)>> {
let mut skipped = false;
loop {
let packet = match self.probe_result.format.next_packet() {
Ok(packet) => packet,
Err(Error::IoError(err)) => {
if err.kind() == io::ErrorKind::UnexpectedEof {
return Ok(None);
} else {
return Err(DecoderError::SymphoniaDecoder(err.to_string()));
}
}
Err(err) => {
return Err(err.into());
}
};
let position_ms = self.ts_to_ms(packet.ts());
let packet_position = AudioPacketPosition {
position_ms,
skipped,
};
match self.decoder.decode(&packet) {
Ok(decoded) => {
let sample_buffer = match self.sample_buffer.as_mut() {
Some(buffer) => buffer,
None => {
let spec = *decoded.spec();
let duration = decoded.capacity() as u64;
self.sample_buffer.insert(SampleBuffer::new(duration, spec))
}
};
sample_buffer.copy_interleaved_ref(decoded);
let samples = AudioPacket::Samples(sample_buffer.samples().to_vec());
return Ok(Some((packet_position, samples)));
}
Err(Error::DecodeError(_)) => {
warn!("Skipping malformed audio packet at {position_ms} ms");
skipped = true;
continue;
}
Err(err) => return Err(err.into()),
}
}
}
}