Skip to content

Commit fc5e274

Browse files
committed
minimize unsafe block and return errors that map to what I can tell as the most appropriate constants from objc2_audio-toolbox.
Added a bounds check against the input_bytes just in case.
1 parent 02c070e commit fc5e274

1 file changed

Lines changed: 43 additions & 17 deletions

File tree

src/host/coreaudio/macos/device.rs

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ use coreaudio::audio_unit::render_callback::{self, data};
1818
use coreaudio::audio_unit::{AudioUnit, Element, Scope};
1919
use objc2_audio_toolbox::{
2020
kAudioOutputUnitProperty_CurrentDevice, kAudioOutputUnitProperty_EnableIO,
21-
kAudioUnitProperty_SetRenderCallback, kAudioUnitProperty_StreamFormat, AURenderCallbackStruct,
22-
AudioUnitRender, AudioUnitRenderActionFlags,
21+
kAudioUnitErr_TooManyFramesToProcess, kAudioUnitProperty_SetRenderCallback,
22+
kAudioUnitProperty_StreamFormat, AURenderCallbackStruct, AudioUnitRender,
23+
AudioUnitRenderActionFlags,
2324
};
2425
use objc2_core_audio::kAudioDevicePropertyDeviceUID;
2526
use objc2_core_audio::kAudioObjectPropertyElementMain;
@@ -34,7 +35,8 @@ use objc2_core_audio::{
3435
AudioObjectPropertyScope, AudioObjectSetPropertyData,
3536
};
3637
use objc2_core_audio_types::{
37-
AudioBuffer, AudioBufferList, AudioStreamBasicDescription, AudioTimeStamp, AudioValueRange,
38+
kAudio_ParamError, AudioBuffer, AudioBufferList, AudioStreamBasicDescription, AudioTimeStamp,
39+
AudioValueRange,
3840
};
3941
use objc2_core_foundation::CFString;
4042
use objc2_core_foundation::Type;
@@ -1108,8 +1110,6 @@ impl Device {
11081110
// Move data callback into closure
11091111
let mut data_callback = data_callback;
11101112

1111-
// Create the duplex callback closure
1112-
// This closure owns all captured state - no Mutex needed for data_callback or input_buffer
11131113
let duplex_proc: Box<DuplexProcFn> = Box::new(
11141114
move |io_action_flags: NonNull<AudioUnitRenderActionFlags>,
11151115
in_time_stamp: NonNull<AudioTimeStamp>,
@@ -1121,13 +1121,33 @@ impl Device {
11211121
let input_samples = num_frames * input_channels;
11221122
let input_bytes = input_samples * sample_bytes;
11231123

1124+
// Bounds check: ensure the requested frames don't exceed our pre-allocated buffer
1125+
if input_bytes > input_buffer.len() {
1126+
invoke_error_callback(
1127+
&error_callback_for_callback,
1128+
StreamError::BackendSpecific {
1129+
err: BackendSpecificError {
1130+
description: format!(
1131+
"Callback requested {} frames ({} bytes) but buffer is {} bytes",
1132+
num_frames, input_bytes, input_buffer.len()
1133+
),
1134+
},
1135+
},
1136+
);
1137+
// Return error - this is a persistent failure that will happen every callback
1138+
return kAudioUnitErr_TooManyFramesToProcess;
1139+
}
1140+
11241141
// SAFETY: in_time_stamp is valid per CoreAudio contract
1125-
let timestamp = unsafe { in_time_stamp.as_ref() };
1142+
let timestamp: &AudioTimeStamp = unsafe { in_time_stamp.as_ref() };
11261143

11271144
// Create StreamInstant for callback_instant
11281145
let callback_instant = match host_time_to_stream_instant(timestamp.mHostTime) {
11291146
Err(err) => {
11301147
invoke_error_callback(&error_callback_for_callback, err.into());
1148+
// Return 0 (noErr) to keep the stream alive while notifying the error
1149+
// callback. This matches input/output stream behavior and allows graceful
1150+
// degradation rather than stopping the stream on transient errors.
11311151
return 0;
11321152
}
11331153
Ok(cb) => cb,
@@ -1193,7 +1213,8 @@ impl Device {
11931213

11941214
// Get output buffer from CoreAudio
11951215
if io_data.is_null() {
1196-
return 0;
1216+
// Return error - Core Audio should never pass null, this is a fatal error
1217+
return kAudio_ParamError;
11971218
}
11981219

11991220
// Create Data wrappers for input and output
@@ -1205,16 +1226,20 @@ impl Device {
12051226
)
12061227
};
12071228

1229+
// SAFETY: io_data is guaranteed valid by Core Audio for the duration of this callback
1230+
let buffer_list = unsafe { &mut *io_data };
1231+
if buffer_list.mNumberBuffers == 0 {
1232+
// Return error - no buffers available, cannot render
1233+
return kAudio_ParamError;
1234+
}
1235+
let buffer = &mut buffer_list.mBuffers[0];
1236+
if buffer.mData.is_null() {
1237+
// Return error - null buffer data, cannot render
1238+
return kAudio_ParamError;
1239+
}
1240+
let output_samples = buffer.mDataByteSize as usize / sample_bytes;
1241+
// SAFETY: buffer.mData points to valid audio data provided by Core Audio
12081242
let mut output_data = unsafe {
1209-
let buffer_list = &mut *io_data;
1210-
if buffer_list.mNumberBuffers == 0 {
1211-
return 0;
1212-
}
1213-
let buffer = &mut buffer_list.mBuffers[0];
1214-
if buffer.mData.is_null() {
1215-
return 0;
1216-
}
1217-
let output_samples = buffer.mDataByteSize as usize / sample_bytes;
12181243
Data::from_parts(buffer.mData as *mut (), output_samples, sample_format)
12191244
};
12201245

@@ -1232,7 +1257,8 @@ impl Device {
12321257
// Call user callback with input and output Data
12331258
data_callback(&input_data, &mut output_data, &callback_info);
12341259

1235-
0 // noErr
1260+
// Return 0 (noErr) to indicate successful render
1261+
0
12361262
},
12371263
);
12381264

0 commit comments

Comments
 (0)