librespot_playback/decoder/
symphonia_decoder.rs

1use std::{io, time::Duration};
2
3use symphonia::core::{
4    audio::SampleBuffer,
5    codecs::{Decoder, DecoderOptions},
6    errors::Error,
7    formats::{FormatOptions, SeekMode, SeekTo},
8    io::{MediaSource, MediaSourceStream, MediaSourceStreamOptions},
9    meta::{MetadataOptions, StandardTagKey, Value},
10    probe::{Hint, ProbeResult},
11};
12
13use super::{AudioDecoder, AudioPacket, AudioPacketPosition, DecoderError, DecoderResult};
14
15use crate::{NUM_CHANNELS, PAGES_PER_MS, SAMPLE_RATE, player::NormalisationData, symphonia_util};
16
17pub struct SymphoniaDecoder {
18    probe_result: ProbeResult,
19    decoder: Box<dyn Decoder>,
20    sample_buffer: Option<SampleBuffer<f64>>,
21}
22
23#[derive(Default)]
24pub(crate) struct LocalFileMetadata {
25    pub name: Option<String>,
26    pub language: Option<String>,
27    pub album: Option<String>,
28    pub artists: Option<String>,
29    pub album_artists: Option<String>,
30    pub number: Option<u32>,
31    pub disc_number: Option<u32>,
32}
33
34impl SymphoniaDecoder {
35    pub fn new<R>(input: R, hint: Hint) -> DecoderResult<Self>
36    where
37        R: MediaSource + 'static,
38    {
39        let mss_opts = MediaSourceStreamOptions {
40            buffer_len: librespot_audio::AudioFetchParams::get().minimum_download_size,
41        };
42        let mss = MediaSourceStream::new(Box::new(input), mss_opts);
43
44        let format_opts = FormatOptions {
45            enable_gapless: true,
46            ..Default::default()
47        };
48        let metadata_opts: MetadataOptions = Default::default();
49
50        let probe_result =
51            symphonia::default::get_probe().format(&hint, mss, &format_opts, &metadata_opts)?;
52
53        let format = &probe_result.format;
54
55        let track = format.default_track().ok_or_else(|| {
56            DecoderError::SymphoniaDecoder("Could not retrieve default track".into())
57        })?;
58
59        let decoder_opts: DecoderOptions = Default::default();
60
61        let decoder = symphonia::default::get_codecs().make(&track.codec_params, &decoder_opts)?;
62
63        let rate = decoder.codec_params().sample_rate.ok_or_else(|| {
64            DecoderError::SymphoniaDecoder("Could not retrieve sample rate".into())
65        })?;
66
67        // TODO: The official client supports local files with sample rates other than 44,100 kHz.
68        // To play these accurately, we need to either resample the input audio, or introduce a way
69        // to change the player's current sample rate (likely by closing and re-opening the sink
70        // with new parameters).
71        if rate != SAMPLE_RATE {
72            return Err(DecoderError::SymphoniaDecoder(format!(
73                "Unsupported sample rate: {rate}"
74            )));
75        }
76
77        let channels = decoder.codec_params().channels.ok_or_else(|| {
78            DecoderError::SymphoniaDecoder("Could not retrieve channel configuration".into())
79        })?;
80        if channels.count() != NUM_CHANNELS as usize {
81            return Err(DecoderError::SymphoniaDecoder(format!(
82                "Unsupported number of channels: {channels}"
83            )));
84        }
85
86        Ok(Self {
87            probe_result,
88            decoder,
89            // We set the sample buffer when decoding the first full packet,
90            // whose duration is also the ideal sample buffer size.
91            sample_buffer: None,
92        })
93    }
94
95    pub fn normalisation_data(&mut self) -> Option<NormalisationData> {
96        let metadata = symphonia_util::get_latest_metadata(&mut self.probe_result)?;
97        let tags = metadata.current()?.tags();
98
99        if tags.is_empty() {
100            None
101        } else {
102            let mut data = NormalisationData::default();
103
104            for tag in tags {
105                if let Value::Float(value) = tag.value {
106                    match tag.std_key {
107                        Some(StandardTagKey::ReplayGainAlbumGain) => data.album_gain_db = value,
108                        Some(StandardTagKey::ReplayGainAlbumPeak) => data.album_peak = value,
109                        Some(StandardTagKey::ReplayGainTrackGain) => data.track_gain_db = value,
110                        Some(StandardTagKey::ReplayGainTrackPeak) => data.track_peak = value,
111                        _ => (),
112                    }
113                }
114            }
115
116            Some(data)
117        }
118    }
119
120    pub(crate) fn local_file_metadata(&mut self) -> Option<LocalFileMetadata> {
121        let metadata = symphonia_util::get_latest_metadata(&mut self.probe_result)?;
122        let tags = metadata.current()?.tags();
123        let mut metadata = LocalFileMetadata::default();
124
125        for tag in tags {
126            if let Value::String(value) = &tag.value {
127                match tag.std_key {
128                    // We could possibly use mem::take here to avoid cloning, but that risks leaving
129                    // the audio item metadata in a bad state.
130                    Some(StandardTagKey::TrackTitle) => metadata.name = Some(value.clone()),
131                    Some(StandardTagKey::Language) => metadata.language = Some(value.clone()),
132                    Some(StandardTagKey::Artist) => metadata.artists = Some(value.clone()),
133                    Some(StandardTagKey::AlbumArtist) => {
134                        metadata.album_artists = Some(value.clone())
135                    }
136                    Some(StandardTagKey::Album) => metadata.album = Some(value.clone()),
137                    Some(StandardTagKey::TrackNumber) => {
138                        metadata.number = match value.parse::<u32>() {
139                            Ok(value) => Some(value),
140                            Err(e) => {
141                                warn!(
142                                    "Failed to parse local file's track number tag '{value}': {e}"
143                                );
144                                None
145                            }
146                        }
147                    }
148                    Some(StandardTagKey::DiscNumber) => {
149                        metadata.disc_number = match value.parse::<u32>() {
150                            Ok(value) => Some(value),
151                            Err(e) => {
152                                warn!(
153                                    "Failed to parse local file's disc number tag '{value}': {e}"
154                                );
155                                None
156                            }
157                        }
158                    }
159                    _ => (),
160                }
161            } else if let Value::UnsignedInt(value) = &tag.value {
162                match tag.std_key {
163                    Some(StandardTagKey::TrackNumber) => metadata.number = Some(*value as u32),
164                    Some(StandardTagKey::DiscNumber) => metadata.disc_number = Some(*value as u32),
165                    _ => (),
166                }
167            } else if let Value::SignedInt(value) = &tag.value {
168                match tag.std_key {
169                    Some(StandardTagKey::TrackNumber) => metadata.number = Some(*value as u32),
170                    Some(StandardTagKey::DiscNumber) => metadata.disc_number = Some(*value as u32),
171                    _ => (),
172                }
173            }
174        }
175
176        Some(metadata)
177    }
178
179    #[inline]
180    fn ts_to_ms(&self, ts: u64) -> u32 {
181        match self.decoder.codec_params().time_base {
182            Some(time_base) => {
183                let time = Duration::from(time_base.calc_time(ts));
184                time.as_millis() as u32
185            }
186            // Fallback in the unexpected case that the format has no base time set.
187            None => (ts as f64 * PAGES_PER_MS) as u32,
188        }
189    }
190}
191
192impl AudioDecoder for SymphoniaDecoder {
193    fn seek(&mut self, position_ms: u32) -> Result<u32, DecoderError> {
194        // "Saturate" the position_ms to the duration of the track if it exceeds it.
195        let mut target = Duration::from_millis(position_ms.into());
196        let codec_params = self.decoder.codec_params();
197        if let (Some(time_base), Some(n_frames)) = (codec_params.time_base, codec_params.n_frames) {
198            let duration = Duration::from(time_base.calc_time(n_frames));
199            if target > duration {
200                target = duration;
201            }
202        }
203
204        // `track_id: None` implies the default track ID (of the container, not of Spotify).
205        let seeked_to_ts = self.probe_result.format.seek(
206            SeekMode::Accurate,
207            SeekTo::Time {
208                time: target.into(),
209                track_id: None,
210            },
211        )?;
212
213        // Seeking is a `FormatReader` operation, so the decoder cannot reliably
214        // know when a seek took place. Reset it to avoid audio glitches.
215        self.decoder.reset();
216
217        Ok(self.ts_to_ms(seeked_to_ts.actual_ts))
218    }
219
220    fn next_packet(&mut self) -> DecoderResult<Option<(AudioPacketPosition, AudioPacket)>> {
221        let mut skipped = false;
222
223        loop {
224            let packet = match self.probe_result.format.next_packet() {
225                Ok(packet) => packet,
226                Err(Error::IoError(err)) => {
227                    if err.kind() == io::ErrorKind::UnexpectedEof {
228                        return Ok(None);
229                    } else {
230                        return Err(DecoderError::SymphoniaDecoder(err.to_string()));
231                    }
232                }
233                Err(err) => {
234                    return Err(err.into());
235                }
236            };
237
238            let position_ms = self.ts_to_ms(packet.ts());
239            let packet_position = AudioPacketPosition {
240                position_ms,
241                skipped,
242            };
243
244            match self.decoder.decode(&packet) {
245                Ok(decoded) => {
246                    let sample_buffer = match self.sample_buffer.as_mut() {
247                        Some(buffer) => buffer,
248                        None => {
249                            let spec = *decoded.spec();
250                            let duration = decoded.capacity() as u64;
251                            self.sample_buffer.insert(SampleBuffer::new(duration, spec))
252                        }
253                    };
254
255                    sample_buffer.copy_interleaved_ref(decoded);
256                    let samples = AudioPacket::Samples(sample_buffer.samples().to_vec());
257
258                    return Ok(Some((packet_position, samples)));
259                }
260                Err(Error::DecodeError(_)) => {
261                    // The packet failed to decode due to corrupted or invalid data, get a new
262                    // packet and try again.
263                    warn!("Skipping malformed audio packet at {position_ms} ms");
264                    skipped = true;
265                    continue;
266                }
267                Err(err) => return Err(err.into()),
268            }
269        }
270    }
271}