Add RodioDevice wrapper for cpal::Device

* Remove static/global facilities.
This commit is contained in:
Alex Butler 2020-01-31 00:06:12 +00:00
parent 00c627241c
commit 0517ee7216
No known key found for this signature in database
GPG key ID: E1355A2F8E415521
14 changed files with 108 additions and 141 deletions

View file

@ -10,9 +10,8 @@ documentation = "http://docs.rs/rodio"
[dependencies]
claxon = { version = "0.4.2", optional = true }
cpal = { git = "https://github.com/RustAudio/cpal/" }
cpal = { git = "https://github.com/RustAudio/cpal" }
hound = { version = "3.3.1", optional = true }
lazy_static = "1.0.0"
lewton = { version = "0.10", optional = true }
minimp3 = { version = "0.3.2", optional = true }

View file

@ -5,24 +5,22 @@ use std::thread;
use std::time::Duration;
fn main() {
let device = rodio::default_output_device().unwrap();
let device = rodio::RodioDevice::default_output().unwrap();
let file = std::fs::File::open("examples/beep.wav").unwrap();
let beep1 = rodio::play_once(&device, BufReader::new(file)).unwrap();
let beep1 = device.play_once(BufReader::new(file)).unwrap();
beep1.set_volume(0.2);
println!("Started beep1");
thread::sleep(Duration::from_millis(1500));
let file = std::fs::File::open("examples/beep2.wav").unwrap();
rodio::play_once(&device, BufReader::new(file))
.unwrap()
.detach();
device.play_once(BufReader::new(file)).unwrap().detach();
println!("Started beep2");
thread::sleep(Duration::from_millis(1500));
let file = std::fs::File::open("examples/beep3.ogg").unwrap();
let beep3 = rodio::play_once(&device, file).unwrap();
let beep3 = device.play_once(file).unwrap();
println!("Started beep3");
thread::sleep(Duration::from_millis(1500));

View file

@ -3,7 +3,7 @@ extern crate rodio;
use std::io::BufReader;
fn main() {
let device = rodio::default_output_device().unwrap();
let device = rodio::RodioDevice::default_output().unwrap();
let sink = rodio::Sink::new(&device);
let file = std::fs::File::open("examples/music.flac").unwrap();

View file

@ -3,7 +3,7 @@ extern crate rodio;
use std::io::BufReader;
fn main() {
let device = rodio::default_output_device().unwrap();
let device = rodio::RodioDevice::default_output().unwrap();
let sink = rodio::Sink::new(&device);
let file = std::fs::File::open("examples/music.mp3").unwrap();

View file

@ -3,7 +3,7 @@ extern crate rodio;
use std::io::BufReader;
fn main() {
let device = rodio::default_output_device().unwrap();
let device = rodio::RodioDevice::default_output().unwrap();
let sink = rodio::Sink::new(&device);
let file = std::fs::File::open("examples/music.ogg").unwrap();

View file

@ -3,7 +3,7 @@ extern crate rodio;
use std::io::BufReader;
fn main() {
let device = rodio::default_output_device().unwrap();
let device = rodio::RodioDevice::default_output().unwrap();
let sink = rodio::Sink::new(&device);
let file = std::fs::File::open("examples/music.wav").unwrap();

View file

@ -5,7 +5,7 @@ use std::io::BufReader;
use std::time::Duration;
fn main() {
let device = rodio::default_output_device().unwrap();
let device = rodio::RodioDevice::default_output().unwrap();
let sink = rodio::Sink::new(&device);
let file = std::fs::File::open("examples/music.ogg").unwrap();

View file

@ -5,7 +5,7 @@ use std::thread;
use std::time::Duration;
fn main() {
let device = rodio::default_output_device().unwrap();
let device = rodio::RodioDevice::default_output().unwrap();
let sink = rodio::SpatialSink::new(
&device,
[-10.0, 0.0, 0.0],

View file

@ -1,9 +1,58 @@
use cpal::{traits::DeviceTrait, Sample};
use dynamic_mixer::{self, DynamicMixer, DynamicMixerController};
use cpal::{
traits::{DeviceTrait, HostTrait},
Sample,
};
use decoder;
use device_mixer::DeviceMixer;
use dynamic_mixer::{self, DynamicMixerController};
use sink::Sink;
use source::Source;
use std::cell::RefCell;
use std::io::{Read, Seek};
use std::sync::Arc;
pub struct RodioDevice {
mixer: RefCell<DeviceMixer>,
inner: cpal::Device,
}
impl From<cpal::Device> for RodioDevice {
fn from(device: cpal::Device) -> Self {
Self {
inner: device,
mixer: <_>::default(),
}
}
}
impl RodioDevice {
pub fn default_output() -> Option<Self> {
Some(cpal::default_host().default_output_device()?.into())
}
/// Plays a source with a device until it ends.
pub fn play_raw<S>(&self, source: S)
where
S: Source<Item = f32> + Send + 'static,
{
self.mixer.borrow_mut().play(&self.inner, source)
}
/// Plays a sound once. Returns a `Sink` that can be used to control the sound.
#[inline]
pub fn play_once<R>(&self, input: R) -> Result<Sink, decoder::DecoderError>
where
R: Read + Seek + Send + 'static,
{
let input = decoder::Decoder::new(input)?;
let sink = Sink::new(&self);
sink.append(input);
Ok(sink)
}
}
/// Extensions to `cpal::Device`
pub(crate) trait RodioDevice {
pub(crate) trait CpalDeviceExt {
fn new_output_stream_with_format(
&self,
format: cpal::Format,
@ -12,7 +61,7 @@ pub(crate) trait RodioDevice {
fn new_output_stream(&self) -> (Arc<DynamicMixerController<f32>>, cpal::Stream);
}
impl RodioDevice for cpal::Device {
impl CpalDeviceExt for cpal::Device {
fn new_output_stream_with_format(
&self,
format: cpal::Format,
@ -20,11 +69,38 @@ impl RodioDevice for cpal::Device {
let (mixer_tx, mut mixer_rx) =
dynamic_mixer::mixer::<f32>(format.channels, format.sample_rate.0);
self.build_output_stream(
&format,
move |data| audio_callback(&mut mixer_rx, data),
move |err| eprintln!("an error occurred on output stream: {}", err),
)
let error_callback = |err| eprintln!("an error occurred on output stream: {}", err);
match format.data_type {
cpal::SampleFormat::F32 => self.build_output_stream::<f32, _, _>(
&format.shape(),
move |data| {
data.iter_mut()
.for_each(|d| *d = mixer_rx.next().unwrap_or(0f32))
},
error_callback,
),
cpal::SampleFormat::I16 => self.build_output_stream::<i16, _, _>(
&format.shape(),
move |data| {
data.iter_mut()
.for_each(|d| *d = mixer_rx.next().map(|s| s.to_i16()).unwrap_or(0i16))
},
error_callback,
),
cpal::SampleFormat::U16 => self.build_output_stream::<u16, _, _>(
&format.shape(),
move |data| {
data.iter_mut().for_each(|d| {
*d = mixer_rx
.next()
.map(|s| s.to_u16())
.unwrap_or(u16::max_value() / 2)
})
},
error_callback,
),
}
.map(|stream| (mixer_tx, stream))
}
@ -46,40 +122,6 @@ impl RodioDevice for cpal::Device {
}
}
fn audio_callback(mixer: &mut DynamicMixer<f32>, buffer: cpal::StreamData) {
use cpal::{StreamData, UnknownTypeOutputBuffer};
match buffer {
StreamData::Output {
buffer: UnknownTypeOutputBuffer::U16(mut buffer),
} => {
for d in buffer.iter_mut() {
*d = mixer
.next()
.map(|s| s.to_u16())
.unwrap_or(u16::max_value() / 2);
}
}
StreamData::Output {
buffer: UnknownTypeOutputBuffer::I16(mut buffer),
} => {
for d in buffer.iter_mut() {
*d = mixer.next().map(|s| s.to_i16()).unwrap_or(0i16);
}
}
StreamData::Output {
buffer: UnknownTypeOutputBuffer::F32(mut buffer),
} => {
for d in buffer.iter_mut() {
*d = mixer.next().unwrap_or(0f32);
}
}
StreamData::Input { .. } => {
panic!("Can't play an input stream!");
}
};
}
/// All the supported output formats with sample rates
fn supported_output_formats(device: &cpal::Device) -> impl Iterator<Item = cpal::Format> {
const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100);

View file

@ -1,5 +1,5 @@
use cpal::traits::{DeviceTrait, StreamTrait};
use device::RodioDevice;
use device::CpalDeviceExt;
use dynamic_mixer::DynamicMixerController;
use source::Source;
use std::collections::HashMap;

View file

@ -26,8 +26,8 @@ where
let input = Arc::new(DynamicMixerController {
has_pending: AtomicBool::new(false),
pending_sources: Mutex::new(Vec::new()),
channels: channels,
sample_rate: sample_rate,
channels,
sample_rate,
});
let output = DynamicMixer {

View file

@ -20,7 +20,7 @@
//! use std::io::BufReader;
//! use rodio::Source;
//!
//! let device = rodio::default_output_device().unwrap();
//! let device = rodio::RodioDevice::default_output().unwrap();
//!
//! let file = File::open("sound.ogg").unwrap();
//! let source = rodio::Decoder::new(BufReader::new(file)).unwrap();
@ -38,7 +38,7 @@
//! ```no_run
//! use rodio::Sink;
//!
//! let device = rodio::default_output_device().unwrap();
//! let device = rodio::RodioDevice::default_output().unwrap();
//! let sink = Sink::new(&device);
//!
//! // Add a dummy source of the sake of the example.
@ -88,8 +88,6 @@ extern crate claxon;
extern crate cpal;
#[cfg(feature = "wav")]
extern crate hound;
#[macro_use]
extern crate lazy_static;
#[cfg(feature = "vorbis")]
extern crate lewton;
#[cfg(feature = "mp3")]
@ -104,11 +102,7 @@ pub use decoder::Decoder;
pub use sink::Sink;
pub use source::Source;
pub use spatial_sink::SpatialSink;
use cpal::traits::HostTrait;
use device_mixer::DeviceMixer;
use std::io::{Read, Seek};
use std::sync::Mutex;
pub use device::RodioDevice;
mod conversions;
mod sink;
@ -122,68 +116,3 @@ pub mod dynamic_mixer;
pub mod queue;
pub mod source;
pub mod static_buffer;
/// Plays a source with a device until it ends.
pub fn play_raw<S>(device: &cpal::Device, source: S)
where
S: Source<Item = f32> + Send + 'static,
{
lazy_static! {
static ref GLOBAL_MIXER: Mutex<DeviceMixer> = <_>::default();
}
GLOBAL_MIXER.lock().unwrap().play(device, source)
}
/// Plays a sound once. Returns a `Sink` that can be used to control the sound.
#[inline]
pub fn play_once<R>(device: &Device, input: R) -> Result<Sink, decoder::DecoderError>
where
R: Read + Seek + Send + 'static,
{
let input = decoder::Decoder::new(input)?;
let sink = Sink::new(device);
sink.append(input);
Ok(sink)
}
/// The default input audio device on the system.
///
/// Returns `None` if no input device is available.
#[inline]
pub fn default_input_device() -> Option<Device> {
cpal::default_host().default_input_device()
}
/// The default output audio device on the system.
///
/// Returns `None` if no output device is available.
#[inline]
pub fn default_output_device() -> Option<Device> {
cpal::default_host().default_output_device()
}
/// An iterator yielding all `Device`s currently available to the host on the system.
///
/// Can be empty if the system does not support audio in general.
#[inline]
pub fn devices() -> Result<Devices, DevicesError> {
cpal::default_host().devices()
}
/// An iterator yielding all `Device`s currently available to the system that support one or more
/// input stream formats.
///
/// Can be empty if the system does not support audio input.
#[inline]
pub fn input_devices() -> Result<InputDevices<Devices>, DevicesError> {
cpal::default_host().input_devices()
}
/// An iterator yielding all `Device`s currently available to the system that support one or more
/// output stream formats.
///
/// Can be empty if the system does not support audio output.
#[inline]
pub fn output_devices() -> Result<OutputDevices<Devices>, DevicesError> {
cpal::default_host().output_devices()
}

View file

@ -1,3 +1,4 @@
use device::RodioDevice;
use std::sync::atomic::Ordering;
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::sync::mpsc::Receiver;
@ -5,10 +6,8 @@ use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use play_raw;
use queue;
use source::Done;
use Device;
use Sample;
use Source;
@ -35,9 +34,9 @@ struct Controls {
impl Sink {
/// Builds a new `Sink`, beginning playback on a Device.
#[inline]
pub fn new(device: &Device) -> Sink {
pub fn new(device: &RodioDevice) -> Sink {
let (sink, queue_rx) = Sink::new_idle();
play_raw(device, queue_rx);
device.play_raw(queue_rx);
sink
}
@ -47,7 +46,7 @@ impl Sink {
let (queue_tx, queue_rx) = queue::queue(true);
let sink = Sink {
queue_tx: queue_tx,
queue_tx,
sleep_until_end: Mutex::new(None),
controls: Arc::new(Controls {
pause: AtomicBool::new(false),

View file

@ -1,9 +1,9 @@
use device::RodioDevice;
use source::Spatial;
use std::f32;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use Device;
use Sample;
use Sink;
use Source;
@ -23,7 +23,7 @@ impl SpatialSink {
/// Builds a new `SpatialSink`.
#[inline]
pub fn new(
device: &Device, emitter_position: [f32; 3], left_ear: [f32; 3], right_ear: [f32; 3],
device: &RodioDevice, emitter_position: [f32; 3], left_ear: [f32; 3], right_ear: [f32; 3],
) -> SpatialSink {
SpatialSink {
sink: Sink::new(device),