Skip to content

Commit 2b4e804

Browse files
committed
clean-up comments
1 parent 73179a0 commit 2b4e804

File tree

4 files changed

+24
-106
lines changed

4 files changed

+24
-106
lines changed

src/duplex.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl DuplexCallbackInfo {
2727
}
2828
}
2929

30-
#[derive(Clone, Debug, Eq, PartialEq)]
30+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3131
pub struct DuplexStreamConfig {
3232
pub input_channels: ChannelCount,
3333
pub output_channels: ChannelCount,

src/host/coreaudio/macos/duplex.rs

Lines changed: 17 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ type DuplexProcFn = dyn FnMut(
3434
*mut AudioBufferList,
3535
) -> i32;
3636

37-
/// Wrapper for the boxed duplex callback closure.
38-
///
39-
/// This struct is allocated on the heap and its pointer is passed to CoreAudio
40-
/// as the refcon. The extern "C" callback function casts the refcon back to
41-
/// this type and calls the closure.
4237
pub(crate) struct DuplexProcWrapper {
4338
callback: Box<DuplexProcFn>,
4439
}
@@ -52,16 +47,8 @@ pub(crate) struct DuplexProcWrapper {
5247
// 3. CoreAudio guarantees single-threaded callback invocation
5348
unsafe impl Send for DuplexProcWrapper {}
5449

55-
/// CoreAudio render callback for duplex audio.
56-
///
57-
/// This is a thin wrapper that casts the refcon back to our DuplexProcWrapper
58-
/// and calls the inner closure. The closure owns all the callback state via
59-
/// move semantics, so no Mutex is needed.
60-
///
61-
/// Note: `extern "C-unwind"` is required here because `AURenderCallbackStruct`
62-
/// from coreaudio-sys types the `inputProc` field as `extern "C-unwind"`.
63-
/// We use `catch_unwind` to prevent panics from unwinding through CoreAudio's
64-
/// C frames, which would be undefined behavior.
50+
// `extern "C-unwind"` matches `AURenderCallbackStruct::inputProc`.
51+
// `catch_unwind` prevents panics from unwinding through CoreAudio's C frames.
6552
extern "C-unwind" fn duplex_input_proc(
6653
in_ref_con: NonNull<c_void>,
6754
io_action_flags: NonNull<AudioUnitRenderActionFlags>,
@@ -70,12 +57,10 @@ extern "C-unwind" fn duplex_input_proc(
7057
in_number_frames: u32,
7158
io_data: *mut AudioBufferList,
7259
) -> i32 {
73-
// SAFETY: `in_ref_con` points to a heap-allocated `DuplexProcWrapper` created
74-
// via `Box::into_raw` in `build_duplex_stream`, and remains valid for the
75-
// lifetime of the audio unit (reclaimed in `StreamInner::drop`). The `as_mut()` call
76-
// produces an exclusive `&mut` reference, which is sound because CoreAudio
77-
// guarantees single-threaded callback invocation — this function is never
78-
// called concurrently, so only one `&mut` to the wrapper exists at a time.
60+
// SAFETY: `in_ref_con` originates from `Box::into_raw` in `build_duplex_stream_raw`.
61+
// `StreamInner::drop` stops the audio unit before reclaiming the pointer,
62+
// so it remains valid for the lifetime of the callback.
63+
// Called from a single render thread per audio unit, so `as_mut()` has exclusive access.
7964
let wrapper = unsafe { in_ref_con.cast::<DuplexProcWrapper>().as_mut() };
8065
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
8166
(wrapper.callback)(
@@ -92,11 +77,7 @@ extern "C-unwind" fn duplex_input_proc(
9277
}
9378

9479
impl Device {
95-
/// Build a duplex stream with synchronized input and output.
96-
///
97-
/// This creates a single HAL AudioUnit with both input and output enabled,
98-
/// ensuring they share the same hardware clock.
99-
/// For details, see: https://developer.apple.com/library/archive/technotes/tn2091/_index.html
80+
// See: https://developer.apple.com/library/archive/technotes/tn2091/_index.html
10081
pub(crate) fn build_duplex_stream_raw<D, E>(
10182
&self,
10283
config: &crate::duplex::DuplexStreamConfig,
@@ -109,87 +90,64 @@ impl Device {
10990
D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static,
11091
E: FnMut(StreamError) + Send + 'static,
11192
{
112-
// Validate that device supports duplex
11393
if !self.supports_duplex() {
11494
return Err(BuildStreamError::StreamConfigNotSupported);
11595
}
11696

117-
// Potentially change the device sample rate to match the config.
11897
set_sample_rate(self.audio_device_id, config.sample_rate)?;
11998

120-
// Create HAL AudioUnit - always use HalOutput for duplex
12199
let mut audio_unit = AudioUnit::new(coreaudio::audio_unit::IOType::HalOutput)?;
122100

123-
// Enable BOTH input and output on the AudioUnit
124-
// Enable input on Element 1
125101
audio_unit.set_property(
126102
kAudioOutputUnitProperty_EnableIO,
127103
Scope::Input,
128104
Element::Input,
129105
Some(&AUDIO_UNIT_IO_ENABLED),
130106
)?;
131107

132-
// Enable output on Element 0 (usually enabled by default, but be explicit)
133108
audio_unit.set_property(
134109
kAudioOutputUnitProperty_EnableIO,
135110
Scope::Output,
136111
Element::Output,
137112
Some(&AUDIO_UNIT_IO_ENABLED),
138113
)?;
139114

140-
// Set device for the unit (applies to both input and output)
141115
audio_unit.set_property(
142116
kAudioOutputUnitProperty_CurrentDevice,
143117
Scope::Global,
144118
Element::Output,
145119
Some(&self.audio_device_id),
146120
)?;
147121

148-
// Create StreamConfig for input side
149122
let input_stream_config = StreamConfig {
150123
channels: config.input_channels,
151124
sample_rate: config.sample_rate,
152125
buffer_size: config.buffer_size,
153126
};
154127

155-
// Create StreamConfig for output side
156128
let output_stream_config = StreamConfig {
157129
channels: config.output_channels,
158130
sample_rate: config.sample_rate,
159131
buffer_size: config.buffer_size,
160132
};
161133

162-
// Core Audio's HAL AU has two buses, each with a hardware side and a
163-
// client (app) side. We set the stream format on the client-facing side;
164-
// the AU's built-in converter handles translation to/from the hardware format.
165-
//
166-
// Mic ─[Scope::Input]──▶ Input Bus ──[Scope::Output]─▶ App
167-
// (hardware side) (client side)
168-
//
169-
// App ─[Scope::Input]──▶ Output Bus ─[Scope::Output]─▶ Speaker
170-
// (client side) (hardware side)
171-
//
172-
// So the client side is Scope::Output for the input bus (where we read
173-
// captured samples) and Scope::Input for the output bus (where we write
174-
// playback samples). See Apple TN2091 for details.
175-
let input_asbd = asbd_from_config(&input_stream_config, sample_format);
134+
// Client-side format: Scope::Output for input bus, Scope::Input for output bus.
135+
let input_asbd = asbd_from_config(input_stream_config, sample_format);
176136
audio_unit.set_property(
177137
kAudioUnitProperty_StreamFormat,
178138
Scope::Output,
179139
Element::Input,
180140
Some(&input_asbd),
181141
)?;
182142

183-
let output_asbd = asbd_from_config(&output_stream_config, sample_format);
143+
let output_asbd = asbd_from_config(output_stream_config, sample_format);
184144
audio_unit.set_property(
185145
kAudioUnitProperty_StreamFormat,
186146
Scope::Input,
187147
Element::Output,
188148
Some(&output_asbd),
189149
)?;
190150

191-
// Buffer frame size is a device-level property. Element::Output (0) is
192-
// the standard convention for Scope::Global properties.
193151
if let BufferSize::Fixed(buffer_size) = &config.buffer_size {
194152
audio_unit.set_property(
195153
kAudioDevicePropertyBufferFrameSize,
@@ -199,7 +157,6 @@ impl Device {
199157
)?;
200158
}
201159

202-
// Allocate input buffer for the current device buffer size.
203160
let current_buffer_size = get_device_buffer_frame_size(&audio_unit).map_err(|e| {
204161
BuildStreamError::BackendSpecific {
205162
err: BackendSpecificError {
@@ -217,7 +174,6 @@ impl Device {
217174
let input_buffer_bytes = current_buffer_size * input_channels * sample_bytes;
218175
let mut input_buffer: Box<[u8]> = vec![0u8; input_buffer_bytes].into_boxed_slice();
219176

220-
// Wrap error callback in Arc<Mutex> for sharing between callback and disconnect handler
221177
let error_callback = Arc::new(Mutex::new(error_callback));
222178
let error_callback_for_callback = error_callback.clone();
223179

@@ -238,7 +194,7 @@ impl Device {
238194
if io_data.is_null() {
239195
return kAudio_ParamError;
240196
}
241-
// SAFETY: io_data validated as non-null above
197+
// SAFETY: io_data validated as non-null above.
242198
let buffer_list = unsafe { &mut *io_data };
243199
if buffer_list.mNumberBuffers == 0 {
244200
return kAudio_ParamError;
@@ -253,16 +209,12 @@ impl Device {
253209
return kAudio_ParamError;
254210
}
255211

256-
// SAFETY: in_time_stamp is valid per CoreAudio contract
212+
// SAFETY: in_time_stamp is valid per CoreAudio callback contract.
257213
let timestamp: &AudioTimeStamp = unsafe { in_time_stamp.as_ref() };
258214

259-
// Create StreamInstant for callback_instant
260215
let callback_instant = match host_time_to_stream_instant(timestamp.mHostTime) {
261216
Err(err) => {
262217
invoke_error_callback(&error_callback_for_callback, err.into());
263-
// Return 0 (noErr) to keep the stream alive while notifying the error
264-
// callback. This matches input/output stream behavior and allows graceful
265-
// degradation rather than stopping the stream on transient errors.
266218
return 0;
267219
}
268220
Ok(cb) => cb,
@@ -274,7 +226,7 @@ impl Device {
274226
}
275227
let output_samples = buffer.mDataByteSize as usize / sample_bytes;
276228

277-
// SAFETY: buffer.mData validated as non-null above
229+
// SAFETY: buffer.mData validated as non-null above.
278230
let mut output_data = unsafe {
279231
Data::from_parts(buffer.mData as *mut (), output_samples, sample_format)
280232
};
@@ -289,7 +241,6 @@ impl Device {
289241
&error_callback_for_callback,
290242
);
291243

292-
// Create callback info with latency-adjusted times
293244
let input_timestamp = crate::InputStreamTimestamp {
294245
callback: callback_instant,
295246
capture,
@@ -299,9 +250,6 @@ impl Device {
299250
playback,
300251
};
301252

302-
// Pull input from Element 1 using AudioUnitRender
303-
// use the pre-allocated input_buffer
304-
// Set up AudioBufferList pointing to our input buffer
305253
let mut input_buffer_list = AudioBufferList {
306254
mNumberBuffers: 1,
307255
mBuffers: [AudioBuffer {
@@ -311,13 +259,8 @@ impl Device {
311259
}],
312260
};
313261

314-
// SAFETY: AudioUnitRender is called with valid parameters:
315-
// - raw_audio_unit is valid for the callback duration
316-
// - input_buffer_list is created just above on the stack with a pointer to
317-
// input_buffer, which is properly aligned (Rust's standard allocators return
318-
// pointers aligned to at least 8 bytes, exceeding f32/i16 requirements)
319-
// - input_buffer has been bounds-checked above to ensure sufficient capacity
320-
// - All other parameters (timestamps, flags, etc.) come from CoreAudio itself
262+
// SAFETY: raw_audio_unit is valid for the callback duration,
263+
// input_buffer_list points to bounds-checked input_buffer.
321264
let status = unsafe {
322265
AudioUnitRender(
323266
raw_audio_unit,
@@ -330,8 +273,6 @@ impl Device {
330273
};
331274

332275
if status != 0 {
333-
// Report error but continue with silence for graceful degradation
334-
// The application should decide what to do.
335276
invoke_error_callback(
336277
&error_callback_for_callback,
337278
StreamError::BackendSpecific {
@@ -346,18 +287,8 @@ impl Device {
346287
input_buffer[..input_bytes].fill(0);
347288
}
348289

349-
// SAFETY: Creating Data from input_buffer is safe because:
350-
// - input_buffer is a valid Vec<u8> owned by this closure
351-
// - input_samples (num_frames * input_channels) was bounds-checked above to ensure
352-
// input_samples * sample_bytes <= input_buffer.len()
353-
// - AudioUnitRender just filled the buffer with valid audio data (or we filled
354-
// it with silence on error)
355-
// - The Data lifetime is scoped to this callback and doesn't outlive input_buffer
356-
// - The pointer is suitably aligned: We successfully passed this buffer to
357-
// AudioUnitRender (line 1314), which requires properly aligned buffers and would
358-
// have failed if alignment were incorrect. Additionally, in practice, Rust's
359-
// standard allocators (System, jemalloc, etc.) return pointers aligned to at
360-
// least 8 bytes, which exceeds the requirements for f32 (4 bytes) and i16 (2 bytes).
290+
// SAFETY: input_buffer is bounds-checked, filled by AudioUnitRender
291+
// (or zeroed on error), and outlives this Data reference.
361292
let input_data = unsafe {
362293
Data::from_parts(
363294
input_buffer.as_mut_ptr() as *mut (),
@@ -369,18 +300,15 @@ impl Device {
369300
let callback_info = DuplexCallbackInfo::new(input_timestamp, output_timestamp);
370301
data_callback(&input_data, &mut output_data, &callback_info);
371302

372-
// Return 0 (noErr) to indicate successful render
373303
0
374304
},
375305
);
376306

377-
// Box the wrapper and get raw pointer for CoreAudio
378307
let wrapper = Box::new(DuplexProcWrapper {
379308
callback: duplex_proc,
380309
});
381310
let wrapper_ptr = Box::into_raw(wrapper);
382311

383-
// Set up the render callback
384312
let render_callback = AURenderCallbackStruct {
385313
inputProc: Some(duplex_input_proc),
386314
inputProcRefCon: wrapper_ptr as *mut std::ffi::c_void,
@@ -393,7 +321,6 @@ impl Device {
393321
Some(&render_callback),
394322
)?;
395323

396-
// Create the stream inner, storing the callback pointer for cleanup
397324
let inner = StreamInner {
398325
playing: true,
399326
audio_unit: ManuallyDrop::new(audio_unit),
@@ -402,17 +329,13 @@ impl Device {
402329
duplex_callback_ptr: Some(DuplexCallbackPtr(wrapper_ptr)),
403330
};
404331

405-
// Always propagate disconnect errors for duplex streams. A duplex stream
406-
// is broken when either direction changes device.
407332
let error_callback_clone = error_callback.clone();
408333
let error_callback_for_stream: super::ErrorCallback = Box::new(move |err: StreamError| {
409334
invoke_error_callback(&error_callback_clone, err);
410335
});
411336

412-
// Create the duplex stream
413337
let stream = Stream::new(inner, error_callback_for_stream, true)?;
414338

415-
// Start the audio unit
416339
stream
417340
.inner
418341
.lock()

src/host/coreaudio/macos/mod.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,6 @@ impl DisconnectManager {
109109
let (disconnect_tx, disconnect_rx) = mpsc::channel();
110110
let (ready_tx, ready_rx) = mpsc::channel();
111111

112-
// Buffer size listener uses a separate channel so the handler
113-
// can fire StreamInvalidated instead of DeviceNotAvailable.
114112
let (buffer_size_tx, buffer_size_rx) = mpsc::channel();
115113

116114
let disconnect_tx_clone = disconnect_tx.clone();
@@ -197,7 +195,6 @@ impl DisconnectManager {
197195
}
198196
});
199197

200-
// Handle buffer size change events
201198
if listen_buffer_size {
202199
let stream_weak_clone = stream_weak.clone();
203200
let error_callback_clone = error_callback.clone();
@@ -290,18 +287,18 @@ impl StreamInner {
290287

291288
impl Drop for StreamInner {
292289
fn drop(&mut self) {
293-
// SAFETY: audio_unit is not accessed after this point, and no references
294-
// to self outlive this function. Dropping first ensures callbacks have
295-
// stopped before reclaiming the duplex callback pointer below.
290+
// SAFETY: This is the sole owning instance of audio_unit (wrapped in
291+
// ManuallyDrop so we control drop order). Dropping it stops the audio
292+
// unit, which guarantees CoreAudio will not invoke the render callback
293+
// after this point. That makes it safe to reclaim the duplex callback
294+
// pointer below. audio_unit is not accessed after this point.
296295
unsafe {
297296
ManuallyDrop::drop(&mut self.audio_unit);
298297
}
299298

300299
if let Some(DuplexCallbackPtr(ptr)) = self.duplex_callback_ptr {
301300
if !ptr.is_null() {
302-
// SAFETY: `ptr` was created via `Box::into_raw` in
303-
// `build_duplex_stream` and has not been reclaimed elsewhere.
304-
// The audio unit was dropped above, so no callbacks are active.
301+
// SAFETY: ptr created via Box::into_raw, not reclaimed elsewhere.
305302
unsafe {
306303
let _ = Box::from_raw(ptr);
307304
}

src/traits.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,6 @@ pub trait DeviceTrait {
300300
E: FnMut(StreamError) + Send + 'static;
301301

302302
/// Create a duplex stream with synchronized input and output sharing the same hardware clock.
303-
///
304-
/// Check [`supports_duplex`](Self::supports_duplex) before calling. See the example below for usage.
305303
fn build_duplex_stream<T, D, E>(
306304
&self,
307305
config: &DuplexStreamConfig,

0 commit comments

Comments
 (0)