use libc::{c_char, c_int, c_void};
use std::convert::TryFrom;
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::mem;
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::ptr;
use crate::get_error;
use crate::rwops::RWops;
use crate::AudioSubsystem;
use crate::sys;
use crate::sys::SDL_AudioStatus;
impl AudioSubsystem {
#[inline]
pub fn open_playback<'a, CB, F, D>(
&self,
device: D,
spec: &AudioSpecDesired,
get_callback: F,
) -> Result<AudioDevice<CB>, String>
where
CB: AudioCallback,
F: FnOnce(AudioSpec) -> CB,
D: Into<Option<&'a str>>,
{
AudioDevice::open_playback(self, device, spec, get_callback)
}
pub fn open_capture<'a, CB, F, D>(
&self,
device: D,
spec: &AudioSpecDesired,
get_callback: F,
) -> Result<AudioDevice<CB>, String>
where
CB: AudioCallback,
F: FnOnce(AudioSpec) -> CB,
D: Into<Option<&'a str>>,
{
AudioDevice::open_capture(self, device, spec, get_callback)
}
#[inline]
pub fn open_queue<'a, Channel, D>(
&self,
device: D,
spec: &AudioSpecDesired,
) -> Result<AudioQueue<Channel>, String>
where
Channel: AudioFormatNum,
D: Into<Option<&'a str>>,
{
AudioQueue::open_queue(self, device, spec)
}
#[doc(alias = "SDL_GetCurrentAudioDriver")]
pub fn current_audio_driver(&self) -> &'static str {
unsafe {
let buf = sys::SDL_GetCurrentAudioDriver();
assert!(!buf.is_null());
CStr::from_ptr(buf as *const _).to_str().unwrap()
}
}
#[doc(alias = "SDL_GetNumAudioDevices")]
pub fn num_audio_playback_devices(&self) -> Option<u32> {
let result = unsafe { sys::SDL_GetNumAudioDevices(0) };
if result < 0 {
None
} else {
Some(result as u32)
}
}
#[doc(alias = "SDL_GetNumAudioDevices")]
pub fn num_audio_capture_devices(&self) -> Option<u32> {
let result = unsafe { sys::SDL_GetNumAudioDevices(1) };
if result < 0 {
None
} else {
Some(result as u32)
}
}
#[doc(alias = "SDL_GetAudioDeviceName")]
pub fn audio_playback_device_name(&self, index: u32) -> Result<String, String> {
unsafe {
let dev_name = sys::SDL_GetAudioDeviceName(index as c_int, 0);
if dev_name.is_null() {
Err(get_error())
} else {
let cstr = CStr::from_ptr(dev_name as *const _);
Ok(cstr.to_str().unwrap().to_owned())
}
}
}
#[doc(alias = "SDL_GetAudioDeviceName")]
pub fn audio_capture_device_name(&self, index: u32) -> Result<String, String> {
unsafe {
let dev_name = sys::SDL_GetAudioDeviceName(index as c_int, 1);
if dev_name.is_null() {
Err(get_error())
} else {
let cstr = CStr::from_ptr(dev_name as *const _);
Ok(cstr.to_str().unwrap().to_owned())
}
}
}
}
#[repr(i32)]
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub enum AudioFormat {
U8 = sys::AUDIO_U8 as i32,
S8 = sys::AUDIO_S8 as i32,
U16LSB = sys::AUDIO_U16LSB as i32,
U16MSB = sys::AUDIO_U16MSB as i32,
S16LSB = sys::AUDIO_S16LSB as i32,
S16MSB = sys::AUDIO_S16MSB as i32,
S32LSB = sys::AUDIO_S32LSB as i32,
S32MSB = sys::AUDIO_S32MSB as i32,
F32LSB = sys::AUDIO_F32LSB as i32,
F32MSB = sys::AUDIO_F32MSB as i32,
}
impl AudioFormat {
fn from_ll(raw: sys::SDL_AudioFormat) -> Option<AudioFormat> {
use self::AudioFormat::*;
match raw as u32 {
sys::AUDIO_U8 => Some(U8),
sys::AUDIO_S8 => Some(S8),
sys::AUDIO_U16LSB => Some(U16LSB),
sys::AUDIO_U16MSB => Some(U16MSB),
sys::AUDIO_S16LSB => Some(S16LSB),
sys::AUDIO_S16MSB => Some(S16MSB),
sys::AUDIO_S32LSB => Some(S32LSB),
sys::AUDIO_S32MSB => Some(S32MSB),
sys::AUDIO_F32LSB => Some(F32LSB),
sys::AUDIO_F32MSB => Some(F32MSB),
_ => None,
}
}
#[doc(alias = "SDL_AudioFormat")]
fn to_ll(self) -> sys::SDL_AudioFormat {
self as sys::SDL_AudioFormat
}
}
#[cfg(target_endian = "little")]
impl AudioFormat {
#[inline]
pub const fn u16_sys() -> AudioFormat {
AudioFormat::U16LSB
}
#[inline]
pub const fn s16_sys() -> AudioFormat {
AudioFormat::S16LSB
}
#[inline]
pub const fn s32_sys() -> AudioFormat {
AudioFormat::S32LSB
}
#[inline]
pub const fn f32_sys() -> AudioFormat {
AudioFormat::F32LSB
}
}
#[cfg(target_endian = "big")]
impl AudioFormat {
#[inline]
pub const fn u16_sys() -> AudioFormat {
AudioFormat::U16MSB
}
#[inline]
pub const fn s16_sys() -> AudioFormat {
AudioFormat::S16MSB
}
#[inline]
pub const fn s32_sys() -> AudioFormat {
AudioFormat::S32MSB
}
#[inline]
pub const fn f32_sys() -> AudioFormat {
AudioFormat::F32MSB
}
}
#[repr(i32)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum AudioStatus {
Stopped = SDL_AudioStatus::SDL_AUDIO_STOPPED as i32,
Playing = SDL_AudioStatus::SDL_AUDIO_PLAYING as i32,
Paused = SDL_AudioStatus::SDL_AUDIO_PAUSED as i32,
}
impl TryFrom<u32> for AudioStatus {
type Error = ();
fn try_from(n: u32) -> Result<Self, Self::Error> {
use self::AudioStatus::*;
use crate::sys::SDL_AudioStatus::*;
Ok(match unsafe { mem::transmute(n) } {
SDL_AUDIO_STOPPED => Stopped,
SDL_AUDIO_PLAYING => Playing,
SDL_AUDIO_PAUSED => Paused,
})
}
}
#[doc(alias = "SDL_GetAudioDriver")]
#[derive(Copy, Clone)]
pub struct DriverIterator {
length: i32,
index: i32,
}
impl Iterator for DriverIterator {
type Item = &'static str;
#[inline]
fn next(&mut self) -> Option<&'static str> {
if self.index >= self.length {
None
} else {
unsafe {
let buf = sys::SDL_GetAudioDriver(self.index);
assert!(!buf.is_null());
self.index += 1;
Some(CStr::from_ptr(buf as *const _).to_str().unwrap())
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let l = self.length as usize;
(l, Some(l))
}
}
impl ExactSizeIterator for DriverIterator {}
#[doc(alias = "SDL_GetAudioDriver")]
#[inline]
pub fn drivers() -> DriverIterator {
DriverIterator {
length: unsafe { sys::SDL_GetNumAudioDrivers() },
index: 0,
}
}
pub struct AudioSpecWAV {
pub freq: i32,
pub format: AudioFormat,
pub channels: u8,
audio_buf: *mut u8,
audio_len: u32,
}
impl AudioSpecWAV {
pub fn load_wav<P: AsRef<Path>>(path: P) -> Result<AudioSpecWAV, String> {
let mut file = RWops::from_file(path, "rb")?;
AudioSpecWAV::load_wav_rw(&mut file)
}
#[doc(alias = "SDL_LoadWAV_RW")]
pub fn load_wav_rw(src: &mut RWops) -> Result<AudioSpecWAV, String> {
use std::mem::MaybeUninit;
use std::ptr::null_mut;
let mut desired = MaybeUninit::uninit();
let mut audio_buf: *mut u8 = null_mut();
let mut audio_len: u32 = 0;
unsafe {
let ret = sys::SDL_LoadWAV_RW(
src.raw(),
0,
desired.as_mut_ptr(),
&mut audio_buf,
&mut audio_len,
);
if ret.is_null() {
Err(get_error())
} else {
let desired = desired.assume_init();
Ok(AudioSpecWAV {
freq: desired.freq,
format: AudioFormat::from_ll(desired.format).unwrap(),
channels: desired.channels,
audio_buf,
audio_len,
})
}
}
}
pub fn buffer(&self) -> &[u8] {
use std::slice::from_raw_parts;
unsafe {
let ptr = self.audio_buf as *const u8;
let len = self.audio_len as usize;
from_raw_parts(ptr, len)
}
}
}
impl Drop for AudioSpecWAV {
#[doc(alias = "SDL_FreeWAV")]
fn drop(&mut self) {
unsafe {
sys::SDL_FreeWAV(self.audio_buf);
}
}
}
pub trait AudioCallback: Send
where
Self::Channel: AudioFormatNum + 'static,
{
type Channel;
fn callback(&mut self, _: &mut [Self::Channel]);
}
pub trait AudioFormatNum {
fn audio_format() -> AudioFormat;
const SILENCE: Self;
}
impl AudioFormatNum for i8 {
fn audio_format() -> AudioFormat {
AudioFormat::S8
}
const SILENCE: i8 = 0;
}
impl AudioFormatNum for u8 {
fn audio_format() -> AudioFormat {
AudioFormat::U8
}
const SILENCE: u8 = 0x80;
}
impl AudioFormatNum for i16 {
fn audio_format() -> AudioFormat {
AudioFormat::s16_sys()
}
const SILENCE: i16 = 0;
}
impl AudioFormatNum for u16 {
fn audio_format() -> AudioFormat {
AudioFormat::u16_sys()
}
const SILENCE: u16 = 0x8000;
}
impl AudioFormatNum for i32 {
fn audio_format() -> AudioFormat {
AudioFormat::s32_sys()
}
const SILENCE: i32 = 0;
}
impl AudioFormatNum for f32 {
fn audio_format() -> AudioFormat {
AudioFormat::f32_sys()
}
const SILENCE: f32 = 0.0;
}
extern "C" fn audio_callback_marshall<CB: AudioCallback>(
userdata: *mut c_void,
stream: *mut u8,
len: c_int,
) {
use std::mem::size_of;
use std::slice::from_raw_parts_mut;
unsafe {
let cb_userdata: &mut Option<CB> = &mut *(userdata as *mut _);
let buf: &mut [CB::Channel] = from_raw_parts_mut(
stream as *mut CB::Channel,
len as usize / size_of::<CB::Channel>(),
);
if let Some(cb) = cb_userdata {
cb.callback(buf);
}
}
}
#[derive(Clone)]
pub struct AudioSpecDesired {
pub freq: Option<i32>,
pub channels: Option<u8>,
pub samples: Option<u16>,
}
impl AudioSpecDesired {
fn convert_to_ll<CB, F, C, S>(
freq: F,
channels: C,
samples: S,
userdata: *mut Option<CB>,
) -> sys::SDL_AudioSpec
where
CB: AudioCallback,
F: Into<Option<i32>>,
C: Into<Option<u8>>,
S: Into<Option<u16>>,
{
let freq = freq.into();
let channels = channels.into();
let samples = samples.into();
if let Some(freq) = freq {
assert!(freq > 0);
}
if let Some(channels) = channels {
assert!(channels > 0);
}
if let Some(samples) = samples {
assert!(samples > 0);
}
sys::SDL_AudioSpec {
freq: freq.unwrap_or(0),
format: <CB::Channel as AudioFormatNum>::audio_format().to_ll(),
channels: channels.unwrap_or(0),
silence: 0,
samples: samples.unwrap_or(0),
padding: 0,
size: 0,
callback: Some(
audio_callback_marshall::<CB>
as extern "C" fn(arg1: *mut c_void, arg2: *mut u8, arg3: c_int),
),
userdata: userdata as *mut _,
}
}
fn convert_queue_to_ll<Channel, F, C, S>(freq: F, channels: C, samples: S) -> sys::SDL_AudioSpec
where
Channel: AudioFormatNum,
F: Into<Option<i32>>,
C: Into<Option<u8>>,
S: Into<Option<u16>>,
{
let freq = freq.into();
let channels = channels.into();
let samples = samples.into();
if let Some(freq) = freq {
assert!(freq > 0);
}
if let Some(channels) = channels {
assert!(channels > 0);
}
if let Some(samples) = samples {
assert!(samples > 0);
}
sys::SDL_AudioSpec {
freq: freq.unwrap_or(0),
format: <Channel as AudioFormatNum>::audio_format().to_ll(),
channels: channels.unwrap_or(0),
silence: 0,
samples: samples.unwrap_or(0),
padding: 0,
size: 0,
callback: None,
userdata: ptr::null_mut(),
}
}
}
#[allow(missing_copy_implementations)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct AudioSpec {
pub freq: i32,
pub format: AudioFormat,
pub channels: u8,
pub silence: u8,
pub samples: u16,
pub size: u32,
}
impl AudioSpec {
fn convert_from_ll(spec: sys::SDL_AudioSpec) -> AudioSpec {
AudioSpec {
freq: spec.freq,
format: AudioFormat::from_ll(spec.format).unwrap(),
channels: spec.channels,
silence: spec.silence,
samples: spec.samples,
size: spec.size,
}
}
}
enum AudioDeviceID {
PlaybackDevice(sys::SDL_AudioDeviceID),
}
impl AudioDeviceID {
fn id(&self) -> sys::SDL_AudioDeviceID {
match *self {
AudioDeviceID::PlaybackDevice(id) => id,
}
}
}
impl Drop for AudioDeviceID {
#[doc(alias = "SDL_CloseAudioDevice")]
fn drop(&mut self) {
unsafe { sys::SDL_CloseAudioDevice(self.id()) }
}
}
pub struct AudioQueue<Channel: AudioFormatNum> {
subsystem: AudioSubsystem,
device_id: AudioDeviceID,
phantom: PhantomData<Channel>,
spec: AudioSpec,
}
impl<'a, Channel: AudioFormatNum> AudioQueue<Channel> {
#[doc(alias = "SDL_OpenAudioDevice")]
pub fn open_queue<D: Into<Option<&'a str>>>(
a: &AudioSubsystem,
device: D,
spec: &AudioSpecDesired,
) -> Result<AudioQueue<Channel>, String> {
use std::mem::MaybeUninit;
let desired = AudioSpecDesired::convert_queue_to_ll::<
Channel,
Option<i32>,
Option<u8>,
Option<u16>,
>(spec.freq, spec.channels, spec.samples);
let mut obtained = MaybeUninit::uninit();
unsafe {
let device = match device.into() {
Some(device) => Some(CString::new(device).unwrap()),
None => None,
};
let device_ptr = device.as_ref().map_or(ptr::null(), |s| s.as_ptr());
let iscapture_flag = 0;
let device_id = sys::SDL_OpenAudioDevice(
device_ptr as *const c_char,
iscapture_flag,
&desired,
obtained.as_mut_ptr(),
0,
);
match device_id {
0 => Err(get_error()),
id => {
let obtained = obtained.assume_init();
let device_id = AudioDeviceID::PlaybackDevice(id);
let spec = AudioSpec::convert_from_ll(obtained);
Ok(AudioQueue {
subsystem: a.clone(),
device_id,
phantom: PhantomData::default(),
spec,
})
}
}
}
}
#[inline]
#[doc(alias = "SDL_GetAudioDeviceStatus")]
pub fn subsystem(&self) -> &AudioSubsystem {
&self.subsystem
}
#[inline]
pub fn spec(&self) -> &AudioSpec {
&self.spec
}
pub fn status(&self) -> AudioStatus {
unsafe {
let status = sys::SDL_GetAudioDeviceStatus(self.device_id.id());
AudioStatus::try_from(status as u32).unwrap()
}
}
#[doc(alias = "SDL_PauseAudioDevice")]
pub fn pause(&self) {
unsafe { sys::SDL_PauseAudioDevice(self.device_id.id(), 1) }
}
#[doc(alias = "SDL_PauseAudioDevice")]
pub fn resume(&self) {
unsafe { sys::SDL_PauseAudioDevice(self.device_id.id(), 0) }
}
#[doc(alias = "SDL_QueueAudio")]
pub fn queue(&self, data: &[Channel]) -> bool {
let result = unsafe {
sys::SDL_QueueAudio(
self.device_id.id(),
data.as_ptr() as *const c_void,
(data.len() * mem::size_of::<Channel>()) as u32,
)
};
result == 0
}
#[doc(alias = "SDL_GetQueuedAudioSize")]
pub fn size(&self) -> u32 {
unsafe { sys::SDL_GetQueuedAudioSize(self.device_id.id()) }
}
#[doc(alias = "SDL_ClearQueuedAudio")]
pub fn clear(&self) {
unsafe {
sys::SDL_ClearQueuedAudio(self.device_id.id());
}
}
}
pub struct AudioDevice<CB: AudioCallback> {
subsystem: AudioSubsystem,
device_id: AudioDeviceID,
spec: AudioSpec,
userdata: Box<Option<CB>>,
}
impl<CB: AudioCallback> AudioDevice<CB> {
#[doc(alias = "SDL_OpenAudioDevice")]
fn open<'a, F, D>(
a: &AudioSubsystem,
device: D,
spec: &AudioSpecDesired,
get_callback: F,
capture: bool,
) -> Result<AudioDevice<CB>, String>
where
F: FnOnce(AudioSpec) -> CB,
D: Into<Option<&'a str>>,
{
use std::mem::MaybeUninit;
let mut userdata: Box<Option<CB>> = Box::new(None);
let desired =
AudioSpecDesired::convert_to_ll(spec.freq, spec.channels, spec.samples, &mut *userdata);
let mut obtained = MaybeUninit::uninit();
unsafe {
let device = match device.into() {
Some(device) => Some(CString::new(device).unwrap()),
None => None,
};
let device_ptr = device.as_ref().map_or(ptr::null(), |s| s.as_ptr());
let iscapture_flag = if capture { 1 } else { 0 };
let device_id = sys::SDL_OpenAudioDevice(
device_ptr as *const c_char,
iscapture_flag,
&desired,
obtained.as_mut_ptr(),
0,
);
match device_id {
0 => Err(get_error()),
id => {
let obtained = obtained.assume_init();
let device_id = AudioDeviceID::PlaybackDevice(id);
let spec = AudioSpec::convert_from_ll(obtained);
*userdata = Some(get_callback(spec));
Ok(AudioDevice {
subsystem: a.clone(),
device_id,
userdata,
spec,
})
}
}
}
}
pub fn open_playback<'a, F, D>(
a: &AudioSubsystem,
device: D,
spec: &AudioSpecDesired,
get_callback: F,
) -> Result<AudioDevice<CB>, String>
where
F: FnOnce(AudioSpec) -> CB,
D: Into<Option<&'a str>>,
{
AudioDevice::open(a, device, spec, get_callback, false)
}
pub fn open_capture<'a, F, D>(
a: &AudioSubsystem,
device: D,
spec: &AudioSpecDesired,
get_callback: F,
) -> Result<AudioDevice<CB>, String>
where
F: FnOnce(AudioSpec) -> CB,
D: Into<Option<&'a str>>,
{
AudioDevice::open(a, device, spec, get_callback, true)
}
#[inline]
#[doc(alias = "SDL_GetAudioDeviceStatus")]
pub fn subsystem(&self) -> &AudioSubsystem {
&self.subsystem
}
#[inline]
pub fn spec(&self) -> &AudioSpec {
&self.spec
}
pub fn status(&self) -> AudioStatus {
unsafe {
let status = sys::SDL_GetAudioDeviceStatus(self.device_id.id());
AudioStatus::try_from(status as u32).unwrap()
}
}
#[doc(alias = "SDL_PauseAudioDevice")]
pub fn pause(&self) {
unsafe { sys::SDL_PauseAudioDevice(self.device_id.id(), 1) }
}
#[doc(alias = "SDL_PauseAudioDevice")]
pub fn resume(&self) {
unsafe { sys::SDL_PauseAudioDevice(self.device_id.id(), 0) }
}
#[doc(alias = "SDL_LockAudioDevice")]
pub fn lock(&mut self) -> AudioDeviceLockGuard<CB> {
unsafe { sys::SDL_LockAudioDevice(self.device_id.id()) };
AudioDeviceLockGuard {
device: self,
_nosend: PhantomData,
}
}
pub fn close_and_get_callback(self) -> CB {
drop(self.device_id);
self.userdata.expect("Missing callback")
}
}
pub struct AudioDeviceLockGuard<'a, CB>
where
CB: AudioCallback,
CB: 'a,
{
device: &'a mut AudioDevice<CB>,
_nosend: PhantomData<*mut ()>,
}
impl<'a, CB: AudioCallback> Deref for AudioDeviceLockGuard<'a, CB> {
type Target = CB;
#[doc(alias = "SDL_UnlockAudioDevice")]
fn deref(&self) -> &CB {
(*self.device.userdata).as_ref().expect("Missing callback")
}
}
impl<'a, CB: AudioCallback> DerefMut for AudioDeviceLockGuard<'a, CB> {
fn deref_mut(&mut self) -> &mut CB {
(*self.device.userdata).as_mut().expect("Missing callback")
}
}
impl<'a, CB: AudioCallback> Drop for AudioDeviceLockGuard<'a, CB> {
fn drop(&mut self) {
unsafe { sys::SDL_UnlockAudioDevice(self.device.device_id.id()) }
}
}
#[derive(Copy, Clone)]
pub struct AudioCVT {
raw: sys::SDL_AudioCVT,
}
impl AudioCVT {
#[doc(alias = "SDL_BuildAudioCVT")]
pub fn new(
src_format: AudioFormat,
src_channels: u8,
src_rate: i32,
dst_format: AudioFormat,
dst_channels: u8,
dst_rate: i32,
) -> Result<AudioCVT, String> {
use std::mem::MaybeUninit;
let mut raw: MaybeUninit<sys::SDL_AudioCVT> = mem::MaybeUninit::uninit();
unsafe {
let ret = sys::SDL_BuildAudioCVT(
raw.as_mut_ptr(),
src_format.to_ll(),
src_channels,
src_rate as c_int,
dst_format.to_ll(),
dst_channels,
dst_rate as c_int,
);
if ret == 1 || ret == 0 {
let raw = raw.assume_init();
Ok(AudioCVT { raw })
} else {
Err(get_error())
}
}
}
#[doc(alias = "SDL_ConvertAudio")]
pub fn convert(&self, mut src: Vec<u8>) -> Vec<u8> {
unsafe {
if self.raw.needed != 0 {
let mut raw = self.raw;
use std::convert::TryInto;
raw.len = src.len().try_into().expect("Buffer length overflow");
let dst_size = self.capacity(src.len());
let needed = dst_size - src.len();
src.reserve_exact(needed);
raw.buf = src.as_mut_ptr();
let ret = sys::SDL_ConvertAudio(&mut raw);
if ret != 0 {
panic!("{}", get_error())
}
debug_assert!(raw.len_cvt > 0);
debug_assert!(raw.len_cvt as usize <= src.capacity());
src.set_len(raw.len_cvt as usize);
src
} else {
src
}
}
}
pub fn is_conversion_needed(&self) -> bool {
self.raw.needed != 0
}
pub fn capacity(&self, src_len: usize) -> usize {
src_len
.checked_mul(self.raw.len_mult as usize)
.expect("Integer overflow")
}
}
#[cfg(test)]
mod test {
use super::{AudioCVT, AudioFormat};
#[test]
fn test_audio_cvt() {
use std::iter::repeat;
let buffer: Vec<u8> = (0..255).collect();
let new_buffer_expected: Vec<u8> = (0..255).flat_map(|v| repeat(v).take(2)).collect();
let cvt = AudioCVT::new(AudioFormat::U8, 1, 44100, AudioFormat::U8, 2, 44100).unwrap();
assert!(cvt.is_conversion_needed());
assert!(
cvt.capacity(255) >= 255 * 2,
"capacity must be able to hold the converted audio sample"
);
let new_buffer = cvt.convert(buffer);
assert_eq!(
new_buffer.len(),
new_buffer_expected.len(),
"capacity must be exactly equal to twice the original vec size"
);
}
}