mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge remote-tracking branch 'librespot-org/dev' into new-api-wip
This commit is contained in:
commit
82b2653c5f
4 changed files with 602 additions and 439 deletions
|
@ -9,8 +9,10 @@
|
||||||
#
|
#
|
||||||
# If only one architecture is desired, cargo can be invoked directly with the appropriate options :
|
# If only one architecture is desired, cargo can be invoked directly with the appropriate options :
|
||||||
# $ docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --no-default-features --features "alsa-backend"
|
# $ docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --no-default-features --features "alsa-backend"
|
||||||
# $ docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --target arm-unknown-linux-gnueabihf --no-default-features --features "alsa-backend"
|
# $ docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --target arm-unknown-linux-gnueabihf --no-default-features --features alsa-backend
|
||||||
# $ docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --target arm-unknown-linux-gnueabi --no-default-features --features "alsa-backend"
|
# $ docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --target arm-unknown-linux-gnueabi --no-default-features --features alsa-backend
|
||||||
|
# $ docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --target aarch64-unknown-linux-gnu --no-default-features --features alsa-backend
|
||||||
|
|
||||||
# $ docker run -v /tmp/librespot-build:/build librespot-cross contrib/docker-build-pi-armv6hf.sh
|
# $ docker run -v /tmp/librespot-build:/build librespot-cross contrib/docker-build-pi-armv6hf.sh
|
||||||
|
|
||||||
FROM debian:stretch
|
FROM debian:stretch
|
||||||
|
|
|
@ -4,16 +4,18 @@ use crate::convert::Converter;
|
||||||
use crate::decoder::AudioPacket;
|
use crate::decoder::AudioPacket;
|
||||||
use crate::{NUM_CHANNELS, SAMPLE_RATE};
|
use crate::{NUM_CHANNELS, SAMPLE_RATE};
|
||||||
use alsa::device_name::HintIter;
|
use alsa::device_name::HintIter;
|
||||||
use alsa::pcm::{Access, Format, HwParams, PCM};
|
use alsa::pcm::{Access, Format, Frames, HwParams, PCM};
|
||||||
use alsa::{Direction, ValueOr};
|
use alsa::{Direction, ValueOr};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::time::Duration;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
// 0.5 sec buffer.
|
const MAX_BUFFER: Frames = (SAMPLE_RATE / 2) as Frames;
|
||||||
const PERIOD_TIME: Duration = Duration::from_millis(100);
|
const MIN_BUFFER: Frames = (SAMPLE_RATE / 10) as Frames;
|
||||||
const BUFFER_TIME: Duration = Duration::from_millis(500);
|
const ZERO_FRAMES: Frames = 0;
|
||||||
|
|
||||||
|
const MAX_PERIOD_DIVISOR: Frames = 4;
|
||||||
|
const MIN_PERIOD_DIVISOR: Frames = 10;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
enum AlsaError {
|
enum AlsaError {
|
||||||
|
@ -195,28 +197,187 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
|
||||||
e,
|
e,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
hwp.set_buffer_time_near(BUFFER_TIME.as_micros() as u32, ValueOr::Nearest)
|
// Clone the hwp while it's in
|
||||||
.map_err(AlsaError::HwParams)?;
|
// a good working state so that
|
||||||
|
// in the event of an error setting
|
||||||
|
// the buffer and period sizes
|
||||||
|
// we can use the good working clone
|
||||||
|
// instead of the hwp that's in an
|
||||||
|
// error state.
|
||||||
|
let hwp_clone = hwp.clone();
|
||||||
|
|
||||||
hwp.set_period_time_near(PERIOD_TIME.as_micros() as u32, ValueOr::Nearest)
|
// At a sampling rate of 44100:
|
||||||
.map_err(AlsaError::HwParams)?;
|
// The largest buffer is 22050 Frames (500ms) with 5512 Frame periods (125ms).
|
||||||
|
// The smallest buffer is 4410 Frames (100ms) with 441 Frame periods (10ms).
|
||||||
|
// Actual values may vary.
|
||||||
|
//
|
||||||
|
// Larger buffer and period sizes are preferred as extremely small values
|
||||||
|
// will cause high CPU useage.
|
||||||
|
//
|
||||||
|
// If no buffer or period size is in those ranges or an error happens
|
||||||
|
// trying to set the buffer or period size use the device's defaults
|
||||||
|
// which may not be ideal but are *hopefully* serviceable.
|
||||||
|
|
||||||
|
let buffer_size = {
|
||||||
|
let max = match hwp.get_buffer_size_max() {
|
||||||
|
Err(e) => {
|
||||||
|
trace!("Error getting the device's max Buffer size: {}", e);
|
||||||
|
ZERO_FRAMES
|
||||||
|
}
|
||||||
|
Ok(s) => s,
|
||||||
|
};
|
||||||
|
|
||||||
|
let min = match hwp.get_buffer_size_min() {
|
||||||
|
Err(e) => {
|
||||||
|
trace!("Error getting the device's min Buffer size: {}", e);
|
||||||
|
ZERO_FRAMES
|
||||||
|
}
|
||||||
|
Ok(s) => s,
|
||||||
|
};
|
||||||
|
|
||||||
|
let buffer_size = if min < max {
|
||||||
|
match (MIN_BUFFER..=MAX_BUFFER)
|
||||||
|
.rev()
|
||||||
|
.find(|f| (min..=max).contains(f))
|
||||||
|
{
|
||||||
|
Some(size) => {
|
||||||
|
trace!("Desired Frames per Buffer: {:?}", size);
|
||||||
|
|
||||||
|
match hwp.set_buffer_size_near(size) {
|
||||||
|
Err(e) => {
|
||||||
|
trace!("Error setting the device's Buffer size: {}", e);
|
||||||
|
ZERO_FRAMES
|
||||||
|
}
|
||||||
|
Ok(s) => s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
trace!("No Desired Buffer size in range reported by the device.");
|
||||||
|
ZERO_FRAMES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
trace!("The device's min reported Buffer size was greater than or equal to it's max reported Buffer size.");
|
||||||
|
ZERO_FRAMES
|
||||||
|
};
|
||||||
|
|
||||||
|
if buffer_size == ZERO_FRAMES {
|
||||||
|
trace!(
|
||||||
|
"Desired Buffer Frame range: {:?} - {:?}",
|
||||||
|
MIN_BUFFER,
|
||||||
|
MAX_BUFFER
|
||||||
|
);
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Actual Buffer Frame range as reported by the device: {:?} - {:?}",
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_size
|
||||||
|
};
|
||||||
|
|
||||||
|
let period_size = {
|
||||||
|
if buffer_size == ZERO_FRAMES {
|
||||||
|
ZERO_FRAMES
|
||||||
|
} else {
|
||||||
|
let max = match hwp.get_period_size_max() {
|
||||||
|
Err(e) => {
|
||||||
|
trace!("Error getting the device's max Period size: {}", e);
|
||||||
|
ZERO_FRAMES
|
||||||
|
}
|
||||||
|
Ok(s) => s,
|
||||||
|
};
|
||||||
|
|
||||||
|
let min = match hwp.get_period_size_min() {
|
||||||
|
Err(e) => {
|
||||||
|
trace!("Error getting the device's min Period size: {}", e);
|
||||||
|
ZERO_FRAMES
|
||||||
|
}
|
||||||
|
Ok(s) => s,
|
||||||
|
};
|
||||||
|
|
||||||
|
let max_period = buffer_size / MAX_PERIOD_DIVISOR;
|
||||||
|
let min_period = buffer_size / MIN_PERIOD_DIVISOR;
|
||||||
|
|
||||||
|
let period_size = if min < max && min_period < max_period {
|
||||||
|
match (min_period..=max_period)
|
||||||
|
.rev()
|
||||||
|
.find(|f| (min..=max).contains(f))
|
||||||
|
{
|
||||||
|
Some(size) => {
|
||||||
|
trace!("Desired Frames per Period: {:?}", size);
|
||||||
|
|
||||||
|
match hwp.set_period_size_near(size, ValueOr::Nearest) {
|
||||||
|
Err(e) => {
|
||||||
|
trace!("Error setting the device's Period size: {}", e);
|
||||||
|
ZERO_FRAMES
|
||||||
|
}
|
||||||
|
Ok(s) => s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
trace!("No Desired Period size in range reported by the device.");
|
||||||
|
ZERO_FRAMES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
trace!("The device's min reported Period size was greater than or equal to it's max reported Period size,");
|
||||||
|
trace!("or the desired min Period size was greater than or equal to the desired max Period size.");
|
||||||
|
ZERO_FRAMES
|
||||||
|
};
|
||||||
|
|
||||||
|
if period_size == ZERO_FRAMES {
|
||||||
|
trace!("Buffer size: {:?}", buffer_size);
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Desired Period Frame range: {:?} (Buffer size / {:?}) - {:?} (Buffer size / {:?})",
|
||||||
|
min_period,
|
||||||
|
MIN_PERIOD_DIVISOR,
|
||||||
|
max_period,
|
||||||
|
MAX_PERIOD_DIVISOR,
|
||||||
|
);
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Actual Period Frame range as reported by the device: {:?} - {:?}",
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
period_size
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if buffer_size == ZERO_FRAMES || period_size == ZERO_FRAMES {
|
||||||
|
trace!(
|
||||||
|
"Failed to set Buffer and/or Period size, falling back to the device's defaults."
|
||||||
|
);
|
||||||
|
|
||||||
|
trace!("You may experience higher than normal CPU usage and/or audio issues.");
|
||||||
|
|
||||||
|
pcm.hw_params(&hwp_clone).map_err(AlsaError::Pcm)?;
|
||||||
|
} else {
|
||||||
pcm.hw_params(&hwp).map_err(AlsaError::Pcm)?;
|
pcm.hw_params(&hwp).map_err(AlsaError::Pcm)?;
|
||||||
|
}
|
||||||
|
|
||||||
let swp = pcm.sw_params_current().map_err(AlsaError::Pcm)?;
|
let hwp = pcm.hw_params_current().map_err(AlsaError::Pcm)?;
|
||||||
|
|
||||||
// Don't assume we got what we wanted. Ask to make sure.
|
// Don't assume we got what we wanted. Ask to make sure.
|
||||||
let frames_per_period = hwp.get_period_size().map_err(AlsaError::HwParams)?;
|
let frames_per_period = hwp.get_period_size().map_err(AlsaError::HwParams)?;
|
||||||
|
|
||||||
let frames_per_buffer = hwp.get_buffer_size().map_err(AlsaError::HwParams)?;
|
let frames_per_buffer = hwp.get_buffer_size().map_err(AlsaError::HwParams)?;
|
||||||
|
|
||||||
|
let swp = pcm.sw_params_current().map_err(AlsaError::Pcm)?;
|
||||||
|
|
||||||
swp.set_start_threshold(frames_per_buffer - frames_per_period)
|
swp.set_start_threshold(frames_per_buffer - frames_per_period)
|
||||||
.map_err(AlsaError::SwParams)?;
|
.map_err(AlsaError::SwParams)?;
|
||||||
|
|
||||||
pcm.sw_params(&swp).map_err(AlsaError::Pcm)?;
|
pcm.sw_params(&swp).map_err(AlsaError::Pcm)?;
|
||||||
|
|
||||||
trace!("Frames per Buffer: {:?}", frames_per_buffer);
|
trace!("Actual Frames per Buffer: {:?}", frames_per_buffer);
|
||||||
trace!("Frames per Period: {:?}", frames_per_period);
|
trace!("Actual Frames per Period: {:?}", frames_per_period);
|
||||||
|
|
||||||
// Let ALSA do the math for us.
|
// Let ALSA do the math for us.
|
||||||
pcm.frames_to_bytes(frames_per_period) as usize
|
pcm.frames_to_bytes(frames_per_period) as usize
|
||||||
|
|
|
@ -739,7 +739,14 @@ impl PlayerTrackLoader {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(audio.duration >= 0);
|
if audio.duration < 0 {
|
||||||
|
error!(
|
||||||
|
"Track duration for <{}> cannot be {}",
|
||||||
|
spotify_id.to_uri(),
|
||||||
|
audio.duration
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let duration_ms = audio.duration as u32;
|
let duration_ms = audio.duration as u32;
|
||||||
|
|
||||||
// (Most) podcasts seem to support only 96 kbps Vorbis, so fall back to it
|
// (Most) podcasts seem to support only 96 kbps Vorbis, so fall back to it
|
||||||
|
@ -946,9 +953,12 @@ impl Future for PlayerInternal {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Ready(Err(_)) => {
|
Poll::Ready(Err(e)) => {
|
||||||
warn!("Unable to load <{:?}>\nSkipping to next track", track_id);
|
warn!(
|
||||||
assert!(self.state.is_loading());
|
"Skipping to next track, unable to load track <{:?}>: {:?}",
|
||||||
|
track_id, e
|
||||||
|
);
|
||||||
|
debug_assert!(self.state.is_loading());
|
||||||
self.send_event(PlayerEvent::EndOfTrack {
|
self.send_event(PlayerEvent::EndOfTrack {
|
||||||
track_id,
|
track_id,
|
||||||
play_request_id,
|
play_request_id,
|
||||||
|
@ -1048,8 +1058,11 @@ impl Future for PlayerInternal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("PlayerInternal poll: {}", e);
|
warn!("Skipping to next track, unable to decode samples for track <{:?}>: {:?}", track_id, e);
|
||||||
exit(1);
|
self.send_event(PlayerEvent::EndOfTrack {
|
||||||
|
track_id,
|
||||||
|
play_request_id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1061,8 +1074,11 @@ impl Future for PlayerInternal {
|
||||||
self.handle_packet(packet, normalisation_factor);
|
self.handle_packet(packet, normalisation_factor);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("PlayerInternal poll: {}", e);
|
warn!("Skipping to next track, unable to get next packet for track <{:?}>: {:?}", track_id, e);
|
||||||
exit(1);
|
self.send_event(PlayerEvent::EndOfTrack {
|
||||||
|
track_id,
|
||||||
|
play_request_id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
770
src/main.rs
770
src/main.rs
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue