1
0
Fork 0
mirror of https://github.com/librespot-org/librespot.git synced 2025-10-03 09:49:31 +02:00

Move decoder to playback crate

This commit is contained in:
johannesd3 2021-04-13 10:29:34 +02:00
parent 255f0c6ccf
commit 555274b5af
No known key found for this signature in database
GPG key ID: 8C2739E91D410F75
25 changed files with 116 additions and 111 deletions

View file

@ -14,18 +14,7 @@ version = "0.2.0"
aes-ctr = "0.6"
byteorder = "1.4"
bytes = "1.0"
cfg-if = "1"
lewton = "0.10"
log = "0.4"
futures-util = { version = "0.3", default_features = false }
ogg = "0.8"
tempfile = "3.1"
tokio = { version = "1", features = ["sync", "macros"] }
zerocopy = "0.3"
librespot-tremor = { version = "0.2", optional = true }
vorbis = { version ="0.0", optional = true }
[features]
with-tremor = ["librespot-tremor"]
with-vorbis = ["vorbis"]

View file

@ -1,56 +0,0 @@
use zerocopy::AsBytes;
#[derive(AsBytes, Copy, Clone, Debug)]
#[allow(non_camel_case_types)]
#[repr(transparent)]
pub struct i24([u8; 3]);
impl i24 {
fn pcm_from_i32(sample: i32) -> Self {
// drop the least significant byte
let [a, b, c, _d] = (sample >> 8).to_le_bytes();
i24([a, b, c])
}
}
// Losslessly represent [-1.0, 1.0] to [$type::MIN, $type::MAX] while maintaining DC linearity.
macro_rules! convert_samples_to {
($type: ident, $samples: expr) => {
convert_samples_to!($type, $samples, 0)
};
($type: ident, $samples: expr, $drop_bits: expr) => {
$samples
.iter()
.map(|sample| {
// Losslessly represent [-1.0, 1.0] to [$type::MIN, $type::MAX]
// while maintaining DC linearity. There is nothing to be gained
// by doing this in f64, as the significand of a f32 is 24 bits,
// just like the maximum bit depth we are converting to.
let int_value = *sample * (std::$type::MAX as f32 + 0.5) - 0.5;
// Casting floats to ints truncates by default, which results
// in larger quantization error than rounding arithmetically.
// Flooring is faster, but again with larger error.
int_value.round() as $type >> $drop_bits
})
.collect()
};
}
pub fn to_s32(samples: &[f32]) -> Vec<i32> {
convert_samples_to!(i32, samples)
}
pub fn to_s24(samples: &[f32]) -> Vec<i32> {
convert_samples_to!(i32, samples, 8)
}
pub fn to_s24_3(samples: &[f32]) -> Vec<i24> {
to_s32(samples)
.iter()
.map(|sample| i24::pcm_from_i32(*sample))
.collect()
}
pub fn to_s16(samples: &[f32]) -> Vec<i16> {
convert_samples_to!(i16, samples)
}

View file

@ -1,75 +0,0 @@
use super::{AudioDecoder, AudioError, AudioPacket};
use lewton::inside_ogg::OggStreamReader;
use std::error;
use std::fmt;
use std::io::{Read, Seek};
pub struct VorbisDecoder<R: Read + Seek>(OggStreamReader<R>);
pub struct VorbisError(lewton::VorbisError);
impl<R> VorbisDecoder<R>
where
R: Read + Seek,
{
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
Ok(VorbisDecoder(OggStreamReader::new(input)?))
}
}
impl<R> AudioDecoder for VorbisDecoder<R>
where
R: Read + Seek,
{
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
let absgp = ms * 44100 / 1000;
match self.0.seek_absgp_pg(absgp as u64) {
Ok(_) => Ok(()),
Err(err) => Err(AudioError::VorbisError(err.into())),
}
}
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
use lewton::audio::AudioReadError::AudioIsHeader;
use lewton::OggReadError::NoCapturePatternFound;
use lewton::VorbisError::{BadAudio, OggError};
loop {
match self
.0
.read_dec_packet_generic::<lewton::samples::InterleavedSamples<f32>>()
{
Ok(Some(packet)) => return Ok(Some(AudioPacket::Samples(packet.samples))),
Ok(None) => return Ok(None),
Err(BadAudio(AudioIsHeader)) => (),
Err(OggError(NoCapturePatternFound)) => (),
Err(err) => return Err(AudioError::VorbisError(err.into())),
}
}
}
}
impl From<lewton::VorbisError> for VorbisError {
fn from(err: lewton::VorbisError) -> VorbisError {
VorbisError(err)
}
}
impl fmt::Debug for VorbisError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl fmt::Display for VorbisError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl error::Error for VorbisError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
error::Error::source(&self.0)
}
}

View file

@ -3,25 +3,9 @@
#[macro_use]
extern crate log;
pub mod convert;
mod decrypt;
mod fetch;
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(any(feature = "with-tremor", feature = "with-vorbis"))] {
mod libvorbis_decoder;
pub use crate::libvorbis_decoder::{VorbisDecoder, VorbisError};
} else {
mod lewton_decoder;
pub use lewton_decoder::{VorbisDecoder, VorbisError};
}
}
mod passthrough_decoder;
pub use passthrough_decoder::{PassthroughDecoder, PassthroughError};
mod range_set;
pub use decrypt::AudioDecrypt;
@ -30,64 +14,3 @@ pub use fetch::{
READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS,
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
};
use std::fmt;
pub enum AudioPacket {
Samples(Vec<f32>),
OggData(Vec<u8>),
}
impl AudioPacket {
pub fn samples(&self) -> &[f32] {
match self {
AudioPacket::Samples(s) => s,
AudioPacket::OggData(_) => panic!("can't return OggData on samples"),
}
}
pub fn oggdata(&self) -> &[u8] {
match self {
AudioPacket::Samples(_) => panic!("can't return samples on OggData"),
AudioPacket::OggData(d) => d,
}
}
pub fn is_empty(&self) -> bool {
match self {
AudioPacket::Samples(s) => s.is_empty(),
AudioPacket::OggData(d) => d.is_empty(),
}
}
}
#[derive(Debug)]
pub enum AudioError {
PassthroughError(PassthroughError),
VorbisError(VorbisError),
}
impl fmt::Display for AudioError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AudioError::PassthroughError(err) => write!(f, "PassthroughError({})", err),
AudioError::VorbisError(err) => write!(f, "VorbisError({})", err),
}
}
}
impl From<VorbisError> for AudioError {
fn from(err: VorbisError) -> AudioError {
AudioError::VorbisError(err)
}
}
impl From<PassthroughError> for AudioError {
fn from(err: PassthroughError) -> AudioError {
AudioError::PassthroughError(err)
}
}
pub trait AudioDecoder {
fn seek(&mut self, ms: i64) -> Result<(), AudioError>;
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError>;
}

View file

@ -1,89 +0,0 @@
#[cfg(feature = "with-tremor")]
use librespot_tremor as vorbis;
use super::{AudioDecoder, AudioError, AudioPacket};
use std::error;
use std::fmt;
use std::io::{Read, Seek};
pub struct VorbisDecoder<R: Read + Seek>(vorbis::Decoder<R>);
pub struct VorbisError(vorbis::VorbisError);
impl<R> VorbisDecoder<R>
where
R: Read + Seek,
{
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
Ok(VorbisDecoder(vorbis::Decoder::new(input)?))
}
}
impl<R> AudioDecoder for VorbisDecoder<R>
where
R: Read + Seek,
{
#[cfg(not(feature = "with-tremor"))]
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
self.0.time_seek(ms as f64 / 1000f64)?;
Ok(())
}
#[cfg(feature = "with-tremor")]
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
self.0.time_seek(ms)?;
Ok(())
}
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
loop {
match self.0.packets().next() {
Some(Ok(packet)) => {
// Losslessly represent [-32768, 32767] to [-1.0, 1.0] while maintaining DC linearity.
return Ok(Some(AudioPacket::Samples(
packet
.data
.iter()
.map(|sample| {
((*sample as f64 + 0.5) / (std::i16::MAX as f64 + 0.5)) as f32
})
.collect(),
)));
}
None => return Ok(None),
Some(Err(vorbis::VorbisError::Hole)) => (),
Some(Err(err)) => return Err(err.into()),
}
}
}
}
impl From<vorbis::VorbisError> for VorbisError {
fn from(err: vorbis::VorbisError) -> VorbisError {
VorbisError(err)
}
}
impl fmt::Debug for VorbisError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl fmt::Display for VorbisError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl error::Error for VorbisError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
error::Error::source(&self.0)
}
}
impl From<vorbis::VorbisError> for AudioError {
fn from(err: vorbis::VorbisError) -> AudioError {
AudioError::VorbisError(VorbisError(err))
}
}

View file

@ -1,204 +0,0 @@
// Passthrough decoder for librespot
use super::{AudioDecoder, AudioError, AudioPacket};
use ogg::{OggReadError, Packet, PacketReader, PacketWriteEndInfo, PacketWriter};
use std::fmt;
use std::io::{Read, Seek};
use std::time::{SystemTime, UNIX_EPOCH};
fn get_header<T>(code: u8, rdr: &mut PacketReader<T>) -> Result<Box<[u8]>, PassthroughError>
where
T: Read + Seek,
{
let pck: Packet = rdr.read_packet_expected()?;
let pkt_type = pck.data[0];
debug!("Vorbis header type{}", &pkt_type);
if pkt_type != code {
return Err(PassthroughError(OggReadError::InvalidData));
}
Ok(pck.data.into_boxed_slice())
}
pub struct PassthroughDecoder<R: Read + Seek> {
rdr: PacketReader<R>,
wtr: PacketWriter<Vec<u8>>,
eos: bool,
bos: bool,
ofsgp_page: u64,
stream_serial: u32,
ident: Box<[u8]>,
comment: Box<[u8]>,
setup: Box<[u8]>,
}
pub struct PassthroughError(ogg::OggReadError);
impl<R: Read + Seek> PassthroughDecoder<R> {
/// Constructs a new Decoder from a given implementation of `Read + Seek`.
pub fn new(rdr: R) -> Result<Self, PassthroughError> {
let mut rdr = PacketReader::new(rdr);
let stream_serial = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u32;
info!("Starting passthrough track with serial {}", stream_serial);
// search for ident, comment, setup
let ident = get_header(1, &mut rdr)?;
let comment = get_header(3, &mut rdr)?;
let setup = get_header(5, &mut rdr)?;
// remove un-needed packets
rdr.delete_unread_packets();
Ok(PassthroughDecoder {
rdr,
wtr: PacketWriter::new(Vec::new()),
ofsgp_page: 0,
stream_serial,
ident,
comment,
setup,
eos: false,
bos: false,
})
}
}
impl<R: Read + Seek> AudioDecoder for PassthroughDecoder<R> {
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
info!("Seeking to {}", ms);
// add an eos to previous stream if missing
if self.bos && !self.eos {
match self.rdr.read_packet() {
Ok(Some(pck)) => {
let absgp_page = pck.absgp_page() - self.ofsgp_page;
self.wtr
.write_packet(
pck.data.into_boxed_slice(),
self.stream_serial,
PacketWriteEndInfo::EndStream,
absgp_page,
)
.unwrap();
}
_ => warn! {"Cannot write EoS after seeking"},
};
}
self.eos = false;
self.bos = false;
self.ofsgp_page = 0;
self.stream_serial += 1;
// hard-coded to 44.1 kHz
match self.rdr.seek_absgp(None, (ms * 44100 / 1000) as u64) {
Ok(_) => {
// need to set some offset for next_page()
let pck = self.rdr.read_packet().unwrap().unwrap();
self.ofsgp_page = pck.absgp_page();
debug!("Seek to offset page {}", self.ofsgp_page);
Ok(())
}
Err(err) => Err(AudioError::PassthroughError(err.into())),
}
}
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
// write headers if we are (re)starting
if !self.bos {
self.wtr
.write_packet(
self.ident.clone(),
self.stream_serial,
PacketWriteEndInfo::EndPage,
0,
)
.unwrap();
self.wtr
.write_packet(
self.comment.clone(),
self.stream_serial,
PacketWriteEndInfo::NormalPacket,
0,
)
.unwrap();
self.wtr
.write_packet(
self.setup.clone(),
self.stream_serial,
PacketWriteEndInfo::EndPage,
0,
)
.unwrap();
self.bos = true;
debug!("Wrote Ogg headers");
}
loop {
let pck = match self.rdr.read_packet() {
Ok(Some(pck)) => pck,
Ok(None) | Err(OggReadError::NoCapturePatternFound) => {
info!("end of streaming");
return Ok(None);
}
Err(err) => return Err(AudioError::PassthroughError(err.into())),
};
let pckgp_page = pck.absgp_page();
// skip till we have audio and a calculable granule position
if pckgp_page == 0 || pckgp_page == self.ofsgp_page {
continue;
}
// set packet type
let inf = if pck.last_in_stream() {
self.eos = true;
PacketWriteEndInfo::EndStream
} else if pck.last_in_page() {
PacketWriteEndInfo::EndPage
} else {
PacketWriteEndInfo::NormalPacket
};
self.wtr
.write_packet(
pck.data.into_boxed_slice(),
self.stream_serial,
inf,
pckgp_page - self.ofsgp_page,
)
.unwrap();
let data = self.wtr.inner_mut();
if !data.is_empty() {
let result = AudioPacket::OggData(std::mem::take(data));
return Ok(Some(result));
}
}
}
}
impl fmt::Debug for PassthroughError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl From<ogg::OggReadError> for PassthroughError {
fn from(err: OggReadError) -> PassthroughError {
PassthroughError(err)
}
}
impl fmt::Display for PassthroughError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}