Make setup_stream become a member function
This commit is contained in:
Родитель
f5340794c5
Коммит
2ed6d7b70e
|
@ -1491,166 +1491,6 @@ extern fn buffer_size_changed_callback(in_client_data: *mut c_void,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn audiounit_setup_stream(stm: &mut AudioUnitStream) -> Result<()>
|
|
||||||
{
|
|
||||||
// TODO: Add stm.context.mutex.assert_current_thread_owns() ?
|
|
||||||
// audiounit_active_streams will require to own the mutex in
|
|
||||||
// stm.context.
|
|
||||||
stm.mutex.assert_current_thread_owns();
|
|
||||||
|
|
||||||
if stm.input_stream_params.prefs().contains(StreamPrefs::LOOPBACK) ||
|
|
||||||
stm.output_stream_params.prefs().contains(StreamPrefs::LOOPBACK) {
|
|
||||||
cubeb_log!("({:p}) Loopback not supported for audiounit.", stm as *const AudioUnitStream);
|
|
||||||
return Err(Error::not_supported());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut in_dev_info = stm.input_device.clone();
|
|
||||||
let mut out_dev_info = stm.output_device.clone();
|
|
||||||
|
|
||||||
if stm.has_input() && stm.has_output() &&
|
|
||||||
stm.input_device.id != stm.output_device.id {
|
|
||||||
if stm.create_aggregate_device().is_err() {
|
|
||||||
stm.aggregate_device_id = kAudioObjectUnknown;
|
|
||||||
cubeb_log!("({:p}) Create aggregate devices failed.", stm as *const AudioUnitStream);
|
|
||||||
// !!!NOTE: It is not necessary to return here. If it does not
|
|
||||||
// return it will fallback to the old implementation. The intention
|
|
||||||
// is to investigate how often it fails. I plan to remove
|
|
||||||
// it after a couple of weeks.
|
|
||||||
} else {
|
|
||||||
in_dev_info.id = stm.aggregate_device_id;
|
|
||||||
out_dev_info.id = stm.aggregate_device_id;
|
|
||||||
in_dev_info.flags = device_flags::DEV_INPUT;
|
|
||||||
out_dev_info.flags = device_flags::DEV_OUTPUT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stm.has_input() {
|
|
||||||
if let Err(r) = audiounit_create_unit(&mut stm.input_unit, &in_dev_info) {
|
|
||||||
cubeb_log!("({:p}) AudioUnit creation for input failed.", stm as *const AudioUnitStream);
|
|
||||||
return Err(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stm.has_output() {
|
|
||||||
if let Err(r) = audiounit_create_unit(&mut stm.output_unit, &out_dev_info) {
|
|
||||||
cubeb_log!("({:p}) AudioUnit creation for output failed.", stm as *const AudioUnitStream);
|
|
||||||
return Err(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Latency cannot change if another stream is operating in parallel. In this case
|
|
||||||
* latecy is set to the other stream value. */
|
|
||||||
if stm.context.active_streams() > 1 {
|
|
||||||
cubeb_log!("({:p}) More than one active stream, use global latency.", stm as *const AudioUnitStream);
|
|
||||||
stm.latency_frames = stm.context.global_latency_frames;
|
|
||||||
} else {
|
|
||||||
/* Silently clamp the latency down to the platform default, because we
|
|
||||||
* synthetize the clock from the callbacks, and we want the clock to update
|
|
||||||
* often. */
|
|
||||||
// Create a `latency_frames` here to avoid the borrowing issue.
|
|
||||||
let latency_frames = stm.latency_frames;
|
|
||||||
stm.latency_frames = stm.clamp_latency(latency_frames);
|
|
||||||
assert!(stm.latency_frames > 0); // Ugly error check
|
|
||||||
stm.context.set_global_latency(stm.latency_frames);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Configure I/O stream */
|
|
||||||
if stm.has_input() {
|
|
||||||
if let Err(r) = stm.configure_input() {
|
|
||||||
cubeb_log!("({:p}) Configure audiounit input failed.", stm as *const AudioUnitStream);
|
|
||||||
return Err(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stm.has_output() {
|
|
||||||
if let Err(r) = stm.configure_output() {
|
|
||||||
cubeb_log!("({:p}) Configure audiounit output failed.", stm as *const AudioUnitStream);
|
|
||||||
return Err(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We use a resampler because input AudioUnit operates
|
|
||||||
* reliable only in the capture device sample rate.
|
|
||||||
* Resampler will convert it to the user sample rate
|
|
||||||
* and deliver it to the callback. */
|
|
||||||
let target_sample_rate = if stm.has_input() {
|
|
||||||
stm.input_stream_params.rate()
|
|
||||||
} else {
|
|
||||||
assert!(stm.has_output());
|
|
||||||
stm.output_stream_params.rate()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut input_unconverted_params: ffi::cubeb_stream_params = unsafe { ::std::mem::zeroed() };
|
|
||||||
if stm.has_input() {
|
|
||||||
input_unconverted_params = unsafe { (*(stm.input_stream_params.as_ptr())) }; // Perform copy.
|
|
||||||
input_unconverted_params.rate = stm.input_hw_rate as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
let stm_ptr = stm as *mut AudioUnitStream as *mut ffi::cubeb_stream;
|
|
||||||
let stm_has_input = stm.has_input();
|
|
||||||
let stm_has_output = stm.has_output();
|
|
||||||
stm.resampler.reset(unsafe {
|
|
||||||
ffi::cubeb_resampler_create(
|
|
||||||
stm_ptr,
|
|
||||||
if stm_has_input { &mut input_unconverted_params } else { ptr::null_mut() },
|
|
||||||
if stm_has_output { stm.output_stream_params.as_ptr() } else { ptr::null_mut() },
|
|
||||||
target_sample_rate,
|
|
||||||
stm.data_callback,
|
|
||||||
stm.user_ptr,
|
|
||||||
ffi::CUBEB_RESAMPLER_QUALITY_DESKTOP
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
if stm.resampler.as_mut_ptr().is_null() {
|
|
||||||
cubeb_log!("({:p}) Could not create resampler.", stm as *const AudioUnitStream);
|
|
||||||
return Err(Error::error());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stm.input_unit.is_null() {
|
|
||||||
let r = audio_unit_initialize(stm.input_unit);
|
|
||||||
if r != NO_ERR {
|
|
||||||
cubeb_log!("AudioUnitInitialize/input rv={}", r);
|
|
||||||
return Err(Error::error());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stm.output_unit.is_null() {
|
|
||||||
let r = audio_unit_initialize(stm.output_unit);
|
|
||||||
if r != NO_ERR {
|
|
||||||
cubeb_log!("AudioUnitInitialize/output rv={}", r);
|
|
||||||
return Err(Error::error());
|
|
||||||
}
|
|
||||||
|
|
||||||
stm.current_latency_frames.store(
|
|
||||||
audiounit_get_device_presentation_latency(
|
|
||||||
stm.output_device.id,
|
|
||||||
kAudioDevicePropertyScopeOutput
|
|
||||||
),
|
|
||||||
atomic::Ordering::SeqCst
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut unit_s: f64 = 0.0;
|
|
||||||
let mut size = mem::size_of_val(&unit_s);
|
|
||||||
if audio_unit_get_property(stm.output_unit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &mut unit_s, &mut size) == NO_ERR {
|
|
||||||
stm.current_latency_frames.fetch_add((unit_s * stm.output_desc.mSampleRate) as u32, atomic::Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stm.input_unit.is_null() && !stm.output_unit.is_null() {
|
|
||||||
// According to the I/O hardware rate it is expected a specific pattern of callbacks
|
|
||||||
// for example is input is 44100 and output is 48000 we expected no more than 2
|
|
||||||
// out callback in a row.
|
|
||||||
// TODO: Make sure `input_hw_rate` is larger than 0 ?
|
|
||||||
stm.expected_output_callbacks_in_a_row = (stm.output_hw_rate / stm.input_hw_rate).ceil() as i32
|
|
||||||
}
|
|
||||||
|
|
||||||
if stm.install_device_changed_callback().is_err() {
|
|
||||||
cubeb_log!("({:p}) Could not install all device change callback.", stm as *const AudioUnitStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_uint32_into_string(data: u32) -> CString
|
fn convert_uint32_into_string(data: u32) -> CString
|
||||||
{
|
{
|
||||||
// Simply create an empty string if no data.
|
// Simply create an empty string if no data.
|
||||||
|
@ -2506,7 +2346,7 @@ impl ContextOps for AudioUnitContext {
|
||||||
// ffi::cubeb_stream_params::default(). To prevent the stream from being initialized with
|
// ffi::cubeb_stream_params::default(). To prevent the stream from being initialized with
|
||||||
// wrong values, some easy checks in `audio_stream_desc_init` are added.
|
// wrong values, some easy checks in `audio_stream_desc_init` are added.
|
||||||
// I believe we can do more. For example,
|
// I believe we can do more. For example,
|
||||||
// 1. the resampler will be initialized in `audiounit_setup_stream` and it only accepts
|
// 1. the resampler will be initialized in `AudioUnitStream::setup` and it only accepts
|
||||||
// the formats with FLOAT32NE or S16NE.
|
// the formats with FLOAT32NE or S16NE.
|
||||||
// 2. If channels is 0, then size of input buffer is zero!
|
// 2. If channels is 0, then size of input buffer is zero!
|
||||||
// 3. What if the channels is different from the channels for the layout ?
|
// 3. What if the channels is different from the channels for the layout ?
|
||||||
|
@ -2565,7 +2405,7 @@ impl ContextOps for AudioUnitContext {
|
||||||
if let Err(r) = {
|
if let Err(r) = {
|
||||||
// It's not critical to lock here, because no other thread has been started
|
// It's not critical to lock here, because no other thread has been started
|
||||||
// yet, but it allows to assert that the lock has been taken in
|
// yet, but it allows to assert that the lock has been taken in
|
||||||
// `audiounit_setup_stream`.
|
// `AudioUnitStream::setup`.
|
||||||
|
|
||||||
// Since we cannot borrow boxed_stream as mutable twice
|
// Since we cannot borrow boxed_stream as mutable twice
|
||||||
// (for boxed_stream.mutex and boxed_stream itself), we store
|
// (for boxed_stream.mutex and boxed_stream itself), we store
|
||||||
|
@ -2575,7 +2415,7 @@ impl ContextOps for AudioUnitContext {
|
||||||
let mutex_ptr = &mut boxed_stream.mutex as *mut OwnedCriticalSection;
|
let mutex_ptr = &mut boxed_stream.mutex as *mut OwnedCriticalSection;
|
||||||
// The scope of `_lock` is a critical section.
|
// The scope of `_lock` is a critical section.
|
||||||
let _lock = AutoLock::new(unsafe { &mut (*mutex_ptr) });
|
let _lock = AutoLock::new(unsafe { &mut (*mutex_ptr) });
|
||||||
audiounit_setup_stream(boxed_stream.as_mut())
|
boxed_stream.setup()
|
||||||
} {
|
} {
|
||||||
cubeb_log!("({:p}) Could not setup the audiounit stream.", boxed_stream.as_ref());
|
cubeb_log!("({:p}) Could not setup the audiounit stream.", boxed_stream.as_ref());
|
||||||
return Err(r);
|
return Err(r);
|
||||||
|
@ -3032,7 +2872,7 @@ impl<'ctx> AudioUnitStream<'ctx> {
|
||||||
return Err(Error::error());
|
return Err(Error::error());
|
||||||
}
|
}
|
||||||
|
|
||||||
if audiounit_setup_stream(self).is_err() {
|
if self.setup().is_err() {
|
||||||
cubeb_log!(
|
cubeb_log!(
|
||||||
"({:p}) Stream reinit failed.",
|
"({:p}) Stream reinit failed.",
|
||||||
self as *const AudioUnitStream
|
self as *const AudioUnitStream
|
||||||
|
@ -3044,7 +2884,7 @@ impl<'ctx> AudioUnitStream<'ctx> {
|
||||||
if self
|
if self
|
||||||
.set_device_info(kAudioObjectUnknown, io_side::INPUT)
|
.set_device_info(kAudioObjectUnknown, io_side::INPUT)
|
||||||
.is_err()
|
.is_err()
|
||||||
|| audiounit_setup_stream(self).is_err()
|
|| self.setup().is_err()
|
||||||
{
|
{
|
||||||
cubeb_log!(
|
cubeb_log!(
|
||||||
"({:p}) Second stream reinit failed.",
|
"({:p}) Second stream reinit failed.",
|
||||||
|
@ -3947,6 +3787,219 @@ impl<'ctx> AudioUnitStream<'ctx> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup(&mut self) -> Result<()> {
|
||||||
|
// TODO: Add self.context.mutex.assert_current_thread_owns() ?
|
||||||
|
// audiounit_active_streams will require to own the mutex in
|
||||||
|
// self.context.
|
||||||
|
self.mutex.assert_current_thread_owns();
|
||||||
|
|
||||||
|
if self
|
||||||
|
.input_stream_params
|
||||||
|
.prefs()
|
||||||
|
.contains(StreamPrefs::LOOPBACK)
|
||||||
|
|| self
|
||||||
|
.output_stream_params
|
||||||
|
.prefs()
|
||||||
|
.contains(StreamPrefs::LOOPBACK)
|
||||||
|
{
|
||||||
|
cubeb_log!(
|
||||||
|
"({:p}) Loopback not supported for audiounit.",
|
||||||
|
self as *const AudioUnitStream
|
||||||
|
);
|
||||||
|
return Err(Error::not_supported());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut in_dev_info = self.input_device.clone();
|
||||||
|
let mut out_dev_info = self.output_device.clone();
|
||||||
|
|
||||||
|
if self.has_input() && self.has_output() && self.input_device.id != self.output_device.id {
|
||||||
|
if self.create_aggregate_device().is_err() {
|
||||||
|
self.aggregate_device_id = kAudioObjectUnknown;
|
||||||
|
cubeb_log!(
|
||||||
|
"({:p}) Create aggregate devices failed.",
|
||||||
|
self as *const AudioUnitStream
|
||||||
|
);
|
||||||
|
// !!!NOTE: It is not necessary to return here. If it does not
|
||||||
|
// return it will fallback to the old implementation. The intention
|
||||||
|
// is to investigate how often it fails. I plan to remove
|
||||||
|
// it after a couple of weeks.
|
||||||
|
} else {
|
||||||
|
in_dev_info.id = self.aggregate_device_id;
|
||||||
|
out_dev_info.id = self.aggregate_device_id;
|
||||||
|
in_dev_info.flags = device_flags::DEV_INPUT;
|
||||||
|
out_dev_info.flags = device_flags::DEV_OUTPUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.has_input() {
|
||||||
|
if let Err(r) = audiounit_create_unit(&mut self.input_unit, &in_dev_info) {
|
||||||
|
cubeb_log!(
|
||||||
|
"({:p}) AudioUnit creation for input failed.",
|
||||||
|
self as *const AudioUnitStream
|
||||||
|
);
|
||||||
|
return Err(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.has_output() {
|
||||||
|
if let Err(r) = audiounit_create_unit(&mut self.output_unit, &out_dev_info) {
|
||||||
|
cubeb_log!(
|
||||||
|
"({:p}) AudioUnit creation for output failed.",
|
||||||
|
self as *const AudioUnitStream
|
||||||
|
);
|
||||||
|
return Err(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Latency cannot change if another stream is operating in parallel. In this case
|
||||||
|
* latecy is set to the other stream value. */
|
||||||
|
if self.context.active_streams() > 1 {
|
||||||
|
cubeb_log!(
|
||||||
|
"({:p}) More than one active stream, use global latency.",
|
||||||
|
self as *const AudioUnitStream
|
||||||
|
);
|
||||||
|
self.latency_frames = self.context.global_latency_frames;
|
||||||
|
} else {
|
||||||
|
/* Silently clamp the latency down to the platform default, because we
|
||||||
|
* synthetize the clock from the callbacks, and we want the clock to update
|
||||||
|
* often. */
|
||||||
|
// Create a `latency_frames` here to avoid the borrowing issue.
|
||||||
|
let latency_frames = self.latency_frames;
|
||||||
|
self.latency_frames = self.clamp_latency(latency_frames);
|
||||||
|
assert!(self.latency_frames > 0); // Ugly error check
|
||||||
|
self.context.set_global_latency(self.latency_frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configure I/O stream */
|
||||||
|
if self.has_input() {
|
||||||
|
if let Err(r) = self.configure_input() {
|
||||||
|
cubeb_log!(
|
||||||
|
"({:p}) Configure audiounit input failed.",
|
||||||
|
self as *const AudioUnitStream
|
||||||
|
);
|
||||||
|
return Err(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.has_output() {
|
||||||
|
if let Err(r) = self.configure_output() {
|
||||||
|
cubeb_log!(
|
||||||
|
"({:p}) Configure audiounit output failed.",
|
||||||
|
self as *const AudioUnitStream
|
||||||
|
);
|
||||||
|
return Err(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We use a resampler because input AudioUnit operates
|
||||||
|
* reliable only in the capture device sample rate.
|
||||||
|
* Resampler will convert it to the user sample rate
|
||||||
|
* and deliver it to the callback. */
|
||||||
|
let target_sample_rate = if self.has_input() {
|
||||||
|
self.input_stream_params.rate()
|
||||||
|
} else {
|
||||||
|
assert!(self.has_output());
|
||||||
|
self.output_stream_params.rate()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut input_unconverted_params: ffi::cubeb_stream_params =
|
||||||
|
unsafe { ::std::mem::zeroed() };
|
||||||
|
if self.has_input() {
|
||||||
|
input_unconverted_params = unsafe { (*(self.input_stream_params.as_ptr())) }; // Perform copy.
|
||||||
|
input_unconverted_params.rate = self.input_hw_rate as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stm_ptr = self as *mut AudioUnitStream as *mut ffi::cubeb_stream;
|
||||||
|
let stm_input_params = if self.has_input() {
|
||||||
|
&mut input_unconverted_params
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
};
|
||||||
|
let stm_output_params = if self.has_output() {
|
||||||
|
self.output_stream_params.as_ptr()
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
};
|
||||||
|
self.resampler.reset(unsafe {
|
||||||
|
ffi::cubeb_resampler_create(
|
||||||
|
stm_ptr,
|
||||||
|
stm_input_params,
|
||||||
|
stm_output_params,
|
||||||
|
target_sample_rate,
|
||||||
|
self.data_callback,
|
||||||
|
self.user_ptr,
|
||||||
|
ffi::CUBEB_RESAMPLER_QUALITY_DESKTOP,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if self.resampler.as_mut_ptr().is_null() {
|
||||||
|
cubeb_log!(
|
||||||
|
"({:p}) Could not create resampler.",
|
||||||
|
self as *const AudioUnitStream
|
||||||
|
);
|
||||||
|
return Err(Error::error());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.input_unit.is_null() {
|
||||||
|
let r = audio_unit_initialize(self.input_unit);
|
||||||
|
if r != NO_ERR {
|
||||||
|
cubeb_log!("AudioUnitInitialize/input rv={}", r);
|
||||||
|
return Err(Error::error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.output_unit.is_null() {
|
||||||
|
let r = audio_unit_initialize(self.output_unit);
|
||||||
|
if r != NO_ERR {
|
||||||
|
cubeb_log!("AudioUnitInitialize/output rv={}", r);
|
||||||
|
return Err(Error::error());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_latency_frames.store(
|
||||||
|
audiounit_get_device_presentation_latency(
|
||||||
|
self.output_device.id,
|
||||||
|
kAudioDevicePropertyScopeOutput,
|
||||||
|
),
|
||||||
|
atomic::Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut unit_s: f64 = 0.0;
|
||||||
|
let mut size = mem::size_of_val(&unit_s);
|
||||||
|
if audio_unit_get_property(
|
||||||
|
self.output_unit,
|
||||||
|
kAudioUnitProperty_Latency,
|
||||||
|
kAudioUnitScope_Global,
|
||||||
|
0,
|
||||||
|
&mut unit_s,
|
||||||
|
&mut size,
|
||||||
|
) == NO_ERR
|
||||||
|
{
|
||||||
|
self.current_latency_frames.fetch_add(
|
||||||
|
(unit_s * self.output_desc.mSampleRate) as u32,
|
||||||
|
atomic::Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.input_unit.is_null() && !self.output_unit.is_null() {
|
||||||
|
// According to the I/O hardware rate it is expected a specific pattern of callbacks
|
||||||
|
// for example is input is 44100 and output is 48000 we expected no more than 2
|
||||||
|
// out callback in a row.
|
||||||
|
// TODO: Make sure `input_hw_rate` is larger than 0 ?
|
||||||
|
self.expected_output_callbacks_in_a_row =
|
||||||
|
(self.output_hw_rate / self.input_hw_rate).ceil() as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.install_device_changed_callback().is_err() {
|
||||||
|
cubeb_log!(
|
||||||
|
"({:p}) Could not install all device change callback.",
|
||||||
|
self as *const AudioUnitStream
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn close(&mut self) {
|
fn close(&mut self) {
|
||||||
self.mutex.assert_current_thread_owns();
|
self.mutex.assert_current_thread_owns();
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче