commit
fece179da7
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
|
|
||||||
name = "cpal"
|
name = "cpal"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
authors = ["Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
authors = ["Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||||
description = "Cross-platform audio playing library in pure Rust."
|
description = "Cross-platform audio playing library in pure Rust."
|
||||||
repository = "https://github.com/tomaka/cpal"
|
repository = "https://github.com/tomaka/cpal"
|
||||||
|
@ -11,6 +11,7 @@ keywords = ["audio", "sound"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "*"
|
libc = "*"
|
||||||
|
lazy_static = "0.1"
|
||||||
|
|
||||||
[target.i686-pc-windows-gnu.dependencies]
|
[target.i686-pc-windows-gnu.dependencies]
|
||||||
winapi = "0.2.1"
|
winapi = "0.2.1"
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
extern crate cpal;
|
extern crate cpal;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut channel = cpal::Voice::new();
|
let endpoint = cpal::get_default_endpoint().unwrap();
|
||||||
|
let format = endpoint.get_supported_formats_list().unwrap().next().unwrap();
|
||||||
|
let mut channel = cpal::Voice::new(&endpoint, &format).unwrap();
|
||||||
|
|
||||||
// Produce a sinusoid of maximum amplitude.
|
// Produce a sinusoid of maximum amplitude.
|
||||||
let mut data_source = (0u64..).map(|t| t as f32 * 0.03)
|
let mut data_source = (0u64..).map(|t| t as f32 * 0.03)
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
use super::alsa;
|
||||||
|
use super::check_errors;
|
||||||
|
use super::Endpoint;
|
||||||
|
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
/// ALSA implementation for `EndpointsIterator`.
|
||||||
|
pub struct EndpointsIterator {
|
||||||
|
// we keep the original list so that we can pass it to the free function
|
||||||
|
global_list: *const *const u8,
|
||||||
|
|
||||||
|
// pointer to the next string ; contained within `global_list`
|
||||||
|
next_str: *const *const u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for EndpointsIterator {}
|
||||||
|
unsafe impl Sync for EndpointsIterator {}
|
||||||
|
|
||||||
|
impl Drop for EndpointsIterator {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
alsa::snd_device_name_free_hint(self.global_list as *mut _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EndpointsIterator {
|
||||||
|
fn default() -> EndpointsIterator {
|
||||||
|
unsafe {
|
||||||
|
let mut hints = mem::uninitialized();
|
||||||
|
// TODO: check in which situation this can fail
|
||||||
|
check_errors(alsa::snd_device_name_hint(-1, b"pcm\0".as_ptr() as *const _,
|
||||||
|
&mut hints)).unwrap();
|
||||||
|
|
||||||
|
let hints = hints as *const *const u8;
|
||||||
|
|
||||||
|
EndpointsIterator {
|
||||||
|
global_list: hints,
|
||||||
|
next_str: hints,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for EndpointsIterator {
|
||||||
|
type Item = Endpoint;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Endpoint> {
|
||||||
|
loop {
|
||||||
|
unsafe {
|
||||||
|
if (*self.next_str).is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = alsa::snd_device_name_get_hint(*self.next_str as *const _,
|
||||||
|
b"NAME".as_ptr() as *const _);
|
||||||
|
self.next_str = self.next_str.offset(1);
|
||||||
|
|
||||||
|
if name.is_null() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = CStr::from_ptr(name).to_bytes().to_vec();
|
||||||
|
let name = String::from_utf8(name).unwrap();
|
||||||
|
|
||||||
|
if name != "null" {
|
||||||
|
return Some(Endpoint(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_default_endpoint() -> Option<Endpoint> {
|
||||||
|
// TODO: do in a different way?
|
||||||
|
Some(Endpoint("default".to_owned()))
|
||||||
|
}
|
|
@ -1,9 +1,39 @@
|
||||||
extern crate alsa_sys as alsa;
|
extern crate alsa_sys as alsa;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
|
||||||
|
pub use self::enumerate::{EndpointsIterator, get_default_endpoint};
|
||||||
|
|
||||||
|
use CreationError;
|
||||||
|
use Format;
|
||||||
|
use FormatsEnumerationError;
|
||||||
|
use SampleFormat;
|
||||||
|
use SamplesRate;
|
||||||
|
|
||||||
use std::{ffi, iter, mem};
|
use std::{ffi, iter, mem};
|
||||||
|
use std::option::IntoIter as OptionIntoIter;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
pub type SupportedFormatsIterator = OptionIntoIter<Format>;
|
||||||
|
|
||||||
|
mod enumerate;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Endpoint(String);
|
||||||
|
|
||||||
|
impl Endpoint {
|
||||||
|
pub fn get_supported_formats_list(&self)
|
||||||
|
-> Result<SupportedFormatsIterator, FormatsEnumerationError>
|
||||||
|
{
|
||||||
|
let format = Format {
|
||||||
|
channels: 2,
|
||||||
|
samples_rate: SamplesRate(44100),
|
||||||
|
data_type: SampleFormat::I16,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(format).into_iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Voice {
|
pub struct Voice {
|
||||||
channel: Mutex<*mut alsa::snd_pcm_t>,
|
channel: Mutex<*mut alsa::snd_pcm_t>,
|
||||||
num_channels: u16,
|
num_channels: u16,
|
||||||
|
@ -15,9 +45,9 @@ pub struct Buffer<'a, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Voice {
|
impl Voice {
|
||||||
pub fn new() -> Voice {
|
pub fn new(endpoint: &Endpoint, _format: &Format) -> Result<Voice, CreationError> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let name = ffi::CString::new(b"default".to_vec()).unwrap();
|
let name = ffi::CString::new(endpoint.0.clone()).unwrap();
|
||||||
|
|
||||||
let mut playback_handle = mem::uninitialized();
|
let mut playback_handle = mem::uninitialized();
|
||||||
check_errors(alsa::snd_pcm_open(&mut playback_handle, name.as_ptr(), alsa::SND_PCM_STREAM_PLAYBACK, alsa::SND_PCM_NONBLOCK)).unwrap();
|
check_errors(alsa::snd_pcm_open(&mut playback_handle, name.as_ptr(), alsa::SND_PCM_STREAM_PLAYBACK, alsa::SND_PCM_NONBLOCK)).unwrap();
|
||||||
|
@ -34,10 +64,10 @@ impl Voice {
|
||||||
|
|
||||||
check_errors(alsa::snd_pcm_prepare(playback_handle)).unwrap();
|
check_errors(alsa::snd_pcm_prepare(playback_handle)).unwrap();
|
||||||
|
|
||||||
Voice {
|
Ok(Voice {
|
||||||
channel: Mutex::new(playback_handle),
|
channel: Mutex::new(playback_handle),
|
||||||
num_channels: 2,
|
num_channels: 2,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
153
src/lib.rs
153
src/lib.rs
|
@ -4,7 +4,16 @@
|
||||||
In order to play a sound, first you need to create a `Voice`.
|
In order to play a sound, first you need to create a `Voice`.
|
||||||
|
|
||||||
```no_run
|
```no_run
|
||||||
let mut voice = cpal::Voice::new();
|
// getting the default sound output of the system (can return `None` if nothing is supported)
|
||||||
|
let endpoint = cpal::get_default_endpoint().unwrap();
|
||||||
|
|
||||||
|
// note that the user can at any moment disconnect the device, therefore all operations return
|
||||||
|
// a `Result` to handle this situation
|
||||||
|
|
||||||
|
// getting a format for the PCM
|
||||||
|
let format = endpoint.get_supported_formats_list().unwrap().next().unwrap();
|
||||||
|
|
||||||
|
let mut voice = cpal::Voice::new(&endpoint, &format).unwrap();
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you must send raw samples to it by calling `append_data`. You must take the number of channels
|
Then you must send raw samples to it by calling `append_data`. You must take the number of channels
|
||||||
|
@ -19,7 +28,7 @@ this is not some obscure situation that can be ignored.
|
||||||
After you have submitted data for the first time, call `play`:
|
After you have submitted data for the first time, call `play`:
|
||||||
|
|
||||||
```no_run
|
```no_run
|
||||||
# let mut voice = cpal::Voice::new();
|
# let mut voice: cpal::Voice = unsafe { std::mem::uninitialized() };
|
||||||
voice.play();
|
voice.play();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -28,8 +37,13 @@ reaches the end of the data, it will stop playing. You must continuously fill th
|
||||||
calling `append_data` repeatedly if you don't want the audio to stop playing.
|
calling `append_data` repeatedly if you don't want the audio to stop playing.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
pub use samples_formats::{SampleFormat, Sample};
|
pub use samples_formats::{SampleFormat, Sample};
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::error::Error;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
mod samples_formats;
|
mod samples_formats;
|
||||||
|
@ -50,6 +64,46 @@ mod cpal_impl;
|
||||||
#[path="null/mod.rs"]
|
#[path="null/mod.rs"]
|
||||||
mod cpal_impl;
|
mod cpal_impl;
|
||||||
|
|
||||||
|
/// An iterator for the list of formats that are supported by the backend.
|
||||||
|
pub struct EndpointsIterator(cpal_impl::EndpointsIterator);
|
||||||
|
|
||||||
|
impl Iterator for EndpointsIterator {
|
||||||
|
type Item = Endpoint;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Endpoint> {
|
||||||
|
self.0.next().map(Endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.0.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an iterator to the list of formats that are supported by the system.
|
||||||
|
pub fn get_endpoints_list() -> EndpointsIterator {
|
||||||
|
EndpointsIterator(Default::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the default endpoint, or `None` if no device is available.
|
||||||
|
pub fn get_default_endpoint() -> Option<Endpoint> {
|
||||||
|
cpal_impl::get_default_endpoint().map(Endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An opaque type that identifies an end point.
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub struct Endpoint(cpal_impl::Endpoint);
|
||||||
|
|
||||||
|
impl Endpoint {
|
||||||
|
/// Returns an iterator that produces the list of formats that are supported by the backend.
|
||||||
|
pub fn get_supported_formats_list(&self) -> Result<SupportedFormatsIterator,
|
||||||
|
FormatsEnumerationError>
|
||||||
|
{
|
||||||
|
Ok(SupportedFormatsIterator(try!(self.0.get_supported_formats_list())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Number of channels.
|
/// Number of channels.
|
||||||
pub type ChannelsCount = u16;
|
pub type ChannelsCount = u16;
|
||||||
|
|
||||||
|
@ -57,6 +111,31 @@ pub type ChannelsCount = u16;
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct SamplesRate(pub u32);
|
pub struct SamplesRate(pub u32);
|
||||||
|
|
||||||
|
/// Describes a format.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Format {
|
||||||
|
pub channels: ChannelsCount,
|
||||||
|
pub samples_rate: SamplesRate,
|
||||||
|
pub data_type: SampleFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator that produces a list of formats supported by the endpoint.
|
||||||
|
pub struct SupportedFormatsIterator(cpal_impl::SupportedFormatsIterator);
|
||||||
|
|
||||||
|
impl Iterator for SupportedFormatsIterator {
|
||||||
|
type Item = Format;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Format> {
|
||||||
|
self.0.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.0.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a buffer that must be filled with audio data.
|
/// Represents a buffer that must be filled with audio data.
|
||||||
///
|
///
|
||||||
/// You should destroy this object as soon as possible. Data is only committed when it
|
/// You should destroy this object as soon as possible. Data is only committed when it
|
||||||
|
@ -79,6 +158,61 @@ pub enum UnknownTypeBuffer<'a> {
|
||||||
F32(Buffer<'a, f32>),
|
F32(Buffer<'a, f32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Error that can happen when enumerating the list of supported formats.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FormatsEnumerationError {
|
||||||
|
/// The device no longer exists. This can happen if the device is disconnected while the
|
||||||
|
/// program is running.
|
||||||
|
DeviceNotAvailable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FormatsEnumerationError {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
write!(fmt, "{}", self.description())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for FormatsEnumerationError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
&FormatsEnumerationError::DeviceNotAvailable => {
|
||||||
|
"The requested device is no longer available (for example, it has been unplugged)."
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error that can happen when creating a `Voice`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CreationError {
|
||||||
|
/// The device no longer exists. This can happen if the device is disconnected while the
|
||||||
|
/// program is running.
|
||||||
|
DeviceNotAvailable,
|
||||||
|
|
||||||
|
/// The required format is not supported.
|
||||||
|
FormatNotSupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CreationError {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
write!(fmt, "{}", self.description())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for CreationError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
&CreationError::DeviceNotAvailable => {
|
||||||
|
"The requested device is no longer available (for example, it has been unplugged)."
|
||||||
|
},
|
||||||
|
|
||||||
|
&CreationError::FormatNotSupported => {
|
||||||
|
"The requested samples format is not supported by the device."
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Controls a sound output. A typical application has one `Voice` for each sound
|
/// Controls a sound output. A typical application has one `Voice` for each sound
|
||||||
/// it wants to output.
|
/// it wants to output.
|
||||||
///
|
///
|
||||||
|
@ -95,15 +229,17 @@ pub struct Voice(cpal_impl::Voice);
|
||||||
|
|
||||||
impl Voice {
|
impl Voice {
|
||||||
/// Builds a new channel.
|
/// Builds a new channel.
|
||||||
pub fn new() -> Voice {
|
#[inline]
|
||||||
let channel = cpal_impl::Voice::new();
|
pub fn new(endpoint: &Endpoint, format: &Format) -> Result<Voice, CreationError> {
|
||||||
Voice(channel)
|
let channel = try!(cpal_impl::Voice::new(&endpoint.0, format));
|
||||||
|
Ok(Voice(channel))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of channels.
|
/// Returns the number of channels.
|
||||||
///
|
///
|
||||||
/// You can add data with any number of channels, but matching the voice's native format
|
/// You can add data with any number of channels, but matching the voice's native format
|
||||||
/// will lead to better performances.
|
/// will lead to better performances.
|
||||||
|
#[inline]
|
||||||
pub fn get_channels(&self) -> ChannelsCount {
|
pub fn get_channels(&self) -> ChannelsCount {
|
||||||
self.0.get_channels()
|
self.0.get_channels()
|
||||||
}
|
}
|
||||||
|
@ -112,6 +248,7 @@ impl Voice {
|
||||||
///
|
///
|
||||||
/// You can add data with any samples rate, but matching the voice's native format
|
/// You can add data with any samples rate, but matching the voice's native format
|
||||||
/// will lead to better performances.
|
/// will lead to better performances.
|
||||||
|
#[inline]
|
||||||
pub fn get_samples_rate(&self) -> SamplesRate {
|
pub fn get_samples_rate(&self) -> SamplesRate {
|
||||||
self.0.get_samples_rate()
|
self.0.get_samples_rate()
|
||||||
}
|
}
|
||||||
|
@ -120,6 +257,7 @@ impl Voice {
|
||||||
///
|
///
|
||||||
/// You can add data of any format, but matching the voice's native format
|
/// You can add data of any format, but matching the voice's native format
|
||||||
/// will lead to better performances.
|
/// will lead to better performances.
|
||||||
|
#[inline]
|
||||||
pub fn get_samples_format(&self) -> SampleFormat {
|
pub fn get_samples_format(&self) -> SampleFormat {
|
||||||
self.0.get_samples_format()
|
self.0.get_samples_format()
|
||||||
}
|
}
|
||||||
|
@ -164,6 +302,7 @@ impl Voice {
|
||||||
///
|
///
|
||||||
/// Only call this after you have submitted some data, otherwise you may hear
|
/// Only call this after you have submitted some data, otherwise you may hear
|
||||||
/// some glitches.
|
/// some glitches.
|
||||||
|
#[inline]
|
||||||
pub fn play(&mut self) {
|
pub fn play(&mut self) {
|
||||||
self.0.play()
|
self.0.play()
|
||||||
}
|
}
|
||||||
|
@ -173,6 +312,7 @@ impl Voice {
|
||||||
/// Has no effect is the voice was already paused.
|
/// Has no effect is the voice was already paused.
|
||||||
///
|
///
|
||||||
/// If you call `play` afterwards, the playback will resume exactly where it was.
|
/// If you call `play` afterwards, the playback will resume exactly where it was.
|
||||||
|
#[inline]
|
||||||
pub fn pause(&mut self) {
|
pub fn pause(&mut self) {
|
||||||
self.0.pause()
|
self.0.pause()
|
||||||
}
|
}
|
||||||
|
@ -181,18 +321,21 @@ impl Voice {
|
||||||
impl<'a, T> Deref for Buffer<'a, T> where T: Sample {
|
impl<'a, T> Deref for Buffer<'a, T> where T: Sample {
|
||||||
type Target = [T];
|
type Target = [T];
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn deref(&self) -> &[T] {
|
fn deref(&self) -> &[T] {
|
||||||
panic!("It is forbidden to read from the audio buffer");
|
panic!("It is forbidden to read from the audio buffer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> DerefMut for Buffer<'a, T> where T: Sample {
|
impl<'a, T> DerefMut for Buffer<'a, T> where T: Sample {
|
||||||
|
#[inline]
|
||||||
fn deref_mut(&mut self) -> &mut [T] {
|
fn deref_mut(&mut self) -> &mut [T] {
|
||||||
self.target.as_mut().unwrap().get_buffer()
|
self.target.as_mut().unwrap().get_buffer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Drop for Buffer<'a, T> where T: Sample {
|
impl<'a, T> Drop for Buffer<'a, T> where T: Sample {
|
||||||
|
#[inline]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.target.take().unwrap().finish();
|
self.target.take().unwrap().finish();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub enum SampleFormat {
|
||||||
|
|
||||||
impl SampleFormat {
|
impl SampleFormat {
|
||||||
/// Returns the size in bytes of a sample of this format.
|
/// Returns the size in bytes of a sample of this format.
|
||||||
|
#[inline]
|
||||||
pub fn get_sample_size(&self) -> usize {
|
pub fn get_sample_size(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
&SampleFormat::I16 => mem::size_of::<i16>(),
|
&SampleFormat::I16 => mem::size_of::<i16>(),
|
||||||
|
@ -29,18 +30,21 @@ pub unsafe trait Sample: Copy + Clone {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Sample for u16 {
|
unsafe impl Sample for u16 {
|
||||||
|
#[inline]
|
||||||
fn get_format() -> SampleFormat {
|
fn get_format() -> SampleFormat {
|
||||||
SampleFormat::U16
|
SampleFormat::U16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Sample for i16 {
|
unsafe impl Sample for i16 {
|
||||||
|
#[inline]
|
||||||
fn get_format() -> SampleFormat {
|
fn get_format() -> SampleFormat {
|
||||||
SampleFormat::I16
|
SampleFormat::I16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Sample for f32 {
|
unsafe impl Sample for f32 {
|
||||||
|
#[inline]
|
||||||
fn get_format() -> SampleFormat {
|
fn get_format() -> SampleFormat {
|
||||||
SampleFormat::F32
|
SampleFormat::F32
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
//! Handles COM initialization and cleanup.
|
||||||
|
|
||||||
|
use std::ptr;
|
||||||
|
use super::winapi;
|
||||||
|
use super::ole32;
|
||||||
|
use super::check_result;
|
||||||
|
|
||||||
|
thread_local!(static COM_INITIALIZED: ComInitialized = {
|
||||||
|
unsafe {
|
||||||
|
// this call can fail if another library initialized COM in single-threaded mode
|
||||||
|
// handling this situation properly would make the API more annoying, so we just don't care
|
||||||
|
check_result(ole32::CoInitializeEx(ptr::null_mut(), winapi::COINIT_MULTITHREADED)).unwrap();
|
||||||
|
ComInitialized(ptr::null_mut())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// RAII object that guards the fact that COM is initialized.
|
||||||
|
///
|
||||||
|
// We store a raw pointer because it's the only way at the moment to remove `Send`/`Sync` from the
|
||||||
|
// object.
|
||||||
|
struct ComInitialized(*mut ());
|
||||||
|
|
||||||
|
impl Drop for ComInitialized {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { ole32::CoUninitialize() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensures that COM is initialized in this thread.
|
||||||
|
pub fn com_initialized() {
|
||||||
|
COM_INITIALIZED.with(|_| {});
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
use super::winapi;
|
||||||
|
use super::ole32;
|
||||||
|
use super::com;
|
||||||
|
use super::Endpoint;
|
||||||
|
use super::check_result;
|
||||||
|
|
||||||
|
use std::mem;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref ENUMERATOR: Enumerator = {
|
||||||
|
// COM initialization is thread local, but we only need to have COM initialized in the
|
||||||
|
// thread we create the objects in
|
||||||
|
com::com_initialized();
|
||||||
|
|
||||||
|
// building the devices enumerator object
|
||||||
|
unsafe {
|
||||||
|
let mut enumerator: *mut winapi::IMMDeviceEnumerator = mem::uninitialized();
|
||||||
|
|
||||||
|
let hresult = ole32::CoCreateInstance(&winapi::CLSID_MMDeviceEnumerator,
|
||||||
|
ptr::null_mut(), winapi::CLSCTX_ALL,
|
||||||
|
&winapi::IID_IMMDeviceEnumerator,
|
||||||
|
&mut enumerator
|
||||||
|
as *mut *mut winapi::IMMDeviceEnumerator
|
||||||
|
as *mut _);
|
||||||
|
|
||||||
|
check_result(hresult).unwrap();
|
||||||
|
Enumerator(enumerator)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RAII object around `winapi::IMMDeviceEnumerator`.
|
||||||
|
struct Enumerator(*mut winapi::IMMDeviceEnumerator);
|
||||||
|
|
||||||
|
unsafe impl Send for Enumerator {}
|
||||||
|
unsafe impl Sync for Enumerator {}
|
||||||
|
|
||||||
|
impl Drop for Enumerator {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
(*self.0).Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// WASAPI implementation for `EndpointsIterator`.
|
||||||
|
pub struct EndpointsIterator {
|
||||||
|
collection: *mut winapi::IMMDeviceCollection,
|
||||||
|
total_count: u32,
|
||||||
|
next_item: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for EndpointsIterator {}
|
||||||
|
unsafe impl Sync for EndpointsIterator {}
|
||||||
|
|
||||||
|
impl Drop for EndpointsIterator {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
(*self.collection).Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EndpointsIterator {
|
||||||
|
fn default() -> EndpointsIterator {
|
||||||
|
unsafe {
|
||||||
|
let mut collection: *mut winapi::IMMDeviceCollection = mem::uninitialized();
|
||||||
|
// can fail because of wrong parameters (should never happen) or out of memory
|
||||||
|
check_result((*ENUMERATOR.0).EnumAudioEndpoints(winapi::EDataFlow::eRender,
|
||||||
|
winapi::DEVICE_STATE_ACTIVE,
|
||||||
|
&mut collection))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut count = mem::uninitialized();
|
||||||
|
// can fail if the parameter is null, which should never happen
|
||||||
|
check_result((*collection).GetCount(&mut count)).unwrap();
|
||||||
|
|
||||||
|
EndpointsIterator {
|
||||||
|
collection: collection,
|
||||||
|
total_count: count,
|
||||||
|
next_item: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for EndpointsIterator {
|
||||||
|
type Item = Endpoint;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Endpoint> {
|
||||||
|
if self.next_item >= self.total_count {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut device = mem::uninitialized();
|
||||||
|
// can fail if out of range, which we just checked above
|
||||||
|
check_result((*self.collection).Item(self.next_item, &mut device)).unwrap();
|
||||||
|
|
||||||
|
self.next_item += 1;
|
||||||
|
Some(Endpoint::from_immdevice(device))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
let num = self.total_count - self.next_item;
|
||||||
|
let num = num as usize;
|
||||||
|
(num, Some(num))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_default_endpoint() -> Option<Endpoint> {
|
||||||
|
unsafe {
|
||||||
|
let mut device = mem::uninitialized();
|
||||||
|
let hres = (*ENUMERATOR.0).GetDefaultAudioEndpoint(winapi::EDataFlow::eRender,
|
||||||
|
winapi::ERole::eConsole, &mut device);
|
||||||
|
|
||||||
|
if let Err(_err) = check_result(hres) {
|
||||||
|
return None; // TODO: check specifically for `E_NOTFOUND`, and panic otherwise
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Endpoint::from_immdevice(device))
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,255 +2,175 @@ extern crate libc;
|
||||||
extern crate winapi;
|
extern crate winapi;
|
||||||
extern crate ole32;
|
extern crate ole32;
|
||||||
|
|
||||||
use std::{cmp, slice, mem, ptr};
|
use std::io::Error as IoError;
|
||||||
use std::marker::PhantomData;
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
use std::ptr;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
// TODO: determine if should be NoSend or not
|
use Format;
|
||||||
pub struct Voice {
|
use FormatsEnumerationError;
|
||||||
audio_client: *mut winapi::IAudioClient,
|
use SamplesRate;
|
||||||
render_client: *mut winapi::IAudioRenderClient,
|
use SampleFormat;
|
||||||
max_frames_in_buffer: winapi::UINT32,
|
|
||||||
num_channels: winapi::WORD,
|
|
||||||
bytes_per_frame: winapi::WORD,
|
|
||||||
samples_per_second: winapi::DWORD,
|
|
||||||
bits_per_sample: winapi::WORD,
|
|
||||||
playing: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Buffer<'a, T: 'a> {
|
pub use std::option::IntoIter as OptionIntoIter;
|
||||||
render_client: *mut winapi::IAudioRenderClient,
|
pub use self::enumerate::{EndpointsIterator, get_default_endpoint};
|
||||||
buffer_data: *mut T,
|
pub use self::voice::{Voice, Buffer};
|
||||||
buffer_len: usize,
|
|
||||||
frames: winapi::UINT32,
|
|
||||||
marker: PhantomData<&'a mut T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Voice {
|
pub type SupportedFormatsIterator = OptionIntoIter<Format>;
|
||||||
pub fn new() -> Voice {
|
|
||||||
init().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_channels(&self) -> ::ChannelsCount {
|
mod com;
|
||||||
self.num_channels as ::ChannelsCount
|
mod enumerate;
|
||||||
}
|
mod voice;
|
||||||
|
|
||||||
pub fn get_samples_rate(&self) -> ::SamplesRate {
|
fn check_result(result: winapi::HRESULT) -> Result<(), IoError> {
|
||||||
::SamplesRate(self.samples_per_second as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_samples_format(&self) -> ::SampleFormat {
|
|
||||||
match self.bits_per_sample {
|
|
||||||
16 => ::SampleFormat::I16,
|
|
||||||
_ => panic!("{}-bit format not yet supported", self.bits_per_sample),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn append_data<'a, T>(&'a mut self, max_elements: usize) -> Buffer<'a, T> {
|
|
||||||
unsafe {
|
|
||||||
loop {
|
|
||||||
//
|
|
||||||
let frames_available = {
|
|
||||||
let mut padding = mem::uninitialized();
|
|
||||||
let hresult = (*self.audio_client).GetCurrentPadding(&mut padding);
|
|
||||||
check_result(hresult).unwrap();
|
|
||||||
self.max_frames_in_buffer - padding
|
|
||||||
};
|
|
||||||
|
|
||||||
if frames_available == 0 {
|
|
||||||
// TODO:
|
|
||||||
::std::thread::sleep_ms(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let frames_available = cmp::min(frames_available,
|
|
||||||
max_elements as u32 * mem::size_of::<T>() as u32 /
|
|
||||||
self.bytes_per_frame as u32);
|
|
||||||
assert!(frames_available != 0);
|
|
||||||
|
|
||||||
// loading buffer
|
|
||||||
let (buffer_data, buffer_len) = {
|
|
||||||
let mut buffer: *mut winapi::BYTE = mem::uninitialized();
|
|
||||||
let hresult = (*self.render_client).GetBuffer(frames_available,
|
|
||||||
&mut buffer as *mut *mut libc::c_uchar);
|
|
||||||
check_result(hresult).unwrap();
|
|
||||||
assert!(!buffer.is_null());
|
|
||||||
|
|
||||||
(buffer as *mut T,
|
|
||||||
frames_available as usize * self.bytes_per_frame as usize
|
|
||||||
/ mem::size_of::<T>())
|
|
||||||
};
|
|
||||||
|
|
||||||
let buffer = Buffer {
|
|
||||||
render_client: self.render_client,
|
|
||||||
buffer_data: buffer_data,
|
|
||||||
buffer_len: buffer_len,
|
|
||||||
frames: frames_available,
|
|
||||||
marker: PhantomData,
|
|
||||||
};
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn play(&mut self) {
|
|
||||||
if !self.playing {
|
|
||||||
unsafe {
|
|
||||||
let hresult = (*self.audio_client).Start();
|
|
||||||
check_result(hresult).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.playing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pause(&mut self) {
|
|
||||||
if self.playing {
|
|
||||||
unsafe {
|
|
||||||
let hresult = (*self.audio_client).Stop();
|
|
||||||
check_result(hresult).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.playing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Voice {}
|
|
||||||
unsafe impl Sync for Voice {}
|
|
||||||
|
|
||||||
impl Drop for Voice {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
(*self.render_client).Release();
|
|
||||||
(*self.audio_client).Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> Buffer<'a, T> {
|
|
||||||
pub fn get_buffer<'b>(&'b mut self) -> &'b mut [T] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(self.buffer_data, self.buffer_len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(self) {
|
|
||||||
// releasing buffer
|
|
||||||
unsafe {
|
|
||||||
let hresult = (*self.render_client).ReleaseBuffer(self.frames as u32, 0);
|
|
||||||
check_result(hresult).unwrap();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init() -> Result<Voice, String> {
|
|
||||||
// FIXME: release everything
|
|
||||||
unsafe {
|
|
||||||
try!(check_result(ole32::CoInitializeEx(ptr::null_mut(), 0)));
|
|
||||||
|
|
||||||
// building the devices enumerator object
|
|
||||||
let enumerator = {
|
|
||||||
let mut enumerator: *mut winapi::IMMDeviceEnumerator = mem::uninitialized();
|
|
||||||
|
|
||||||
let hresult = ole32::CoCreateInstance(&winapi::CLSID_MMDeviceEnumerator,
|
|
||||||
ptr::null_mut(), winapi::CLSCTX_ALL,
|
|
||||||
&winapi::IID_IMMDeviceEnumerator,
|
|
||||||
mem::transmute(&mut enumerator));
|
|
||||||
|
|
||||||
try!(check_result(hresult));
|
|
||||||
&mut *enumerator
|
|
||||||
};
|
|
||||||
|
|
||||||
// getting the default end-point
|
|
||||||
let device = {
|
|
||||||
let mut device: *mut winapi::IMMDevice = mem::uninitialized();
|
|
||||||
let hresult = enumerator.GetDefaultAudioEndpoint(winapi::EDataFlow::eRender, winapi::ERole::eConsole,
|
|
||||||
mem::transmute(&mut device));
|
|
||||||
try!(check_result(hresult));
|
|
||||||
&mut *device
|
|
||||||
};
|
|
||||||
|
|
||||||
// activating in order to get a `IAudioClient`
|
|
||||||
let audio_client: &mut winapi::IAudioClient = {
|
|
||||||
let mut audio_client: *mut winapi::IAudioClient = mem::uninitialized();
|
|
||||||
let hresult = device.Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL,
|
|
||||||
ptr::null_mut(), mem::transmute(&mut audio_client));
|
|
||||||
try!(check_result(hresult));
|
|
||||||
&mut *audio_client
|
|
||||||
};
|
|
||||||
|
|
||||||
// computing the format and initializing the device
|
|
||||||
let format = {
|
|
||||||
let format_attempt = winapi::WAVEFORMATEX {
|
|
||||||
wFormatTag: 1, // WAVE_FORMAT_PCM ; TODO: replace by constant
|
|
||||||
nChannels: 2,
|
|
||||||
nSamplesPerSec: 44100,
|
|
||||||
nAvgBytesPerSec: 2 * 44100 * 2,
|
|
||||||
nBlockAlign: (2 * 16) / 8,
|
|
||||||
wBitsPerSample: 16,
|
|
||||||
cbSize: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized();
|
|
||||||
let hresult = audio_client.IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED,
|
|
||||||
&format_attempt, &mut format_ptr);
|
|
||||||
try!(check_result(hresult));
|
|
||||||
|
|
||||||
let format = if format_ptr.is_null() {
|
|
||||||
&format_attempt
|
|
||||||
} else {
|
|
||||||
&*format_ptr
|
|
||||||
};
|
|
||||||
|
|
||||||
let format_copy = ptr::read(format);
|
|
||||||
|
|
||||||
let hresult = audio_client.Initialize(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED,
|
|
||||||
0, 10000000, 0, format, ptr::null());
|
|
||||||
|
|
||||||
if !format_ptr.is_null() {
|
|
||||||
ole32::CoTaskMemFree(format_ptr as *mut _);
|
|
||||||
}
|
|
||||||
|
|
||||||
try!(check_result(hresult));
|
|
||||||
|
|
||||||
format_copy
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
let max_frames_in_buffer = {
|
|
||||||
let mut max_frames_in_buffer = mem::uninitialized();
|
|
||||||
let hresult = audio_client.GetBufferSize(&mut max_frames_in_buffer);
|
|
||||||
try!(check_result(hresult));
|
|
||||||
max_frames_in_buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
let render_client = {
|
|
||||||
let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized();
|
|
||||||
let hresult = audio_client.GetService(&winapi::IID_IAudioRenderClient,
|
|
||||||
mem::transmute(&mut render_client));
|
|
||||||
try!(check_result(hresult));
|
|
||||||
&mut *render_client
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Voice {
|
|
||||||
audio_client: audio_client,
|
|
||||||
render_client: render_client,
|
|
||||||
max_frames_in_buffer: max_frames_in_buffer,
|
|
||||||
num_channels: format.nChannels,
|
|
||||||
bytes_per_frame: format.nBlockAlign,
|
|
||||||
samples_per_second: format.nSamplesPerSec,
|
|
||||||
bits_per_sample: format.wBitsPerSample,
|
|
||||||
playing: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_result(result: winapi::HRESULT) -> Result<(), String> {
|
|
||||||
if result < 0 {
|
if result < 0 {
|
||||||
return Err(format!("Error in winapi call")); // TODO:
|
Err(IoError::from_raw_os_error(result))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper because of that stupid decision to remove `Send` and `Sync` from raw pointers.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[allow(raw_pointer_derive)]
|
||||||
|
struct IAudioClientWrapper(*mut winapi::IAudioClient);
|
||||||
|
unsafe impl Send for IAudioClientWrapper {}
|
||||||
|
unsafe impl Sync for IAudioClientWrapper {}
|
||||||
|
|
||||||
|
/// An opaque type that identifies an end point.
|
||||||
|
pub struct Endpoint {
|
||||||
|
device: *mut winapi::IMMDevice,
|
||||||
|
|
||||||
|
/// We cache an uninitialized `IAudioClient` so that we can call functions from it without
|
||||||
|
/// having to create/destroy audio clients all the time.
|
||||||
|
future_audio_client: Arc<Mutex<Option<IAudioClientWrapper>>>, // TODO: add NonZero around the ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Endpoint {}
|
||||||
|
unsafe impl Sync for Endpoint {}
|
||||||
|
|
||||||
|
impl Endpoint {
|
||||||
|
#[inline]
|
||||||
|
fn from_immdevice(device: *mut winapi::IMMDevice) -> Endpoint {
|
||||||
|
Endpoint {
|
||||||
|
device: device,
|
||||||
|
future_audio_client: Arc::new(Mutex::new(None)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
/// Ensures that `future_audio_client` contains a `Some` and returns a locked mutex to it.
|
||||||
|
fn ensure_future_audio_client(&self) -> Result<MutexGuard<Option<IAudioClientWrapper>>, IoError> {
|
||||||
|
let mut lock = self.future_audio_client.lock().unwrap();
|
||||||
|
if lock.is_some() {
|
||||||
|
return Ok(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
let audio_client: *mut winapi::IAudioClient = unsafe {
|
||||||
|
let mut audio_client = mem::uninitialized();
|
||||||
|
let hresult = (*self.device).Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL,
|
||||||
|
ptr::null_mut(), &mut audio_client);
|
||||||
|
|
||||||
|
// can fail if the device has been disconnected since we enumerated it, or if
|
||||||
|
// the device doesn't support playback for some reason
|
||||||
|
try!(check_result(hresult));
|
||||||
|
assert!(!audio_client.is_null());
|
||||||
|
audio_client as *mut _
|
||||||
|
};
|
||||||
|
|
||||||
|
*lock = Some(IAudioClientWrapper(audio_client));
|
||||||
|
Ok(lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an uninitialized `IAudioClient`.
|
||||||
|
fn build_audioclient(&self) -> Result<*mut winapi::IAudioClient, IoError> {
|
||||||
|
let mut lock = try!(self.ensure_future_audio_client());
|
||||||
|
let client = lock.unwrap().0;
|
||||||
|
*lock = None;
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_supported_formats_list(&self)
|
||||||
|
-> Result<SupportedFormatsIterator, FormatsEnumerationError>
|
||||||
|
{
|
||||||
|
// We always create voices in shared mode, therefore all samples go through an audio
|
||||||
|
// processor to mix them together.
|
||||||
|
// However there is no way to query the list of all formats that are supported by the
|
||||||
|
// audio processor, but one format is guaranteed to be supported, the one returned by
|
||||||
|
// `GetMixFormat`.
|
||||||
|
|
||||||
|
// initializing COM because we call `CoTaskMemFree`
|
||||||
|
com::com_initialized();
|
||||||
|
|
||||||
|
let lock = match self.ensure_future_audio_client() {
|
||||||
|
Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) =>
|
||||||
|
return Err(FormatsEnumerationError::DeviceNotAvailable),
|
||||||
|
e => e.unwrap(),
|
||||||
|
};
|
||||||
|
let client = lock.unwrap().0;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut format_ptr = mem::uninitialized();
|
||||||
|
match check_result((*client).GetMixFormat(&mut format_ptr)) {
|
||||||
|
Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
|
return Err(FormatsEnumerationError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => panic!("{:?}", e),
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
let format = {
|
||||||
|
let data_type = match (*format_ptr).wFormatTag {
|
||||||
|
winapi::WAVE_FORMAT_PCM => SampleFormat::I16,
|
||||||
|
winapi::WAVE_FORMAT_EXTENSIBLE => {
|
||||||
|
let format_ptr = format_ptr as *const winapi::WAVEFORMATEXTENSIBLE;
|
||||||
|
match (*format_ptr).SubFormat {
|
||||||
|
winapi::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT => SampleFormat::F32,
|
||||||
|
winapi::KSDATAFORMAT_SUBTYPE_PCM => SampleFormat::I16,
|
||||||
|
g => panic!("Unknown SubFormat GUID returned by GetMixFormat: {:?}", g)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
f => panic!("Unknown data format returned by GetMixFormat: {:?}", f)
|
||||||
|
};
|
||||||
|
|
||||||
|
Format {
|
||||||
|
channels: (*format_ptr).nChannels,
|
||||||
|
samples_rate: SamplesRate((*format_ptr).nSamplesPerSec),
|
||||||
|
data_type: data_type,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ole32::CoTaskMemFree(format_ptr as *mut _);
|
||||||
|
|
||||||
|
Ok(Some(format).into_iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Endpoint {
|
||||||
|
fn eq(&self, other: &Endpoint) -> bool {
|
||||||
|
self.device == other.device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Endpoint {}
|
||||||
|
|
||||||
|
impl Clone for Endpoint {
|
||||||
|
fn clone(&self) -> Endpoint {
|
||||||
|
unsafe { (*self.device).AddRef(); }
|
||||||
|
|
||||||
|
Endpoint {
|
||||||
|
device: self.device,
|
||||||
|
future_audio_client: self.future_audio_client.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Endpoint {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { (*self.device).Release(); }
|
||||||
|
|
||||||
|
if let Some(client) = self.future_audio_client.lock().unwrap().take() {
|
||||||
|
unsafe { (*client.0).Release(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,298 @@
|
||||||
|
use super::com;
|
||||||
|
use super::ole32;
|
||||||
|
use super::winapi;
|
||||||
|
use super::Endpoint;
|
||||||
|
use super::check_result;
|
||||||
|
|
||||||
|
use std::cmp;
|
||||||
|
use std::slice;
|
||||||
|
use std::mem;
|
||||||
|
use std::ptr;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use CreationError;
|
||||||
|
use Format;
|
||||||
|
use SampleFormat;
|
||||||
|
|
||||||
|
pub struct Voice {
|
||||||
|
audio_client: *mut winapi::IAudioClient,
|
||||||
|
render_client: *mut winapi::IAudioRenderClient,
|
||||||
|
max_frames_in_buffer: winapi::UINT32,
|
||||||
|
num_channels: winapi::WORD,
|
||||||
|
bytes_per_frame: winapi::WORD,
|
||||||
|
samples_per_second: winapi::DWORD,
|
||||||
|
bits_per_sample: winapi::WORD,
|
||||||
|
playing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Voice {}
|
||||||
|
unsafe impl Sync for Voice {}
|
||||||
|
|
||||||
|
impl Voice {
|
||||||
|
pub fn new(end_point: &Endpoint, format: &Format) -> Result<Voice, CreationError> {
|
||||||
|
// FIXME: release everything
|
||||||
|
unsafe {
|
||||||
|
// making sure that COM is initialized
|
||||||
|
// it's not actually sure that this is required, but when in doubt do it
|
||||||
|
com::com_initialized();
|
||||||
|
|
||||||
|
// obtaining a `IAudioClient`
|
||||||
|
let audio_client = match end_point.build_audioclient() {
|
||||||
|
Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) =>
|
||||||
|
return Err(CreationError::DeviceNotAvailable),
|
||||||
|
e => e.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// computing the format and initializing the device
|
||||||
|
let format = {
|
||||||
|
let format_attempt = winapi::WAVEFORMATEXTENSIBLE {
|
||||||
|
Format: winapi::WAVEFORMATEX {
|
||||||
|
wFormatTag: match format.data_type {
|
||||||
|
SampleFormat::I16 => winapi::WAVE_FORMAT_PCM,
|
||||||
|
SampleFormat::F32 => winapi::WAVE_FORMAT_EXTENSIBLE,
|
||||||
|
SampleFormat::U16 => return Err(CreationError::FormatNotSupported),
|
||||||
|
},
|
||||||
|
nChannels: format.channels as winapi::WORD,
|
||||||
|
nSamplesPerSec: format.samples_rate.0 as winapi::DWORD,
|
||||||
|
nAvgBytesPerSec: format.channels as winapi::DWORD *
|
||||||
|
format.samples_rate.0 as winapi::DWORD *
|
||||||
|
format.data_type.get_sample_size() as winapi::DWORD,
|
||||||
|
nBlockAlign: format.channels as winapi::WORD *
|
||||||
|
format.data_type.get_sample_size() as winapi::WORD,
|
||||||
|
wBitsPerSample: 8 * format.data_type.get_sample_size() as winapi::WORD,
|
||||||
|
cbSize: match format.data_type {
|
||||||
|
SampleFormat::I16 => 0,
|
||||||
|
SampleFormat::F32 => (mem::size_of::<winapi::WAVEFORMATEXTENSIBLE>() -
|
||||||
|
mem::size_of::<winapi::WAVEFORMATEX>()) as winapi::WORD,
|
||||||
|
SampleFormat::U16 => return Err(CreationError::FormatNotSupported),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Samples: 8 * format.data_type.get_sample_size() as winapi::WORD,
|
||||||
|
dwChannelMask: 3, // LEFT | RIGHT
|
||||||
|
SubFormat: match format.data_type {
|
||||||
|
SampleFormat::I16 => winapi::KSDATAFORMAT_SUBTYPE_PCM,
|
||||||
|
SampleFormat::F32 => winapi::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
|
||||||
|
SampleFormat::U16 => return Err(CreationError::FormatNotSupported),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized();
|
||||||
|
let hresult = (*audio_client).IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED,
|
||||||
|
&format_attempt.Format, &mut format_ptr);
|
||||||
|
|
||||||
|
if !format_ptr.is_null() {
|
||||||
|
ole32::CoTaskMemFree(format_ptr as *mut _);
|
||||||
|
}
|
||||||
|
|
||||||
|
if hresult == winapi::S_FALSE {
|
||||||
|
return Err(CreationError::FormatNotSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
match check_result(hresult) {
|
||||||
|
Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) =>
|
||||||
|
{
|
||||||
|
(*audio_client).Release();
|
||||||
|
return Err(CreationError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
panic!("{:?}", e);
|
||||||
|
},
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
let hresult = (*audio_client).Initialize(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED,
|
||||||
|
0, 10000000, 0, &format_attempt.Format, ptr::null());
|
||||||
|
|
||||||
|
match check_result(hresult) {
|
||||||
|
Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) =>
|
||||||
|
{
|
||||||
|
(*audio_client).Release();
|
||||||
|
return Err(CreationError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
panic!("{:?}", e);
|
||||||
|
},
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
format_attempt.Format
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
let max_frames_in_buffer = {
|
||||||
|
let mut max_frames_in_buffer = mem::uninitialized();
|
||||||
|
let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer);
|
||||||
|
|
||||||
|
match check_result(hresult) {
|
||||||
|
Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) =>
|
||||||
|
{
|
||||||
|
(*audio_client).Release();
|
||||||
|
return Err(CreationError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
panic!("{:?}", e);
|
||||||
|
},
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
max_frames_in_buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
let render_client = {
|
||||||
|
let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized();
|
||||||
|
let hresult = (*audio_client).GetService(&winapi::IID_IAudioRenderClient,
|
||||||
|
&mut render_client as *mut *mut winapi::IAudioRenderClient
|
||||||
|
as *mut _);
|
||||||
|
|
||||||
|
match check_result(hresult) {
|
||||||
|
Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) =>
|
||||||
|
{
|
||||||
|
(*audio_client).Release();
|
||||||
|
return Err(CreationError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
panic!("{:?}", e);
|
||||||
|
},
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
&mut *render_client
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Voice {
|
||||||
|
audio_client: audio_client,
|
||||||
|
render_client: render_client,
|
||||||
|
max_frames_in_buffer: max_frames_in_buffer,
|
||||||
|
num_channels: format.nChannels,
|
||||||
|
bytes_per_frame: format.nBlockAlign,
|
||||||
|
samples_per_second: format.nSamplesPerSec,
|
||||||
|
bits_per_sample: format.wBitsPerSample,
|
||||||
|
playing: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_channels(&self) -> ::ChannelsCount {
|
||||||
|
self.num_channels as ::ChannelsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_samples_rate(&self) -> ::SamplesRate {
|
||||||
|
::SamplesRate(self.samples_per_second as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_samples_format(&self) -> ::SampleFormat {
|
||||||
|
match self.bits_per_sample {
|
||||||
|
16 => ::SampleFormat::I16,
|
||||||
|
32 => ::SampleFormat::F32,
|
||||||
|
_ => panic!("{}-bit format not yet supported", self.bits_per_sample),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_data<'a, T>(&'a mut self, max_elements: usize) -> Buffer<'a, T> {
|
||||||
|
unsafe {
|
||||||
|
loop {
|
||||||
|
//
|
||||||
|
let frames_available = {
|
||||||
|
let mut padding = mem::uninitialized();
|
||||||
|
let hresult = (*self.audio_client).GetCurrentPadding(&mut padding);
|
||||||
|
check_result(hresult).unwrap();
|
||||||
|
self.max_frames_in_buffer - padding
|
||||||
|
};
|
||||||
|
|
||||||
|
if frames_available == 0 {
|
||||||
|
// TODO:
|
||||||
|
::std::thread::sleep_ms(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let frames_available = cmp::min(frames_available,
|
||||||
|
max_elements as u32 * mem::size_of::<T>() as u32 /
|
||||||
|
self.bytes_per_frame as u32);
|
||||||
|
assert!(frames_available != 0);
|
||||||
|
|
||||||
|
// loading buffer
|
||||||
|
let (buffer_data, buffer_len) = {
|
||||||
|
let mut buffer: *mut winapi::BYTE = mem::uninitialized();
|
||||||
|
let hresult = (*self.render_client).GetBuffer(frames_available,
|
||||||
|
&mut buffer as *mut *mut _);
|
||||||
|
check_result(hresult).unwrap();
|
||||||
|
assert!(!buffer.is_null());
|
||||||
|
|
||||||
|
(buffer as *mut T,
|
||||||
|
frames_available as usize * self.bytes_per_frame as usize
|
||||||
|
/ mem::size_of::<T>())
|
||||||
|
};
|
||||||
|
|
||||||
|
let buffer = Buffer {
|
||||||
|
render_client: self.render_client,
|
||||||
|
buffer_data: buffer_data,
|
||||||
|
buffer_len: buffer_len,
|
||||||
|
frames: frames_available,
|
||||||
|
marker: PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play(&mut self) {
|
||||||
|
if !self.playing {
|
||||||
|
unsafe {
|
||||||
|
let hresult = (*self.audio_client).Start();
|
||||||
|
check_result(hresult).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.playing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pause(&mut self) {
|
||||||
|
if self.playing {
|
||||||
|
unsafe {
|
||||||
|
let hresult = (*self.audio_client).Stop();
|
||||||
|
check_result(hresult).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.playing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Voice {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
(*self.render_client).Release();
|
||||||
|
(*self.audio_client).Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Buffer<'a, T: 'a> {
|
||||||
|
render_client: *mut winapi::IAudioRenderClient,
|
||||||
|
buffer_data: *mut T,
|
||||||
|
buffer_len: usize,
|
||||||
|
frames: winapi::UINT32,
|
||||||
|
marker: PhantomData<&'a mut T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Buffer<'a, T> {
|
||||||
|
pub fn get_buffer<'b>(&'b mut self) -> &'b mut [T] {
|
||||||
|
unsafe {
|
||||||
|
slice::from_raw_parts_mut(self.buffer_data, self.buffer_len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self) {
|
||||||
|
// releasing buffer
|
||||||
|
unsafe {
|
||||||
|
let hresult = (*self.render_client).ReleaseBuffer(self.frames as u32, 0);
|
||||||
|
check_result(hresult).unwrap();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue