diff --git a/src/audio_backend/mod.rs b/src/audio_backend/mod.rs index e58d01b..2d49932 100644 --- a/src/audio_backend/mod.rs +++ b/src/audio_backend/mod.rs @@ -1,7 +1,7 @@ use std::io; pub trait Open { - fn open() -> Self; + fn open(Option<&str>) -> Self; } pub trait Sink { @@ -50,8 +50,8 @@ macro_rules! _declare_backends { } #[allow(dead_code)] -fn mk_sink() -> Box { - Box::new(S::open()) +fn mk_sink(device: Option<&str>) -> Box { + Box::new(S::open(device)) } #[cfg(feature = "portaudio-backend")] @@ -66,11 +66,13 @@ use self::pulseaudio::PulseAudioSink; declare_backends! { - pub const BACKENDS : &'static [(&'static str, &'static (Fn() -> Box + Sync + Send + 'static))] = &[ + pub const BACKENDS : &'static [ + (&'static str, + &'static (Fn(Option<&str>) -> Box + Sync + Send + 'static)) + ] = &[ #[cfg(feature = "portaudio-backend")] ("portaudio", &mk_sink::), #[cfg(feature = "pulseaudio-backend")] ("pulseaudio", &mk_sink::), - ]; } diff --git a/src/audio_backend/portaudio.rs b/src/audio_backend/portaudio.rs index 1c96bbf..708385b 100644 --- a/src/audio_backend/portaudio.rs +++ b/src/audio_backend/portaudio.rs @@ -1,17 +1,73 @@ use super::{Open, Sink}; use std::io; +use std::process::exit; use portaudio; +use portaudio::device::{DeviceIndex, DeviceInfo, get_default_output_index}; pub struct PortAudioSink<'a>(portaudio::stream::Stream<'a, i16, i16>); +fn output_devices() -> Box> { + let count = portaudio::device::get_count().unwrap(); + let devices = (0..count) + .filter_map(|idx| { + portaudio::device::get_info(idx).map(|info| (idx, info)) + }).filter(|&(_, ref info)| { + info.max_output_channels > 0 + }); + + Box::new(devices) +} + +fn list_outputs() { + let default = get_default_output_index(); + + for (idx, info) in output_devices() { + if Some(idx) == default { + println!("- {} (default)", info.name); + } else { + println!("- {}", info.name) + } + } +} + +fn find_output(device: &str) -> Option { + output_devices() + .find(|&(_, ref info)| info.name == device) + .map(|(idx, _)| idx) +} + impl <'a> Open for PortAudioSink<'a> { - fn open() -> PortAudioSink<'a> { + fn open(device: Option<&str>) -> PortAudioSink<'a> { + use portaudio::stream::*; + + debug!("Using PortAudio sink"); + portaudio::initialize().unwrap(); - let stream = portaudio::stream::Stream::open_default( - 0, 2, 44100.0, - portaudio::stream::FRAMES_PER_BUFFER_UNSPECIFIED, - None + let device_idx = match device { + Some("?") => { + list_outputs(); + exit(0) + } + Some(device) => find_output(device), + None => get_default_output_index(), + }.expect("Could not find device"); + + let params = StreamParameters { + device: device_idx, + channel_count: 2, + // Super hacky workaround the fact that Duration is private + // in portaudio + suggested_latency: unsafe { ::std::mem::transmute(0i64) }, + data: 0i16, + }; + + let stream = Stream::open( + None, Some(params), + 44100.0, + FRAMES_PER_BUFFER_UNSPECIFIED, + StreamFlags::empty(), + None ).unwrap(); PortAudioSink(stream) @@ -30,7 +86,8 @@ impl <'a> Sink for PortAudioSink<'a> { fn write(&self, data: &[i16]) -> io::Result<()> { match self.0.write(&data) { Ok(_) => (), - Err(portaudio::PaError::OutputUnderflowed) => error!("PortAudio write underflow"), + Err(portaudio::PaError::OutputUnderflowed) => + error!("PortAudio write underflow"), Err(e) => panic!("PA Error {}", e), }; diff --git a/src/audio_backend/pulseaudio.rs b/src/audio_backend/pulseaudio.rs index ba7a2e4..93eefc3 100644 --- a/src/audio_backend/pulseaudio.rs +++ b/src/audio_backend/pulseaudio.rs @@ -8,8 +8,12 @@ use std::ffi::CString; pub struct PulseAudioSink(*mut pa_simple); impl Open for PulseAudioSink { - fn open() -> PulseAudioSink { - info!("Using PulseAudioSink"); + fn open(device: Option<&str>) -> PulseAudioSink { + debug!("Using PulseAudio sink"); + + if device.is_some() { + panic!("pulseaudio sink does not support specifying a device name"); + } let ss = pa_sample_spec { format: PA_SAMPLE_S16LE, diff --git a/src/main_helper.rs b/src/main_helper.rs index e12078e..9911989 100644 --- a/src/main_helper.rs +++ b/src/main_helper.rs @@ -11,7 +11,7 @@ use player::Player; use session::{Bitrate, Config, Session}; use version; -pub fn find_backend(name: Option<&str>) -> &'static (Fn() -> Box + Send + Sync) { +pub fn find_backend(name: Option<&str>) -> &'static (Fn(Option<&str>) -> Box + Send + Sync) { match name { Some("?") => { println!("Available Backends : "); @@ -51,6 +51,7 @@ pub fn add_authentication_arguments(opts: &mut getopts::Options) { pub fn add_player_arguments(opts: &mut getopts::Options) { opts.optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND"); + opts.optopt("", "device", "Audio device to use. Use '?' to list options", "DEVICE"); } pub fn create_session(matches: &getopts::Matches) -> Session { @@ -119,7 +120,12 @@ pub fn get_credentials(session: &Session, matches: &getopts::Matches) -> Credent } pub fn create_player(session: &Session, matches: &getopts::Matches) -> Player { - let make_backend = find_backend(matches.opt_str("backend").as_ref().map(AsRef::as_ref)); + let backend_name = matches.opt_str("backend"); + let device_name = matches.opt_str("device"); - Player::new(session.clone(), move || make_backend()) + let make_backend = find_backend(backend_name.as_ref().map(AsRef::as_ref)); + + Player::new(session.clone(), move || { + make_backend(device_name.as_ref().map(AsRef::as_ref)) + }) }