mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Reintroduce offset for passthrough decoder
This commit is contained in:
parent
84e3fe5558
commit
096269c1d0
6 changed files with 81 additions and 49 deletions
|
@ -16,6 +16,27 @@ impl Deref for AudioFiles {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AudioFiles {
|
||||||
|
pub fn is_ogg_vorbis(format: AudioFileFormat) -> bool {
|
||||||
|
matches!(
|
||||||
|
format,
|
||||||
|
AudioFileFormat::OGG_VORBIS_320
|
||||||
|
| AudioFileFormat::OGG_VORBIS_160
|
||||||
|
| AudioFileFormat::OGG_VORBIS_96
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_mp3(format: AudioFileFormat) -> bool {
|
||||||
|
matches!(
|
||||||
|
format,
|
||||||
|
AudioFileFormat::MP3_320
|
||||||
|
| AudioFileFormat::MP3_256
|
||||||
|
| AudioFileFormat::MP3_160
|
||||||
|
| AudioFileFormat::MP3_96
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&[AudioFileMessage]> for AudioFiles {
|
impl From<&[AudioFileMessage]> for AudioFiles {
|
||||||
fn from(files: &[AudioFileMessage]) -> Self {
|
fn from(files: &[AudioFileMessage]) -> Self {
|
||||||
let audio_files = files
|
let audio_files = files
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
pub mod file;
|
pub mod file;
|
||||||
pub mod item;
|
pub mod item;
|
||||||
|
|
||||||
pub use file::AudioFileFormat;
|
pub use file::{AudioFileFormat, AudioFiles};
|
||||||
pub use item::AudioItem;
|
pub use item::AudioItem;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::metadata::audio::AudioFileFormat;
|
|
||||||
|
|
||||||
mod passthrough_decoder;
|
mod passthrough_decoder;
|
||||||
pub use passthrough_decoder::PassthroughDecoder;
|
pub use passthrough_decoder::PassthroughDecoder;
|
||||||
|
|
||||||
|
@ -59,31 +57,6 @@ impl AudioPacket {
|
||||||
pub trait AudioDecoder {
|
pub trait AudioDecoder {
|
||||||
fn seek(&mut self, absgp: u64) -> Result<u64, DecoderError>;
|
fn seek(&mut self, absgp: u64) -> Result<u64, DecoderError>;
|
||||||
fn next_packet(&mut self) -> DecoderResult<Option<AudioPacket>>;
|
fn next_packet(&mut self) -> DecoderResult<Option<AudioPacket>>;
|
||||||
|
|
||||||
fn is_ogg_vorbis(format: AudioFileFormat) -> bool
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
matches!(
|
|
||||||
format,
|
|
||||||
AudioFileFormat::OGG_VORBIS_320
|
|
||||||
| AudioFileFormat::OGG_VORBIS_160
|
|
||||||
| AudioFileFormat::OGG_VORBIS_96
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_mp3(format: AudioFileFormat) -> bool
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
matches!(
|
|
||||||
format,
|
|
||||||
AudioFileFormat::MP3_320
|
|
||||||
| AudioFileFormat::MP3_256
|
|
||||||
| AudioFileFormat::MP3_160
|
|
||||||
| AudioFileFormat::MP3_96
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<symphonia::core::errors::Error> for DecoderError {
|
impl From<symphonia::core::errors::Error> for DecoderError {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use ogg::{OggReadError, Packet, PacketReader, PacketWriteEndInfo, PacketWriter};
|
||||||
|
|
||||||
use super::{AudioDecoder, AudioPacket, DecoderError, DecoderResult};
|
use super::{AudioDecoder, AudioPacket, DecoderError, DecoderResult};
|
||||||
|
|
||||||
use crate::metadata::audio::AudioFileFormat;
|
use crate::metadata::audio::{AudioFileFormat, AudioFiles};
|
||||||
|
|
||||||
fn get_header<T>(code: u8, rdr: &mut PacketReader<T>) -> DecoderResult<Box<[u8]>>
|
fn get_header<T>(code: u8, rdr: &mut PacketReader<T>) -> DecoderResult<Box<[u8]>>
|
||||||
where
|
where
|
||||||
|
@ -44,7 +44,7 @@ pub struct PassthroughDecoder<R: Read + Seek> {
|
||||||
impl<R: Read + Seek> PassthroughDecoder<R> {
|
impl<R: Read + Seek> PassthroughDecoder<R> {
|
||||||
/// Constructs a new Decoder from a given implementation of `Read + Seek`.
|
/// Constructs a new Decoder from a given implementation of `Read + Seek`.
|
||||||
pub fn new(rdr: R, format: AudioFileFormat) -> DecoderResult<Self> {
|
pub fn new(rdr: R, format: AudioFileFormat) -> DecoderResult<Self> {
|
||||||
if !Self::is_ogg_vorbis(format) {
|
if !AudioFiles::is_ogg_vorbis(format) {
|
||||||
return Err(DecoderError::PassthroughDecoder(format!(
|
return Err(DecoderError::PassthroughDecoder(format!(
|
||||||
"Passthrough decoder is not implemented for format {:?}",
|
"Passthrough decoder is not implemented for format {:?}",
|
||||||
format
|
format
|
||||||
|
|
|
@ -12,7 +12,10 @@ use symphonia::core::{
|
||||||
|
|
||||||
use super::{AudioDecoder, AudioPacket, DecoderError, DecoderResult};
|
use super::{AudioDecoder, AudioPacket, DecoderError, DecoderResult};
|
||||||
|
|
||||||
use crate::{metadata::audio::AudioFileFormat, player::NormalisationData};
|
use crate::{
|
||||||
|
metadata::audio::{AudioFileFormat, AudioFiles},
|
||||||
|
player::NormalisationData,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct SymphoniaDecoder {
|
pub struct SymphoniaDecoder {
|
||||||
track_id: u32,
|
track_id: u32,
|
||||||
|
@ -33,10 +36,10 @@ impl SymphoniaDecoder {
|
||||||
|
|
||||||
// Not necessary, but speeds up loading.
|
// Not necessary, but speeds up loading.
|
||||||
let mut hint = Hint::new();
|
let mut hint = Hint::new();
|
||||||
if Self::is_ogg_vorbis(format) {
|
if AudioFiles::is_ogg_vorbis(format) {
|
||||||
hint.with_extension("ogg");
|
hint.with_extension("ogg");
|
||||||
hint.mime_type("audio/ogg");
|
hint.mime_type("audio/ogg");
|
||||||
} else if Self::is_mp3(format) {
|
} else if AudioFiles::is_mp3(format) {
|
||||||
hint.with_extension("mp3");
|
hint.with_extension("mp3");
|
||||||
hint.mime_type("audio/mp3");
|
hint.mime_type("audio/mp3");
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ use crate::{
|
||||||
convert::Converter,
|
convert::Converter,
|
||||||
core::{util::SeqGenerator, Error, Session, SpotifyId},
|
core::{util::SeqGenerator, Error, Session, SpotifyId},
|
||||||
decoder::{AudioDecoder, AudioPacket, PassthroughDecoder, SymphoniaDecoder},
|
decoder::{AudioDecoder, AudioPacket, PassthroughDecoder, SymphoniaDecoder},
|
||||||
metadata::audio::{AudioFileFormat, AudioItem},
|
metadata::audio::{AudioFileFormat, AudioFiles, AudioItem},
|
||||||
mixer::AudioFilter,
|
mixer::AudioFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,6 +39,10 @@ use crate::{MS_PER_PAGE, NUM_CHANNELS, PAGES_PER_MS, SAMPLES_PER_SECOND};
|
||||||
const PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS: u32 = 30000;
|
const PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS: u32 = 30000;
|
||||||
pub const DB_VOLTAGE_RATIO: f64 = 20.0;
|
pub const DB_VOLTAGE_RATIO: f64 = 20.0;
|
||||||
|
|
||||||
|
// Spotify inserts a custom Ogg packet at the start with custom metadata values, that you would
|
||||||
|
// otherwise expect in Vorbis comments. This packet isn't well-formed and players may balk at it.
|
||||||
|
const SPOTIFY_OGG_HEADER_END: u64 = 0xa7;
|
||||||
|
|
||||||
pub type PlayerResult = Result<(), Error>;
|
pub type PlayerResult = Result<(), Error>;
|
||||||
|
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
|
@ -907,19 +911,27 @@ impl PlayerTrackLoader {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let decrypted_file = AudioDecrypt::new(key, encrypted_file);
|
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
|
||||||
let mut audio_file =
|
|
||||||
Subfile::new(decrypted_file, stream_loader_controller.len() as u64);
|
let is_ogg_vorbis = AudioFiles::is_ogg_vorbis(format);
|
||||||
|
let (offset, mut normalisation_data) = if is_ogg_vorbis {
|
||||||
|
// Spotify stores normalisation data in a custom Ogg packet instead of Vorbis comments.
|
||||||
|
let normalisation_data =
|
||||||
|
NormalisationData::parse_from_ogg(&mut decrypted_file).ok();
|
||||||
|
(SPOTIFY_OGG_HEADER_END, normalisation_data)
|
||||||
|
} else {
|
||||||
|
(0, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let audio_file = Subfile::new(
|
||||||
|
decrypted_file,
|
||||||
|
offset,
|
||||||
|
stream_loader_controller.len() as u64,
|
||||||
|
);
|
||||||
|
|
||||||
let mut normalisation_data = None;
|
|
||||||
let result = if self.config.passthrough {
|
let result = if self.config.passthrough {
|
||||||
PassthroughDecoder::new(audio_file, format).map(|x| Box::new(x) as Decoder)
|
PassthroughDecoder::new(audio_file, format).map(|x| Box::new(x) as Decoder)
|
||||||
} else {
|
} else {
|
||||||
// Spotify stores normalisation data in a custom Ogg packet instead of Vorbis comments.
|
|
||||||
if SymphoniaDecoder::is_ogg_vorbis(format) {
|
|
||||||
normalisation_data = NormalisationData::parse_from_ogg(&mut audio_file).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
SymphoniaDecoder::new(audio_file, format).map(|mut decoder| {
|
SymphoniaDecoder::new(audio_file, format).map(|mut decoder| {
|
||||||
// For formats other that Vorbis, we'll try getting normalisation data from
|
// For formats other that Vorbis, we'll try getting normalisation data from
|
||||||
// ReplayGain metadata fields, if present.
|
// ReplayGain metadata fields, if present.
|
||||||
|
@ -2206,21 +2218,30 @@ impl fmt::Debug for PlayerState {
|
||||||
|
|
||||||
struct Subfile<T: Read + Seek> {
|
struct Subfile<T: Read + Seek> {
|
||||||
stream: T,
|
stream: T,
|
||||||
|
offset: u64,
|
||||||
length: u64,
|
length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Read + Seek> Subfile<T> {
|
impl<T: Read + Seek> Subfile<T> {
|
||||||
pub fn new(mut stream: T, length: u64) -> Subfile<T> {
|
pub fn new(mut stream: T, offset: u64, length: u64) -> Subfile<T> {
|
||||||
match stream.seek(SeekFrom::Start(0)) {
|
let target = SeekFrom::Start(offset);
|
||||||
|
match stream.seek(target) {
|
||||||
Ok(pos) => {
|
Ok(pos) => {
|
||||||
if pos != 0 {
|
if pos != offset {
|
||||||
error!("Subfile::new seeking to 0 but position is now {:?}", pos);
|
error!(
|
||||||
|
"Subfile::new seeking to {:?} but position is now {:?}",
|
||||||
|
target, pos
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => error!("Subfile new Error: {}", e),
|
Err(e) => error!("Subfile new Error: {}", e),
|
||||||
}
|
}
|
||||||
|
|
||||||
Subfile { stream, length }
|
Subfile {
|
||||||
|
stream,
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2232,7 +2253,21 @@ impl<T: Read + Seek> Read for Subfile<T> {
|
||||||
|
|
||||||
impl<T: Read + Seek> Seek for Subfile<T> {
|
impl<T: Read + Seek> Seek for Subfile<T> {
|
||||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||||
self.stream.seek(pos)
|
let pos = match pos {
|
||||||
|
SeekFrom::Start(offset) => SeekFrom::Start(offset + self.offset),
|
||||||
|
x => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
let newpos = self.stream.seek(pos)?;
|
||||||
|
|
||||||
|
if newpos >= self.offset {
|
||||||
|
Ok(newpos - self.offset)
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
"newpos < self.offset",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue