Skip to content

Commit

Permalink
SDL audio is now Task-safe. Add rustrt crate. fixes #220
Browse files Browse the repository at this point in the history
  • Loading branch information
nukep committed Nov 27, 2014
1 parent 03b6c21 commit f50b630
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 32 deletions.
2 changes: 1 addition & 1 deletion examples/audio-whitenoise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fn main() {
let desired_spec = AudioSpecDesired {
freq: 44100,
channels: 1,
callback: box MyCallback { rng: StdRng::new().unwrap(), volume: 1.0 }
callback: MyCallback { rng: StdRng::new().unwrap(), volume: 1.0 }
};

// None: use default device
Expand Down
119 changes: 88 additions & 31 deletions src/sdl2/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::c_vec::CVec;
use libc;
use libc::{c_int, size_t, c_void};
use libc::{uint8_t};
use rustrt::task::Task;

use get_error;
use rwops::RWops;
Expand Down Expand Up @@ -242,6 +243,41 @@ pub trait AudioCallback<T> {
fn callback(&mut self, &mut [T]);
}

/// The userdata as seen by the SDL callback.
struct AudioCallbackUserdata<CB> {
task: AudioCallbackTask,
callback: CB
}

/// A Task is required to use libstd from SDL's audio callback.
struct AudioCallbackTask {
/// Set to None if there was an error running a previous task.
task: Option<Box<Task>>
}

impl Drop for AudioCallbackTask {
/// Destroy the callback task.
fn drop(&mut self) {
use rustrt::local::Local;
use std::mem::replace;

// Swap out the task with None in order to own it, since drop() only
// provides a reference.
match replace(&mut self.task, None) {
Some(task) => {
// pop current task
let old_task = Local::take();

task.destroy();

// put task back
Local::put(old_task);
},
None => ()
};
}
}

/// A phantom type for retreiving the SDL_AudioFormat of a given generic type.
/// All format types are returned as native-endian.
///
Expand All @@ -263,50 +299,76 @@ impl AudioFormatNum<f32> for f32 { fn get_audio_format() -> ll::SDL_AudioFormat
extern "C" fn audio_callback_marshall<T: AudioFormatNum<T>, CB: AudioCallback<T>>
(userdata: *const c_void, stream: *const uint8_t, len: c_int) {
use std::raw::Slice;
use std::mem::{size_of, transmute};
use std::mem::{replace, size_of, transmute};
unsafe {
let audio_callback: &mut CB = transmute(userdata);
let mut buf: &mut [T] = transmute(Slice {
let mut cb_userdata: &mut AudioCallbackUserdata<CB> = transmute(userdata);
let buf: &mut [T] = transmute(Slice {
data: stream,
len: len as uint / size_of::<T>()
});
audio_callback.callback(buf);

// Perform a dance to move tasks around without compiler errors
let new_task = match replace(&mut cb_userdata.task.task, None) {
Some(task) => {
let n = task.run(|| {
cb_userdata.callback.callback(buf);
});

if n.is_destroyed() { None }
else { Some(n) }
},
None => None
};

replace(&mut cb_userdata.task.task, new_task);
}
}

pub struct AudioSpecDesired<T: AudioFormatNum<T>, CB: AudioCallback<T>> {
pub freq: i32,
pub channels: u8,
pub callback: Box<CB>
pub callback: CB
}

impl<T: AudioFormatNum<T>, CB: AudioCallback<T>> AudioSpecDesired<T, CB> {
fn convert_to_ll(self) -> ll::SDL_AudioSpec {
fn convert_to_ll(freq: i32, channels: u8, userdata: &mut AudioCallbackUserdata<CB>) -> ll::SDL_AudioSpec {
use std::mem::transmute;

unsafe {
ll::SDL_AudioSpec {
freq: self.freq,
freq: freq,
format: AudioFormatNum::<T>::get_audio_format(),
channels: self.channels,
channels: channels,
silence: 0,
samples: 0,
padding: 0,
size: 0,
callback: Some(audio_callback_marshall::<T, CB>),
userdata: transmute(self.callback)
userdata: transmute(userdata)
}
}
}

fn callback_to_userdata(callback: CB) -> Box<AudioCallbackUserdata<CB>> {
let mut task = box Task::new(None, None);
task.name = Some("SDL audio callback".into_maybe_owned());

box AudioCallbackUserdata {
task: AudioCallbackTask { task: Some(task) },
callback: callback
}
}

/// Opens a new audio device given the desired parameters and callback.
/// Uses `SDL_OpenAudioDevice`.
pub fn open_audio_device(self, device: Option<&str>, iscapture: bool) -> SdlResult<AudioDevice<Box<CB>>> {
pub fn open_audio_device(self, device: Option<&str>, iscapture: bool) -> SdlResult<AudioDevice<CB>> {
use std::mem::uninitialized;
use std::mem::transmute;
use std::ptr::null;
use std::c_str::CString;
use libc::c_char;
let desired = self.convert_to_ll();

let mut userdata = AudioSpecDesired::callback_to_userdata(self.callback);
let desired = AudioSpecDesired::convert_to_ll(self.freq, self.channels, &mut *userdata);

let mut obtained = unsafe { uninitialized::<ll::SDL_AudioSpec>() };
unsafe {
Expand All @@ -322,8 +384,6 @@ impl<T: AudioFormatNum<T>, CB: AudioCallback<T>> AudioSpecDesired<T, CB> {
let device_id = ll::SDL_OpenAudioDevice(device_cstr_ptr, iscapture_flag, &desired, &mut obtained, 0);
match device_id {
0 => {
// uninitialize the callback data to avoid memory leaks
let _: Box<CB> = transmute(desired.userdata);
Err(get_error())
},
id => {
Expand All @@ -332,12 +392,12 @@ impl<T: AudioFormatNum<T>, CB: AudioCallback<T>> AudioSpecDesired<T, CB> {
false => AudioDeviceID::PlaybackDevice(id)
};

let (spec, callback) = AudioSpec::convert_from_ll_box(obtained);
let spec = AudioSpec::convert_from_ll(obtained);

Ok(AudioDevice {
device_id: device_id,
spec: spec,
callback_data: callback
userdata: userdata
})
}
}
Expand All @@ -357,17 +417,14 @@ pub struct AudioSpec {
}

impl AudioSpec {
fn convert_from_ll_box<CB>(spec: ll::SDL_AudioSpec) -> (AudioSpec, Box<CB>) {
use std::mem::transmute;
unsafe {
(AudioSpec {
freq: spec.freq,
format: spec.format,
channels: spec.channels,
silence: spec.silence,
samples: spec.samples,
size: spec.size
}, transmute(spec.userdata))
fn convert_from_ll(spec: ll::SDL_AudioSpec) -> AudioSpec {
AudioSpec {
freq: spec.freq,
format: spec.format,
channels: spec.channels,
silence: spec.silence,
samples: spec.samples,
size: spec.size
}
}
}
Expand Down Expand Up @@ -399,7 +456,7 @@ pub struct AudioDevice<CB> {
/// Every audio device corresponds to an SDL_AudioSpec.
spec: AudioSpec,
/// Store the callback to keep it alive for the entire duration of `AudioDevice`.
callback_data: CB
userdata: Box<AudioCallbackUserdata<CB>>
}

impl<CB> AudioDevice<CB> {
Expand Down Expand Up @@ -441,7 +498,7 @@ impl<CB> AudioDevice<CB> {
/// but the callback data will be dropped.
pub fn close_and_get_callback(self) -> CB {
drop(self.device_id);
self.callback_data
self.userdata.callback
}
}

Expand All @@ -451,11 +508,11 @@ pub struct AudioDeviceLockGuard<'a, CB: 'a> {
}

impl<'a, CB> Deref<CB> for AudioDeviceLockGuard<'a, CB> {
fn deref(&self) -> &CB { &self.device.callback_data }
fn deref(&self) -> &CB { &self.device.userdata.callback }
}

impl<'a, CB> DerefMut<CB> for AudioDeviceLockGuard<'a, CB> {
fn deref_mut(&mut self) -> &mut CB { &mut self.device.callback_data }
fn deref_mut(&mut self) -> &mut CB { &mut self.device.userdata.callback }
}

#[unsafe_destructor]
Expand Down
1 change: 1 addition & 0 deletions src/sdl2/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

extern crate libc;
extern crate collections;
extern crate rustrt;

pub use sdl::*;

Expand Down

0 comments on commit f50b630

Please sign in to comment.