mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Allow specifying an output device.
This commit is contained in:
parent
4cca541339
commit
1396f9729a
4 changed files with 85 additions and 16 deletions
|
@ -1,7 +1,7 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
pub trait Open {
|
pub trait Open {
|
||||||
fn open() -> Self;
|
fn open(Option<&str>) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Sink {
|
pub trait Sink {
|
||||||
|
@ -50,8 +50,8 @@ macro_rules! _declare_backends {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn mk_sink<S: Sink + Open + 'static>() -> Box<Sink> {
|
fn mk_sink<S: Sink + Open + 'static>(device: Option<&str>) -> Box<Sink> {
|
||||||
Box::new(S::open())
|
Box::new(S::open(device))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "portaudio-backend")]
|
#[cfg(feature = "portaudio-backend")]
|
||||||
|
@ -66,11 +66,13 @@ use self::pulseaudio::PulseAudioSink;
|
||||||
|
|
||||||
|
|
||||||
declare_backends! {
|
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")]
|
#[cfg(feature = "portaudio-backend")]
|
||||||
("portaudio", &mk_sink::<PortAudioSink>),
|
("portaudio", &mk_sink::<PortAudioSink>),
|
||||||
#[cfg(feature = "pulseaudio-backend")]
|
#[cfg(feature = "pulseaudio-backend")]
|
||||||
("pulseaudio", &mk_sink::<PulseAudioSink>),
|
("pulseaudio", &mk_sink::<PulseAudioSink>),
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,73 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::process::exit;
|
||||||
use portaudio;
|
use portaudio;
|
||||||
|
use portaudio::device::{DeviceIndex, DeviceInfo, get_default_output_index};
|
||||||
|
|
||||||
pub struct PortAudioSink<'a>(portaudio::stream::Stream<'a, i16, i16>);
|
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> {
|
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();
|
portaudio::initialize().unwrap();
|
||||||
|
|
||||||
let stream = portaudio::stream::Stream::open_default(
|
let device_idx = match device {
|
||||||
0, 2, 44100.0,
|
Some("?") => {
|
||||||
portaudio::stream::FRAMES_PER_BUFFER_UNSPECIFIED,
|
list_outputs();
|
||||||
None
|
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();
|
).unwrap();
|
||||||
|
|
||||||
PortAudioSink(stream)
|
PortAudioSink(stream)
|
||||||
|
@ -30,7 +86,8 @@ impl <'a> Sink for PortAudioSink<'a> {
|
||||||
fn write(&self, data: &[i16]) -> io::Result<()> {
|
fn write(&self, data: &[i16]) -> io::Result<()> {
|
||||||
match self.0.write(&data) {
|
match self.0.write(&data) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(portaudio::PaError::OutputUnderflowed) => error!("PortAudio write underflow"),
|
Err(portaudio::PaError::OutputUnderflowed) =>
|
||||||
|
error!("PortAudio write underflow"),
|
||||||
Err(e) => panic!("PA Error {}", e),
|
Err(e) => panic!("PA Error {}", e),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,12 @@ use std::ffi::CString;
|
||||||
pub struct PulseAudioSink(*mut pa_simple);
|
pub struct PulseAudioSink(*mut pa_simple);
|
||||||
|
|
||||||
impl Open for PulseAudioSink {
|
impl Open for PulseAudioSink {
|
||||||
fn open() -> PulseAudioSink {
|
fn open(device: Option<&str>) -> PulseAudioSink {
|
||||||
info!("Using PulseAudioSink");
|
debug!("Using PulseAudio sink");
|
||||||
|
|
||||||
|
if device.is_some() {
|
||||||
|
panic!("pulseaudio sink does not support specifying a device name");
|
||||||
|
}
|
||||||
|
|
||||||
let ss = pa_sample_spec {
|
let ss = pa_sample_spec {
|
||||||
format: PA_SAMPLE_S16LE,
|
format: PA_SAMPLE_S16LE,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use player::Player;
|
||||||
use session::{Bitrate, Config, Session};
|
use session::{Bitrate, Config, Session};
|
||||||
use version;
|
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 {
|
match name {
|
||||||
Some("?") => {
|
Some("?") => {
|
||||||
println!("Available Backends : ");
|
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) {
|
pub fn add_player_arguments(opts: &mut getopts::Options) {
|
||||||
opts.optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND");
|
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 {
|
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 {
|
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))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue