Add supported formats enumeration

This commit is contained in:
Pierre Krieger 2015-09-01 13:53:54 +02:00
parent 47f966bf75
commit 1985c346ac
5 changed files with 185 additions and 44 deletions

View File

@ -1,7 +1,9 @@
extern crate cpal;
fn main() {
let mut channel = cpal::Voice::new(&cpal::get_default_endpoint().unwrap()).unwrap();
let endpoint = cpal::get_default_endpoint().unwrap();
let format = endpoint.get_supported_formats_list().next().unwrap();
let mut channel = cpal::Voice::new(&endpoint, &format).unwrap();
// Produce a sinusoid of maximum amplitude.
let mut data_source = (0u64..).map(|t| t as f32 * 0.03)

View File

@ -54,31 +54,18 @@ mod cpal_impl;
#[path="null/mod.rs"]
mod cpal_impl;
/// Number of channels.
pub type ChannelsCount = u16;
///
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
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 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()
}
@ -98,6 +85,45 @@ pub fn get_default_endpoint() -> Option<Endpoint> {
#[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) -> SupportedFormatsIterator {
SupportedFormatsIterator(self.0.get_supported_formats_list())
}
}
/// Number of channels.
pub type ChannelsCount = u16;
///
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
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.
///
/// You should destroy this object as soon as possible. Data is only committed when it
@ -136,8 +162,9 @@ pub struct Voice(cpal_impl::Voice);
impl Voice {
/// Builds a new channel.
pub fn new(endpoint: &Endpoint) -> Result<Voice, Box<Error>> {
let channel = try!(cpal_impl::Voice::new(&endpoint.0));
#[inline]
pub fn new(endpoint: &Endpoint, format: &Format) -> Result<Voice, Box<Error>> {
let channel = try!(cpal_impl::Voice::new(&endpoint.0, format));
Ok(Voice(channel))
}
@ -145,6 +172,7 @@ impl Voice {
///
/// You can add data with any number of channels, but matching the voice's native format
/// will lead to better performances.
#[inline]
pub fn get_channels(&self) -> ChannelsCount {
self.0.get_channels()
}
@ -153,6 +181,7 @@ impl Voice {
///
/// You can add data with any samples rate, but matching the voice's native format
/// will lead to better performances.
#[inline]
pub fn get_samples_rate(&self) -> SamplesRate {
self.0.get_samples_rate()
}
@ -161,6 +190,7 @@ impl Voice {
///
/// You can add data of any format, but matching the voice's native format
/// will lead to better performances.
#[inline]
pub fn get_samples_format(&self) -> SampleFormat {
self.0.get_samples_format()
}
@ -205,6 +235,7 @@ impl Voice {
///
/// Only call this after you have submitted some data, otherwise you may hear
/// some glitches.
#[inline]
pub fn play(&mut self) {
self.0.play()
}
@ -214,6 +245,7 @@ impl Voice {
/// Has no effect is the voice was already paused.
///
/// If you call `play` afterwards, the playback will resume exactly where it was.
#[inline]
pub fn pause(&mut self) {
self.0.pause()
}
@ -222,18 +254,21 @@ impl Voice {
impl<'a, T> Deref for Buffer<'a, T> where T: Sample {
type Target = [T];
#[inline]
fn deref(&self) -> &[T] {
panic!("It is forbidden to read from the audio buffer");
}
}
impl<'a, T> DerefMut for Buffer<'a, T> where T: Sample {
#[inline]
fn deref_mut(&mut self) -> &mut [T] {
self.target.as_mut().unwrap().get_buffer()
}
}
impl<'a, T> Drop for Buffer<'a, T> where T: Sample {
#[inline]
fn drop(&mut self) {
self.target.take().unwrap().finish();
}

View File

@ -99,7 +99,7 @@ impl Iterator for EndpointsIterator {
check_result((*self.collection).Item(self.next_item, &mut device)).unwrap();
self.next_item += 1;
Some(Endpoint(device))
Some(Endpoint::from_immdevice(device))
}
}
@ -121,6 +121,6 @@ pub fn get_default_endpoint() -> Option<Endpoint> {
return None; // TODO: check specifically for `E_NOTFOUND`, and panic otherwise
}
Some(Endpoint(device))
Some(Endpoint::from_immdevice(device))
}
}

View File

@ -3,35 +3,24 @@ extern crate winapi;
extern crate ole32;
use std::io::Error as IoError;
use std::sync::{Arc, Mutex, MutexGuard};
use std::ptr;
use std::mem;
use Format;
use SamplesRate;
use SampleFormat;
pub use std::option::IntoIter as OptionIntoIter;
pub use self::enumerate::{EndpointsIterator, get_default_endpoint};
pub use self::voice::{Voice, Buffer};
pub type SupportedFormatsIterator = OptionIntoIter<Format>;
mod com;
mod enumerate;
mod voice;
/// An opaque type that identifies an end point.
#[derive(PartialEq, Eq)]
#[allow(raw_pointer_derive)]
pub struct Endpoint(*mut winapi::IMMDevice);
unsafe impl Send for Endpoint {}
unsafe impl Sync for Endpoint {}
impl Clone for Endpoint {
fn clone(&self) -> Endpoint {
unsafe { (*self.0).AddRef(); }
Endpoint(self.0)
}
}
impl Drop for Endpoint {
fn drop(&mut self) {
unsafe { (*self.0).Release(); }
}
}
fn check_result(result: winapi::HRESULT) -> Result<(), IoError> {
if result < 0 {
Err(IoError::from_raw_os_error(result))
@ -39,3 +28,116 @@ fn check_result(result: winapi::HRESULT) -> Result<(), IoError> {
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)),
}
}
/// Ensures that `future_audio_client` contains a `Some` and returns a locked mutex to it.
fn ensure_future_audio_client(&self) -> MutexGuard<Option<IAudioClientWrapper>> {
let mut lock = self.future_audio_client.lock().unwrap();
if lock.is_some() {
return 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
check_result(hresult).unwrap(); // FIXME: don't unwrap
audio_client as *mut _
};
*lock = Some(IAudioClientWrapper(audio_client));
lock
}
pub fn get_supported_formats_list(&self) -> SupportedFormatsIterator {
// 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 = self.ensure_future_audio_client();
let client = lock.unwrap().0;
unsafe {
let mut format_ptr = mem::uninitialized();
check_result((*client).GetMixFormat(&mut format_ptr)).unwrap(); // FIXME: don't unwrap
let format = {
assert!((*format_ptr).wFormatTag == winapi::WAVE_FORMAT_EXTENSIBLE);
// FIXME: decode from the format
Format {
channels: 2,
samples_rate: SamplesRate(44100),
data_type: SampleFormat::U16,
}
};
ole32::CoTaskMemFree(format_ptr as *mut _);
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(); }
}
}
}

View File

@ -11,6 +11,8 @@ use std::mem;
use std::ptr;
use std::marker::PhantomData;
use Format;
pub struct Voice {
audio_client: *mut winapi::IAudioClient,
render_client: *mut winapi::IAudioRenderClient,
@ -26,7 +28,7 @@ unsafe impl Send for Voice {}
unsafe impl Sync for Voice {}
impl Voice {
pub fn new(end_point: &Endpoint) -> Result<Voice, IoError> {
pub fn new(end_point: &Endpoint, format: &Format) -> Result<Voice, IoError> {
// FIXME: release everything
unsafe {
// making sure that COM is initialized
@ -36,7 +38,7 @@ impl Voice {
// activating the end point in order to get a `IAudioClient`
let audio_client: *mut winapi::IAudioClient = {
let mut audio_client = mem::uninitialized();
let hresult = (*end_point.0).Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL,
let hresult = (*end_point.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