mirror of
https://github.com/RustAudio/rodio
synced 2024-11-10 14:14:21 +00:00
Rework to use no-event-loop cpal
This commit is contained in:
parent
6e2c022b5d
commit
00c627241c
5 changed files with 167 additions and 232 deletions
|
@ -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
121
src/device.rs
Normal 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
30
src/device_mixer.rs
Normal 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);
|
||||
}
|
||||
}
|
229
src/engine.rs
229
src/engine.rs
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
17
src/lib.rs
17
src/lib.rs
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue