Refactor audio output to make it more modular.

This makes the player less hard coded to portaudio, and easier to
experiment with different backends.
This commit is contained in:
Paul Lietar 2016-03-14 00:49:21 +00:00
parent 4b73f83c5e
commit 9274a6bfb3
4 changed files with 69 additions and 30 deletions

46
src/audio_sink.rs Normal file
View file

@ -0,0 +1,46 @@
use portaudio;
use std::io;
pub trait Sink {
fn start(&self) -> io::Result<()>;
fn stop(&self) -> io::Result<()>;
fn write(&self, data: &[i16]) -> io::Result<()>;
}
pub struct PortAudioSink<'a>(portaudio::stream::Stream<'a, i16, i16>);
impl <'a> PortAudioSink<'a> {
pub fn open() -> PortAudioSink<'a> {
portaudio::initialize().unwrap();
let stream = portaudio::stream::Stream::open_default(
0, 2, 44100.0,
portaudio::stream::FRAMES_PER_BUFFER_UNSPECIFIED,
None
).unwrap();
PortAudioSink(stream)
}
}
impl <'a> Sink for PortAudioSink<'a> {
fn start(&self) -> io::Result<()> {
self.0.start().unwrap();
Ok(())
}
fn stop(&self) -> io::Result<()> {
self.0.stop().unwrap();
Ok(())
}
fn write(&self, data: &[i16]) -> io::Result<()> {
self.0.write(&data).unwrap();
Ok(())
}
}
impl <'a> Drop for PortAudioSink<'a> {
fn drop(&mut self) {
portaudio::terminate().unwrap();
}
}

View file

@ -2,6 +2,7 @@
mod audio_decrypt; mod audio_decrypt;
mod audio_file; mod audio_file;
mod audio_key; mod audio_key;
pub mod audio_sink;
pub mod authentication; pub mod authentication;
mod connection; mod connection;
mod diffie_hellman; mod diffie_hellman;

View file

@ -10,6 +10,7 @@ use std::io::{stdout, Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::thread; use std::thread;
use librespot::audio_sink::DefaultSink;
use librespot::authentication::Credentials; use librespot::authentication::Credentials;
use librespot::discovery::DiscoveryManager; use librespot::discovery::DiscoveryManager;
use librespot::player::Player; use librespot::player::Player;
@ -106,10 +107,14 @@ fn main() {
let reusable_credentials = session.login(credentials).unwrap(); let reusable_credentials = session.login(credentials).unwrap();
reusable_credentials.save_to_file(credentials_path); reusable_credentials.save_to_file(credentials_path);
let player = Player::new(session.clone()); portaudio::initialize().unwrap();
let player = Player::new(session.clone(), || DefaultSink::open());
let spirc = SpircManager::new(session.clone(), player); let spirc = SpircManager::new(session.clone(), player);
thread::spawn(move || spirc.run()); thread::spawn(move || spirc.run());
portaudio::terminate().unwrap();
loop { loop {
session.poll(); session.poll();
} }

View file

@ -1,14 +1,14 @@
use eventual::{self, Async}; use eventual::{self, Async};
use portaudio;
use std::borrow::Cow; use std::borrow::Cow;
use std::sync::{mpsc, Mutex, Arc, MutexGuard}; use std::sync::{mpsc, Mutex, Arc, MutexGuard};
use std::thread; use std::thread;
use std::io::{Read, Seek}; use std::io::{Read, Seek};
use vorbis; use vorbis;
use audio_decrypt::AudioDecrypt;
use audio_sink::Sink;
use metadata::{FileFormat, Track, TrackRef}; use metadata::{FileFormat, Track, TrackRef};
use session::{Bitrate, Session}; use session::{Bitrate, Session};
use audio_decrypt::AudioDecrypt;
use util::{self, SpotifyId, Subfile}; use util::{self, SpotifyId, Subfile};
use spirc::PlayStatus; use spirc::PlayStatus;
@ -71,7 +71,8 @@ enum PlayerCommand {
} }
impl Player { impl Player {
pub fn new(session: Session) -> Player { pub fn new<S, F>(session: Session, sink_builder: F) -> Player
where S: Sink, F: FnOnce() -> S + Send + 'static {
let (cmd_tx, cmd_rx) = mpsc::channel(); let (cmd_tx, cmd_rx) = mpsc::channel();
let state = Arc::new(Mutex::new(PlayerState { let state = Arc::new(Mutex::new(PlayerState {
@ -92,7 +93,7 @@ impl Player {
observers: observers.clone(), observers: observers.clone(),
}; };
thread::spawn(move || internal.run()); thread::spawn(move || internal.run(sink_builder()));
Player { Player {
commands: cmd_tx, commands: cmd_tx,
@ -154,15 +155,7 @@ fn apply_volume(volume: u16, data: &[i16]) -> Cow<[i16]> {
} }
impl PlayerInternal { impl PlayerInternal {
fn run(self) { fn run<S: Sink>(self, sink: S) {
portaudio::initialize().unwrap();
let stream = portaudio::stream::Stream::<i16, i16>::open_default(
0, 2, 44100.0,
portaudio::stream::FRAMES_PER_BUFFER_UNSPECIFIED,
None
).unwrap();
let mut decoder = None; let mut decoder = None;
loop { loop {
@ -177,7 +170,7 @@ impl PlayerInternal {
Some(PlayerCommand::Load(track_id, play, position)) => { Some(PlayerCommand::Load(track_id, play, position)) => {
self.update(|state| { self.update(|state| {
if state.status == PlayStatus::kPlayStatusPlay { if state.status == PlayStatus::kPlayStatusPlay {
stream.stop().unwrap(); sink.stop().unwrap();
} }
state.end_of_track = false; state.end_of_track = false;
state.status = PlayStatus::kPlayStatusPause; state.status = PlayStatus::kPlayStatusPause;
@ -221,7 +214,7 @@ impl PlayerInternal {
self.update(|state| { self.update(|state| {
state.status = if play { state.status = if play {
stream.start().unwrap(); sink.start().unwrap();
PlayStatus::kPlayStatusPlay PlayStatus::kPlayStatusPlay
} else { } else {
PlayStatus::kPlayStatusPause PlayStatus::kPlayStatusPause
@ -250,7 +243,7 @@ impl PlayerInternal {
true true
}); });
stream.start().unwrap(); sink.start().unwrap();
} }
Some(PlayerCommand::Pause) => { Some(PlayerCommand::Pause) => {
self.update(|state| { self.update(|state| {
@ -261,7 +254,7 @@ impl PlayerInternal {
true true
}); });
stream.stop().unwrap(); sink.stop().unwrap();
} }
Some(PlayerCommand::Volume(vol)) => { Some(PlayerCommand::Volume(vol)) => {
self.update(|state| { self.update(|state| {
@ -279,22 +272,20 @@ impl PlayerInternal {
true true
}); });
stream.stop().unwrap(); sink.stop().unwrap();
decoder = None; decoder = None;
} }
None => (), None => (),
} }
if self.state.lock().unwrap().status == PlayStatus::kPlayStatusPlay { if self.state.lock().unwrap().status == PlayStatus::kPlayStatusPlay {
match decoder.as_mut().unwrap().packets().next() { let packet = decoder.as_mut().unwrap().packets().next();
match packet {
Some(Ok(packet)) => { Some(Ok(packet)) => {
let buffer = apply_volume(self.state.lock().unwrap().volume, let buffer = apply_volume(self.state.lock().unwrap().volume,
&packet.data); &packet.data);
match stream.write(&buffer) { sink.write(&buffer).unwrap();
Ok(_) => (),
Err(portaudio::PaError::OutputUnderflowed) => eprintln!("Underflow"),
Err(e) => panic!("PA Error {}", e),
};
} }
Some(Err(vorbis::VorbisError::Hole)) => (), Some(Err(vorbis::VorbisError::Hole)) => (),
Some(Err(e)) => panic!("Vorbis error {:?}", e), Some(Err(e)) => panic!("Vorbis error {:?}", e),
@ -305,16 +296,12 @@ impl PlayerInternal {
true true
}); });
stream.stop().unwrap(); sink.stop().unwrap();
decoder = None; decoder = None;
} }
} }
} }
} }
drop(stream);
portaudio::terminate().unwrap();
} }
fn update<F>(&self, f: F) fn update<F>(&self, f: F)