Rework to use no-event-loop cpal

This commit is contained in:
Alex Butler 2019-08-23 20:15:54 +01:00
parent 6e2c022b5d
commit 00c627241c
No known key found for this signature in database
GPG key ID: E1355A2F8E415521
5 changed files with 167 additions and 232 deletions

View file

@ -10,7 +10,7 @@ documentation = "http://docs.rs/rodio"
[dependencies]
claxon = { version = "0.4.2", optional = true }
cpal = "0.11"
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 }

121
src/device.rs Normal file
View file

@ -0,0 +1,121 @@
use cpal::{traits::DeviceTrait, Sample};
use dynamic_mixer::{self, DynamicMixer, DynamicMixerController};
use std::sync::Arc;
/// Extensions to `cpal::Device`
pub(crate) trait RodioDevice {
fn new_output_stream_with_format(
&self,
format: cpal::Format,
) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), cpal::BuildStreamError>;
fn new_output_stream(&self) -> (Arc<DynamicMixerController<f32>>, cpal::Stream);
}
impl RodioDevice for cpal::Device {
fn new_output_stream_with_format(
&self,
format: cpal::Format,
) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), cpal::BuildStreamError> {
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),
)
.map(|stream| (mixer_tx, stream))
}
fn new_output_stream(&self) -> (Arc<DynamicMixerController<f32>>, cpal::Stream) {
// Determine the format to use for the new stream.
let default_format = self
.default_output_format()
.expect("The device doesn't support any format!?");
self.new_output_stream_with_format(default_format)
.unwrap_or_else(|err| {
// look through all supported formats to see if another works
supported_output_formats(self)
.filter_map(|format| self.new_output_stream_with_format(format).ok())
.next()
.ok_or(err)
.expect("build_output_stream failed with all supported formats")
})
}
}
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);
let mut supported: Vec<_> = device
.supported_output_formats()
.expect("No supported output formats")
.collect();
supported.sort_by(|a, b| b.cmp_default_heuristics(a));
supported.into_iter().flat_map(|sf| {
let max_rate = sf.max_sample_rate;
let min_rate = sf.min_sample_rate;
let mut formats = vec![sf.clone().with_max_sample_rate()];
if HZ_44100 < max_rate && HZ_44100 > min_rate {
formats.push(sf.clone().with_sample_rate(HZ_44100))
}
formats.push(sf.with_sample_rate(min_rate));
formats
})
}
trait SupportedFormatExt {
fn with_sample_rate(self, sample_rate: cpal::SampleRate) -> cpal::Format;
}
impl SupportedFormatExt for cpal::SupportedFormat {
fn with_sample_rate(self, sample_rate: cpal::SampleRate) -> cpal::Format {
let Self {
channels,
data_type,
..
} = self;
cpal::Format {
channels,
sample_rate,
data_type,
}
}
}

30
src/device_mixer.rs Normal file
View file

@ -0,0 +1,30 @@
use cpal::traits::{DeviceTrait, StreamTrait};
use device::RodioDevice;
use dynamic_mixer::DynamicMixerController;
use source::Source;
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Default)]
pub(crate) struct DeviceMixer {
// TODO: don't use the device name as it's slow
/// Device name -> (mixer, stream)
mixers: HashMap<String, (Arc<DynamicMixerController<f32>>, cpal::Stream)>,
}
impl DeviceMixer {
pub(crate) fn play<S>(&mut self, device: &cpal::Device, source: S)
where
S: Source<Item = f32> + Send + 'static,
{
let device_name = device.name().expect("No device name");
let (ref mut mixer, _) = self.mixers.entry(device_name).or_insert_with(|| {
let (mixer, stream) = device.new_output_stream();
stream.play().expect("play");
(mixer, stream)
});
mixer.add(source);
}
}

View file

@ -1,229 +0,0 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Weak;
use std::thread::Builder;
use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait};
use cpal::Device;
use cpal::EventLoop;
use cpal::Sample as CpalSample;
use cpal::StreamData;
use cpal::StreamId;
use cpal::UnknownTypeOutputBuffer;
use dynamic_mixer;
use source::Source;
/// Plays a source with a device until it ends.
///
/// The playing uses a background thread.
pub fn play_raw<S>(device: &Device, source: S)
where
S: Source<Item = f32> + Send + 'static,
{
lazy_static! {
static ref ENGINE: Arc<Engine> = {
let engine = Arc::new(Engine {
events_loop: cpal::default_host().event_loop(),
dynamic_mixers: Mutex::new(HashMap::with_capacity(1)),
end_points: Mutex::new(HashMap::with_capacity(1)),
});
// We ignore errors when creating the background thread.
// The user won't get any audio, but that's better than a panic.
Builder::new()
.name("rodio audio processing".to_string())
.spawn({
let engine = engine.clone();
move || {
engine.events_loop.run(|stream_id, buffer| {
if let Ok(buf) = buffer {
audio_callback(&engine, stream_id, buf);
}
})
}
})
.ok()
.map(|jg| jg.thread().clone());
engine
};
}
start(&ENGINE, device, source);
}
// The internal engine of this library.
//
// Each `Engine` owns a thread that runs in the background and plays the audio.
struct Engine {
// The events loop which the streams are created with.
events_loop: EventLoop,
dynamic_mixers: Mutex<HashMap<StreamId, dynamic_mixer::DynamicMixer<f32>>>,
// TODO: don't use the device name, as it's slow
end_points: Mutex<HashMap<String, Weak<dynamic_mixer::DynamicMixerController<f32>>>>,
}
fn audio_callback(engine: &Arc<Engine>, stream_id: StreamId, buffer: StreamData) {
let mut dynamic_mixers = engine.dynamic_mixers.lock().unwrap();
let mixer_rx = match dynamic_mixers.get_mut(&stream_id) {
Some(m) => m,
None => return,
};
match buffer {
StreamData::Output {
buffer: UnknownTypeOutputBuffer::U16(mut buffer),
} => for d in buffer.iter_mut() {
*d = mixer_rx
.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_rx.next().map(|s| s.to_i16()).unwrap_or(0i16);
},
StreamData::Output {
buffer: UnknownTypeOutputBuffer::F32(mut buffer),
} => for d in buffer.iter_mut() {
*d = mixer_rx.next().unwrap_or(0f32);
},
StreamData::Input { .. } => {
panic!("Can't play an input stream!");
},
};
}
// Builds a new sink that targets a given device.
fn start<S>(engine: &Arc<Engine>, device: &Device, source: S)
where
S: Source<Item = f32> + Send + 'static,
{
let mut stream_to_start = None;
let mixer = if let Ok(device_name) = device.name() {
let mut end_points = engine.end_points.lock().unwrap();
match end_points.entry(device_name) {
Entry::Vacant(e) => {
let (mixer, stream) = new_output_stream(engine, device);
e.insert(Arc::downgrade(&mixer));
stream_to_start = Some(stream);
mixer
},
Entry::Occupied(mut e) => {
if let Some(m) = e.get().upgrade() {
m.clone()
} else {
let (mixer, stream) = new_output_stream(engine, device);
e.insert(Arc::downgrade(&mixer));
stream_to_start = Some(stream);
mixer
}
},
}
} else {
let (mixer, stream) = new_output_stream(engine, device);
stream_to_start = Some(stream);
mixer
};
if let Some(stream) = stream_to_start {
engine.events_loop.play_stream(stream).expect("play_stream failed");
}
mixer.add(source);
}
// Adds a new stream to the engine.
fn new_output_stream(
engine: &Arc<Engine>,
device: &Device,
) -> (Arc<dynamic_mixer::DynamicMixerController<f32>>, StreamId) {
let (format, stream_id) = {
// Determine the format to use for the new stream.
let default_format = device
.default_output_format()
.expect("The device doesn't support any format!?");
match engine
.events_loop
.build_output_stream(device, &default_format)
{
Ok(sid) => (default_format, sid),
Err(err) => find_working_output_stream(engine, device)
.ok_or(err)
.expect("build_output_stream failed with all supported formats"),
}
};
let (mixer_tx, mixer_rx) = dynamic_mixer::mixer::<f32>(format.channels, format.sample_rate.0);
engine
.dynamic_mixers
.lock()
.unwrap()
.insert(stream_id.clone(), mixer_rx);
(mixer_tx, stream_id)
}
/// Search through all the supported formats trying to find one that
/// will `build_output_stream` successfully.
fn find_working_output_stream(
engine: &Arc<Engine>,
device: &Device,
) -> Option<(cpal::Format, cpal::StreamId)> {
const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100);
let mut supported: Vec<_> = device
.supported_output_formats()
.expect("No supported output formats")
.collect();
supported.sort_by(|a, b| b.cmp_default_heuristics(a));
supported
.into_iter()
.flat_map(|sf| {
let max_rate = sf.max_sample_rate;
let min_rate = sf.min_sample_rate;
let mut formats = vec![sf.clone().with_max_sample_rate()];
if HZ_44100 < max_rate && HZ_44100 > min_rate {
formats.push(sf.clone().with_sample_rate(HZ_44100))
}
formats.push(sf.with_sample_rate(min_rate));
formats
})
.filter_map(|format| {
engine
.events_loop
.build_output_stream(device, &format)
.ok()
.map(|stream| (format, stream))
})
.next()
}
trait SupportedFormatExt {
fn with_sample_rate(self, sample_rate: cpal::SampleRate) -> cpal::Format;
}
impl SupportedFormatExt for cpal::SupportedFormat {
fn with_sample_rate(self, sample_rate: cpal::SampleRate) -> cpal::Format {
let Self {
channels,
data_type,
..
} = self;
cpal::Format {
channels,
sample_rate,
data_type,
}
}
}

View file

@ -101,18 +101,20 @@ pub use cpal::{
pub use conversions::Sample;
pub use decoder::Decoder;
pub use engine::play_raw;
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;
mod conversions;
mod engine;
mod sink;
mod spatial_sink;
mod device_mixer;
mod device;
pub mod buffer;
pub mod decoder;
@ -121,6 +123,17 @@ 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>