Move code from stream to device
This commit is contained in:
parent
dd418c08ae
commit
e25c44a4a1
|
@ -7,7 +7,7 @@ use std::ops::{Deref, DerefMut};
|
||||||
use std::os::windows::ffi::OsStringExt;
|
use std::os::windows::ffi::OsStringExt;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard, atomic::Ordering};
|
||||||
|
|
||||||
use BackendSpecificError;
|
use BackendSpecificError;
|
||||||
use DefaultFormatError;
|
use DefaultFormatError;
|
||||||
|
@ -33,6 +33,7 @@ use super::winapi::shared::guiddef::{
|
||||||
use super::winapi::shared::winerror;
|
use super::winapi::shared::winerror;
|
||||||
use super::winapi::shared::minwindef::{
|
use super::winapi::shared::minwindef::{
|
||||||
DWORD,
|
DWORD,
|
||||||
|
WORD,
|
||||||
};
|
};
|
||||||
use super::winapi::shared::mmreg;
|
use super::winapi::shared::mmreg;
|
||||||
use super::winapi::shared::wtypes;
|
use super::winapi::shared::wtypes;
|
||||||
|
@ -41,12 +42,14 @@ use super::winapi::um::winnt::LPWSTR;
|
||||||
use super::winapi::um::winnt::WCHAR;
|
use super::winapi::um::winnt::WCHAR;
|
||||||
use super::winapi::um::coml2api;
|
use super::winapi::um::coml2api;
|
||||||
use super::winapi::um::audioclient::{
|
use super::winapi::um::audioclient::{
|
||||||
|
self,
|
||||||
IAudioClient,
|
IAudioClient,
|
||||||
IID_IAudioClient,
|
IID_IAudioClient,
|
||||||
AUDCLNT_E_DEVICE_INVALIDATED,
|
AUDCLNT_E_DEVICE_INVALIDATED,
|
||||||
};
|
};
|
||||||
use super::winapi::um::audiosessiontypes::{
|
use super::winapi::um::audiosessiontypes::{
|
||||||
AUDCLNT_SHAREMODE_SHARED,
|
AUDCLNT_SHAREMODE_SHARED,
|
||||||
|
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||||
};
|
};
|
||||||
use super::winapi::um::combaseapi::{
|
use super::winapi::um::combaseapi::{
|
||||||
CoCreateInstance,
|
CoCreateInstance,
|
||||||
|
@ -68,7 +71,8 @@ use super::winapi::um::mmdeviceapi::{
|
||||||
IMMEndpoint,
|
IMMEndpoint,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::traits::DeviceTrait;
|
use crate::{traits::DeviceTrait, BuildStreamError, StreamData, StreamError};
|
||||||
|
use super::{stream::{Stream, AudioClientFlow, StreamInner, Command}, winapi::um::synchapi};
|
||||||
|
|
||||||
pub type SupportedInputFormats = std::vec::IntoIter<SupportedFormat>;
|
pub type SupportedInputFormats = std::vec::IntoIter<SupportedFormat>;
|
||||||
pub type SupportedOutputFormats = std::vec::IntoIter<SupportedFormat>;
|
pub type SupportedOutputFormats = std::vec::IntoIter<SupportedFormat>;
|
||||||
|
@ -112,6 +116,24 @@ impl DeviceTrait for Device {
|
||||||
fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
|
fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
|
||||||
Device::default_output_format(self)
|
Device::default_output_format(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_input_stream<D, E>(&self, format: &Format, data_callback: D, error_callback: E) -> Result<Self::Stream, BuildStreamError>
|
||||||
|
where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static {
|
||||||
|
Ok(Stream::new(
|
||||||
|
Arc::new(self.build_input_stream_inner(format)?),
|
||||||
|
data_callback,
|
||||||
|
error_callback,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_output_stream<D, E>(&self, format: &Format, data_callback: D, error_callback: E) -> Result<Self::Stream, BuildStreamError>
|
||||||
|
where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static {
|
||||||
|
Ok(Stream::new(
|
||||||
|
Arc::new(self.build_output_stream_inner(format)?),
|
||||||
|
data_callback,
|
||||||
|
error_callback,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Endpoint {
|
struct Endpoint {
|
||||||
|
@ -600,6 +622,327 @@ impl Device {
|
||||||
Err(DefaultFormatError::StreamTypeNotSupported)
|
Err(DefaultFormatError::StreamTypeNotSupported)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_input_stream_inner(
|
||||||
|
&self,
|
||||||
|
format: &Format,
|
||||||
|
) -> Result<Stream, BuildStreamError>
|
||||||
|
{
|
||||||
|
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 self.build_audioclient() {
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
|
||||||
|
return Err(BuildStreamError::DeviceNotAvailable),
|
||||||
|
Err(e) => {
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Computing the format and initializing the device.
|
||||||
|
let waveformatex = {
|
||||||
|
let format_attempt = format_to_waveformatextensible(format)
|
||||||
|
.ok_or(BuildStreamError::FormatNotSupported)?;
|
||||||
|
let share_mode = AUDCLNT_SHAREMODE_SHARED;
|
||||||
|
|
||||||
|
// Ensure the format is supported.
|
||||||
|
match super::device::is_format_supported(audio_client, &format_attempt.Format) {
|
||||||
|
Ok(false) => return Err(BuildStreamError::FormatNotSupported),
|
||||||
|
Err(_) => return Err(BuildStreamError::DeviceNotAvailable),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally initializing the audio client
|
||||||
|
let hresult = (*audio_client).Initialize(
|
||||||
|
share_mode,
|
||||||
|
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&format_attempt.Format,
|
||||||
|
ptr::null(),
|
||||||
|
);
|
||||||
|
match check_result(hresult) {
|
||||||
|
Err(ref e)
|
||||||
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
},
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
format_attempt.Format
|
||||||
|
};
|
||||||
|
|
||||||
|
// obtaining the size of the samples buffer in number of frames
|
||||||
|
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(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
},
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
max_frames_in_buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
// Creating the event that will be signalled whenever we need to submit some samples.
|
||||||
|
let event = {
|
||||||
|
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
|
||||||
|
if event == ptr::null_mut() {
|
||||||
|
(*audio_client).Release();
|
||||||
|
let description = format!("failed to create event");
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = check_result((*audio_client).SetEventHandle(event)) {
|
||||||
|
(*audio_client).Release();
|
||||||
|
let description = format!("failed to call SetEventHandle: {}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
event
|
||||||
|
};
|
||||||
|
|
||||||
|
// Building a `IAudioCaptureClient` that will be used to read captured samples.
|
||||||
|
let capture_client = {
|
||||||
|
let mut capture_client: *mut audioclient::IAudioCaptureClient = mem::uninitialized();
|
||||||
|
let hresult = (*audio_client).GetService(
|
||||||
|
&audioclient::IID_IAudioCaptureClient,
|
||||||
|
&mut capture_client as *mut *mut audioclient::IAudioCaptureClient as *mut _,
|
||||||
|
);
|
||||||
|
|
||||||
|
match check_result(hresult) {
|
||||||
|
Err(ref e)
|
||||||
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
let description = format!("failed to build capture client: {}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
},
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
&mut *capture_client
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
||||||
|
if new_stream_id.0 == usize::max_value() {
|
||||||
|
return Err(BuildStreamError::StreamIdOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once we built the `StreamInner`, we add a command that will be picked up by the
|
||||||
|
// `run()` method and added to the `RunContext`.
|
||||||
|
{
|
||||||
|
let client_flow = AudioClientFlow::Capture {
|
||||||
|
capture_client: capture_client,
|
||||||
|
};
|
||||||
|
let inner = StreamInner {
|
||||||
|
id: new_stream_id.clone(),
|
||||||
|
audio_client: audio_client,
|
||||||
|
client_flow: client_flow,
|
||||||
|
event: event,
|
||||||
|
playing: false,
|
||||||
|
max_frames_in_buffer: max_frames_in_buffer,
|
||||||
|
bytes_per_frame: waveformatex.nBlockAlign,
|
||||||
|
sample_format: format.data_type,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.push_command(Command::NewStream(inner));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(new_stream_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_output_stream_inner(
|
||||||
|
&self,
|
||||||
|
format: &Format,
|
||||||
|
) -> Result<Stream, BuildStreamError>
|
||||||
|
{
|
||||||
|
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 self.build_audioclient() {
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
|
||||||
|
return Err(BuildStreamError::DeviceNotAvailable),
|
||||||
|
Err(e) => {
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Computing the format and initializing the device.
|
||||||
|
let waveformatex = {
|
||||||
|
let format_attempt = format_to_waveformatextensible(format)
|
||||||
|
.ok_or(BuildStreamError::FormatNotSupported)?;
|
||||||
|
let share_mode = AUDCLNT_SHAREMODE_SHARED;
|
||||||
|
|
||||||
|
// Ensure the format is supported.
|
||||||
|
match super::device::is_format_supported(audio_client, &format_attempt.Format) {
|
||||||
|
Ok(false) => return Err(BuildStreamError::FormatNotSupported),
|
||||||
|
Err(_) => return Err(BuildStreamError::DeviceNotAvailable),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally initializing the audio client
|
||||||
|
let hresult = (*audio_client).Initialize(share_mode,
|
||||||
|
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&format_attempt.Format,
|
||||||
|
ptr::null());
|
||||||
|
match check_result(hresult) {
|
||||||
|
Err(ref e)
|
||||||
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
},
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
format_attempt.Format
|
||||||
|
};
|
||||||
|
|
||||||
|
// Creating the event that will be signalled whenever we need to submit some samples.
|
||||||
|
let event = {
|
||||||
|
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
|
||||||
|
if event == ptr::null_mut() {
|
||||||
|
(*audio_client).Release();
|
||||||
|
let description = format!("failed to create event");
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
match check_result((*audio_client).SetEventHandle(event)) {
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
let description = format!("failed to call SetEventHandle: {}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
},
|
||||||
|
Ok(_) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
event
|
||||||
|
};
|
||||||
|
|
||||||
|
// obtaining the size of the samples buffer in number of frames
|
||||||
|
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(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
let description = format!("failed to obtain buffer size: {}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
},
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
max_frames_in_buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
// Building a `IAudioRenderClient` that will be used to fill the samples buffer.
|
||||||
|
let render_client = {
|
||||||
|
let mut render_client: *mut audioclient::IAudioRenderClient = mem::uninitialized();
|
||||||
|
let hresult = (*audio_client).GetService(&audioclient::IID_IAudioRenderClient,
|
||||||
|
&mut render_client as
|
||||||
|
*mut *mut audioclient::IAudioRenderClient as
|
||||||
|
*mut _);
|
||||||
|
|
||||||
|
match check_result(hresult) {
|
||||||
|
Err(ref e)
|
||||||
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
(*audio_client).Release();
|
||||||
|
let description = format!("failed to build render client: {}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
},
|
||||||
|
Ok(()) => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
&mut *render_client
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
||||||
|
if new_stream_id.0 == usize::max_value() {
|
||||||
|
return Err(BuildStreamError::StreamIdOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once we built the `StreamInner`, we add a command that will be picked up by the
|
||||||
|
// `run()` method and added to the `RunContext`.
|
||||||
|
{
|
||||||
|
let client_flow = AudioClientFlow::Render {
|
||||||
|
render_client: render_client,
|
||||||
|
};
|
||||||
|
let inner = StreamInner {
|
||||||
|
id: new_stream_id.clone(),
|
||||||
|
audio_client: audio_client,
|
||||||
|
client_flow: client_flow,
|
||||||
|
event: event,
|
||||||
|
playing: false,
|
||||||
|
max_frames_in_buffer: max_frames_in_buffer,
|
||||||
|
bytes_per_frame: waveformatex.nBlockAlign,
|
||||||
|
sample_format: format.data_type,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.push_command(Command::NewStream(inner));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(new_stream_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Device {
|
impl PartialEq for Device {
|
||||||
|
@ -842,3 +1185,58 @@ pub fn default_input_device() -> Option<Device> {
|
||||||
pub fn default_output_device() -> Option<Device> {
|
pub fn default_output_device() -> Option<Device> {
|
||||||
default_device(eRender)
|
default_device(eRender)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Turns a `Format` into a `WAVEFORMATEXTENSIBLE`.
|
||||||
|
//
|
||||||
|
// Returns `None` if the WAVEFORMATEXTENSIBLE does not support the given format.
|
||||||
|
fn format_to_waveformatextensible(format: &Format) -> Option<mmreg::WAVEFORMATEXTENSIBLE> {
|
||||||
|
let format_tag = match format.data_type {
|
||||||
|
SampleFormat::I16 => mmreg::WAVE_FORMAT_PCM,
|
||||||
|
SampleFormat::F32 => mmreg::WAVE_FORMAT_EXTENSIBLE,
|
||||||
|
SampleFormat::U16 => return None,
|
||||||
|
};
|
||||||
|
let channels = format.channels as WORD;
|
||||||
|
let sample_rate = format.sample_rate.0 as DWORD;
|
||||||
|
let sample_bytes = format.data_type.sample_size() as WORD;
|
||||||
|
let avg_bytes_per_sec = channels as DWORD * sample_rate * sample_bytes as DWORD;
|
||||||
|
let block_align = channels * sample_bytes;
|
||||||
|
let bits_per_sample = 8 * sample_bytes;
|
||||||
|
let cb_size = match format.data_type {
|
||||||
|
SampleFormat::I16 => 0,
|
||||||
|
SampleFormat::F32 => {
|
||||||
|
let extensible_size = mem::size_of::<mmreg::WAVEFORMATEXTENSIBLE>();
|
||||||
|
let ex_size = mem::size_of::<mmreg::WAVEFORMATEX>();
|
||||||
|
(extensible_size - ex_size) as WORD
|
||||||
|
},
|
||||||
|
SampleFormat::U16 => return None,
|
||||||
|
};
|
||||||
|
let waveformatex = mmreg::WAVEFORMATEX {
|
||||||
|
wFormatTag: format_tag,
|
||||||
|
nChannels: channels,
|
||||||
|
nSamplesPerSec: sample_rate,
|
||||||
|
nAvgBytesPerSec: avg_bytes_per_sec,
|
||||||
|
nBlockAlign: block_align,
|
||||||
|
wBitsPerSample: bits_per_sample,
|
||||||
|
cbSize: cb_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
// CPAL does not care about speaker positions, so pass audio straight through.
|
||||||
|
// TODO: This constant should be defined in winapi but is missing.
|
||||||
|
const KSAUDIO_SPEAKER_DIRECTOUT: DWORD = 0;
|
||||||
|
let channel_mask = KSAUDIO_SPEAKER_DIRECTOUT;
|
||||||
|
|
||||||
|
let sub_format = match format.data_type {
|
||||||
|
SampleFormat::I16 => ksmedia::KSDATAFORMAT_SUBTYPE_PCM,
|
||||||
|
SampleFormat::F32 => ksmedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
|
||||||
|
SampleFormat::U16 => return None,
|
||||||
|
};
|
||||||
|
let waveformatextensible = mmreg::WAVEFORMATEXTENSIBLE {
|
||||||
|
Format: waveformatex,
|
||||||
|
Samples: bits_per_sample as WORD,
|
||||||
|
dwChannelMask: channel_mask,
|
||||||
|
SubFormat: sub_format,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(waveformatextensible)
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use super::Device;
|
|
||||||
use super::check_result;
|
use super::check_result;
|
||||||
use super::com;
|
use super::com;
|
||||||
use super::winapi::shared::basetsd::UINT32;
|
use super::winapi::shared::basetsd::UINT32;
|
||||||
|
@ -6,8 +5,6 @@ use super::winapi::shared::ksmedia;
|
||||||
use super::winapi::shared::minwindef::{BYTE, DWORD, FALSE, WORD};
|
use super::winapi::shared::minwindef::{BYTE, DWORD, FALSE, WORD};
|
||||||
use super::winapi::shared::mmreg;
|
use super::winapi::shared::mmreg;
|
||||||
use super::winapi::um::audioclient::{self, AUDCLNT_E_DEVICE_INVALIDATED, AUDCLNT_S_BUFFER_EMPTY};
|
use super::winapi::um::audioclient::{self, AUDCLNT_E_DEVICE_INVALIDATED, AUDCLNT_S_BUFFER_EMPTY};
|
||||||
use super::winapi::um::audiosessiontypes::{AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, AUDCLNT_STREAMFLAGS_LOOPBACK};
|
|
||||||
use super::winapi::um::mmdeviceapi::eRender;
|
|
||||||
use super::winapi::um::handleapi;
|
use super::winapi::um::handleapi;
|
||||||
use super::winapi::um::synchapi;
|
use super::winapi::um::synchapi;
|
||||||
use super::winapi::um::winbase;
|
use super::winapi::um::winbase;
|
||||||
|
@ -66,12 +63,12 @@ struct RunContext {
|
||||||
commands: Receiver<Command>,
|
commands: Receiver<Command>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Command {
|
pub (crate) enum Command {
|
||||||
PlayStream,
|
PlayStream,
|
||||||
PauseStream,
|
PauseStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AudioClientFlow {
|
pub (crate) enum AudioClientFlow {
|
||||||
Render {
|
Render {
|
||||||
render_client: *mut audioclient::IAudioRenderClient,
|
render_client: *mut audioclient::IAudioRenderClient,
|
||||||
},
|
},
|
||||||
|
@ -80,7 +77,7 @@ enum AudioClientFlow {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StreamInner {
|
pub (crate) struct StreamInner {
|
||||||
id: StreamId,
|
id: StreamId,
|
||||||
audio_client: *mut audioclient::IAudioClient,
|
audio_client: *mut audioclient::IAudioClient,
|
||||||
client_flow: AudioClientFlow,
|
client_flow: AudioClientFlow,
|
||||||
|
@ -115,335 +112,6 @@ impl EventLoop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build_input_stream(
|
|
||||||
&self,
|
|
||||||
device: &Device,
|
|
||||||
format: &Format,
|
|
||||||
) -> Result<StreamId, BuildStreamError>
|
|
||||||
{
|
|
||||||
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 device.build_audioclient() {
|
|
||||||
Ok(client) => client,
|
|
||||||
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
|
|
||||||
return Err(BuildStreamError::DeviceNotAvailable),
|
|
||||||
Err(e) => {
|
|
||||||
let description = format!("{}", e);
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Computing the format and initializing the device.
|
|
||||||
let waveformatex = {
|
|
||||||
let format_attempt = format_to_waveformatextensible(format)
|
|
||||||
.ok_or(BuildStreamError::FormatNotSupported)?;
|
|
||||||
let share_mode = AUDCLNT_SHAREMODE_SHARED;
|
|
||||||
|
|
||||||
// Ensure the format is supported.
|
|
||||||
match super::device::is_format_supported(audio_client, &format_attempt.Format) {
|
|
||||||
Ok(false) => return Err(BuildStreamError::FormatNotSupported),
|
|
||||||
Err(_) => return Err(BuildStreamError::DeviceNotAvailable),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support capturing output devices.
|
|
||||||
let mut stream_flags: DWORD = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
|
|
||||||
if device.data_flow() == eRender {
|
|
||||||
stream_flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally initializing the audio client
|
|
||||||
let hresult = (*audio_client).Initialize(
|
|
||||||
share_mode,
|
|
||||||
stream_flags,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
&format_attempt.Format,
|
|
||||||
ptr::null(),
|
|
||||||
);
|
|
||||||
match check_result(hresult) {
|
|
||||||
Err(ref e)
|
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
return Err(BuildStreamError::DeviceNotAvailable);
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
let description = format!("{}", e);
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
},
|
|
||||||
Ok(()) => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
format_attempt.Format
|
|
||||||
};
|
|
||||||
|
|
||||||
// obtaining the size of the samples buffer in number of frames
|
|
||||||
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(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
return Err(BuildStreamError::DeviceNotAvailable);
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
let description = format!("{}", e);
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
},
|
|
||||||
Ok(()) => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
max_frames_in_buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
// Creating the event that will be signalled whenever we need to submit some samples.
|
|
||||||
let event = {
|
|
||||||
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
|
|
||||||
if event == ptr::null_mut() {
|
|
||||||
(*audio_client).Release();
|
|
||||||
let description = format!("failed to create event");
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = check_result((*audio_client).SetEventHandle(event)) {
|
|
||||||
(*audio_client).Release();
|
|
||||||
let description = format!("failed to call SetEventHandle: {}", e);
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
event
|
|
||||||
};
|
|
||||||
|
|
||||||
// Building a `IAudioCaptureClient` that will be used to read captured samples.
|
|
||||||
let capture_client = {
|
|
||||||
let mut capture_client: *mut audioclient::IAudioCaptureClient = mem::uninitialized();
|
|
||||||
let hresult = (*audio_client).GetService(
|
|
||||||
&audioclient::IID_IAudioCaptureClient,
|
|
||||||
&mut capture_client as *mut *mut audioclient::IAudioCaptureClient as *mut _,
|
|
||||||
);
|
|
||||||
|
|
||||||
match check_result(hresult) {
|
|
||||||
Err(ref e)
|
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
return Err(BuildStreamError::DeviceNotAvailable);
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
let description = format!("failed to build capture client: {}", e);
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
},
|
|
||||||
Ok(()) => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
&mut *capture_client
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
|
||||||
if new_stream_id.0 == usize::max_value() {
|
|
||||||
return Err(BuildStreamError::StreamIdOverflow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once we built the `StreamInner`, we add a command that will be picked up by the
|
|
||||||
// `run()` method and added to the `RunContext`.
|
|
||||||
{
|
|
||||||
let client_flow = AudioClientFlow::Capture {
|
|
||||||
capture_client: capture_client,
|
|
||||||
};
|
|
||||||
let inner = StreamInner {
|
|
||||||
id: new_stream_id.clone(),
|
|
||||||
audio_client: audio_client,
|
|
||||||
client_flow: client_flow,
|
|
||||||
event: event,
|
|
||||||
playing: false,
|
|
||||||
max_frames_in_buffer: max_frames_in_buffer,
|
|
||||||
bytes_per_frame: waveformatex.nBlockAlign,
|
|
||||||
sample_format: format.data_type,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.push_command(Command::NewStream(inner));
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(new_stream_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn build_output_stream(
|
|
||||||
&self,
|
|
||||||
device: &Device,
|
|
||||||
format: &Format,
|
|
||||||
) -> Result<StreamId, BuildStreamError>
|
|
||||||
{
|
|
||||||
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 device.build_audioclient() {
|
|
||||||
Ok(client) => client,
|
|
||||||
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
|
|
||||||
return Err(BuildStreamError::DeviceNotAvailable),
|
|
||||||
Err(e) => {
|
|
||||||
let description = format!("{}", e);
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Computing the format and initializing the device.
|
|
||||||
let waveformatex = {
|
|
||||||
let format_attempt = format_to_waveformatextensible(format)
|
|
||||||
.ok_or(BuildStreamError::FormatNotSupported)?;
|
|
||||||
let share_mode = AUDCLNT_SHAREMODE_SHARED;
|
|
||||||
|
|
||||||
// Ensure the format is supported.
|
|
||||||
match super::device::is_format_supported(audio_client, &format_attempt.Format) {
|
|
||||||
Ok(false) => return Err(BuildStreamError::FormatNotSupported),
|
|
||||||
Err(_) => return Err(BuildStreamError::DeviceNotAvailable),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally initializing the audio client
|
|
||||||
let hresult = (*audio_client).Initialize(share_mode,
|
|
||||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
&format_attempt.Format,
|
|
||||||
ptr::null());
|
|
||||||
match check_result(hresult) {
|
|
||||||
Err(ref e)
|
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
return Err(BuildStreamError::DeviceNotAvailable);
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
let description = format!("{}", e);
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
},
|
|
||||||
Ok(()) => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
format_attempt.Format
|
|
||||||
};
|
|
||||||
|
|
||||||
// Creating the event that will be signalled whenever we need to submit some samples.
|
|
||||||
let event = {
|
|
||||||
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
|
|
||||||
if event == ptr::null_mut() {
|
|
||||||
(*audio_client).Release();
|
|
||||||
let description = format!("failed to create event");
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
match check_result((*audio_client).SetEventHandle(event)) {
|
|
||||||
Err(e) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
let description = format!("failed to call SetEventHandle: {}", e);
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
},
|
|
||||||
Ok(_) => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
event
|
|
||||||
};
|
|
||||||
|
|
||||||
// obtaining the size of the samples buffer in number of frames
|
|
||||||
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(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
return Err(BuildStreamError::DeviceNotAvailable);
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
let description = format!("failed to obtain buffer size: {}", e);
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
},
|
|
||||||
Ok(()) => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
max_frames_in_buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
// Building a `IAudioRenderClient` that will be used to fill the samples buffer.
|
|
||||||
let render_client = {
|
|
||||||
let mut render_client: *mut audioclient::IAudioRenderClient = mem::uninitialized();
|
|
||||||
let hresult = (*audio_client).GetService(&audioclient::IID_IAudioRenderClient,
|
|
||||||
&mut render_client as
|
|
||||||
*mut *mut audioclient::IAudioRenderClient as
|
|
||||||
*mut _);
|
|
||||||
|
|
||||||
match check_result(hresult) {
|
|
||||||
Err(ref e)
|
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
return Err(BuildStreamError::DeviceNotAvailable);
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
(*audio_client).Release();
|
|
||||||
let description = format!("failed to build render client: {}", e);
|
|
||||||
let err = BackendSpecificError { description };
|
|
||||||
return Err(err.into());
|
|
||||||
},
|
|
||||||
Ok(()) => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
&mut *render_client
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
|
||||||
if new_stream_id.0 == usize::max_value() {
|
|
||||||
return Err(BuildStreamError::StreamIdOverflow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once we built the `StreamInner`, we add a command that will be picked up by the
|
|
||||||
// `run()` method and added to the `RunContext`.
|
|
||||||
{
|
|
||||||
let client_flow = AudioClientFlow::Render {
|
|
||||||
render_client: render_client,
|
|
||||||
};
|
|
||||||
let inner = StreamInner {
|
|
||||||
id: new_stream_id.clone(),
|
|
||||||
audio_client: audio_client,
|
|
||||||
client_flow: client_flow,
|
|
||||||
event: event,
|
|
||||||
playing: false,
|
|
||||||
max_frames_in_buffer: max_frames_in_buffer,
|
|
||||||
bytes_per_frame: waveformatex.nBlockAlign,
|
|
||||||
sample_format: format.data_type,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.push_command(Command::NewStream(inner));
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(new_stream_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn destroy_stream(&self, stream_id: StreamId) {
|
pub(crate) fn destroy_stream(&self, stream_id: StreamId) {
|
||||||
self.push_command(Command::DestroyStream(stream_id));
|
self.push_command(Command::DestroyStream(stream_id));
|
||||||
|
@ -518,60 +186,6 @@ impl Drop for StreamInner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turns a `Format` into a `WAVEFORMATEXTENSIBLE`.
|
|
||||||
//
|
|
||||||
// Returns `None` if the WAVEFORMATEXTENSIBLE does not support the given format.
|
|
||||||
fn format_to_waveformatextensible(format: &Format) -> Option<mmreg::WAVEFORMATEXTENSIBLE> {
|
|
||||||
let format_tag = match format.data_type {
|
|
||||||
SampleFormat::I16 => mmreg::WAVE_FORMAT_PCM,
|
|
||||||
SampleFormat::F32 => mmreg::WAVE_FORMAT_EXTENSIBLE,
|
|
||||||
SampleFormat::U16 => return None,
|
|
||||||
};
|
|
||||||
let channels = format.channels as WORD;
|
|
||||||
let sample_rate = format.sample_rate.0 as DWORD;
|
|
||||||
let sample_bytes = format.data_type.sample_size() as WORD;
|
|
||||||
let avg_bytes_per_sec = channels as DWORD * sample_rate * sample_bytes as DWORD;
|
|
||||||
let block_align = channels * sample_bytes;
|
|
||||||
let bits_per_sample = 8 * sample_bytes;
|
|
||||||
let cb_size = match format.data_type {
|
|
||||||
SampleFormat::I16 => 0,
|
|
||||||
SampleFormat::F32 => {
|
|
||||||
let extensible_size = mem::size_of::<mmreg::WAVEFORMATEXTENSIBLE>();
|
|
||||||
let ex_size = mem::size_of::<mmreg::WAVEFORMATEX>();
|
|
||||||
(extensible_size - ex_size) as WORD
|
|
||||||
},
|
|
||||||
SampleFormat::U16 => return None,
|
|
||||||
};
|
|
||||||
let waveformatex = mmreg::WAVEFORMATEX {
|
|
||||||
wFormatTag: format_tag,
|
|
||||||
nChannels: channels,
|
|
||||||
nSamplesPerSec: sample_rate,
|
|
||||||
nAvgBytesPerSec: avg_bytes_per_sec,
|
|
||||||
nBlockAlign: block_align,
|
|
||||||
wBitsPerSample: bits_per_sample,
|
|
||||||
cbSize: cb_size,
|
|
||||||
};
|
|
||||||
|
|
||||||
// CPAL does not care about speaker positions, so pass audio straight through.
|
|
||||||
// TODO: This constant should be defined in winapi but is missing.
|
|
||||||
const KSAUDIO_SPEAKER_DIRECTOUT: DWORD = 0;
|
|
||||||
let channel_mask = KSAUDIO_SPEAKER_DIRECTOUT;
|
|
||||||
|
|
||||||
let sub_format = match format.data_type {
|
|
||||||
SampleFormat::I16 => ksmedia::KSDATAFORMAT_SUBTYPE_PCM,
|
|
||||||
SampleFormat::F32 => ksmedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
|
|
||||||
SampleFormat::U16 => return None,
|
|
||||||
};
|
|
||||||
let waveformatextensible = mmreg::WAVEFORMATEXTENSIBLE {
|
|
||||||
Format: waveformatex,
|
|
||||||
Samples: bits_per_sample as WORD,
|
|
||||||
dwChannelMask: channel_mask,
|
|
||||||
SubFormat: sub_format,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(waveformatextensible)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process any pending commands that are queued within the `RunContext`.
|
// Process any pending commands that are queued within the `RunContext`.
|
||||||
fn process_commands(run_context: &mut RunContext) -> Result<(), StreamError> {
|
fn process_commands(run_context: &mut RunContext) -> Result<(), StreamError> {
|
||||||
// Process the pending commands.
|
// Process the pending commands.
|
||||||
|
|
Loading…
Reference in New Issue