Forráskód Böngészése

Allow specifying an output device.

Paul Lietar 8 éve
szülő
commit
1396f9729a

+ 7 - 5
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<S: Sink + Open + 'static>() -> Box<Sink> {
-    Box::new(S::open())
+fn mk_sink<S: Sink + Open + 'static>(device: Option<&str>) -> Box<Sink> {
+    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<Sink> + Sync + Send + 'static))] = &[
+    pub const BACKENDS : &'static [
+        (&'static str,
+         &'static (Fn(Option<&str>) -> Box<Sink> + Sync + Send + 'static))
+    ] = &[
         #[cfg(feature = "portaudio-backend")]
         ("portaudio", &mk_sink::<PortAudioSink>),
         #[cfg(feature = "pulseaudio-backend")]
         ("pulseaudio", &mk_sink::<PulseAudioSink>),
-
     ];
 }

+ 63 - 6
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<Iterator<Item=(DeviceIndex, DeviceInfo)>> {
+    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<DeviceIndex> {
+    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),
         };
 

+ 6 - 2
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,

+ 9 - 3
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<Sink> + Send + Sync) {
+pub fn find_backend(name: Option<&str>) -> &'static (Fn(Option<&str>) -> Box<Sink> + 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))
+    })
 }