@@ -18,8 +18,9 @@ use coreaudio::audio_unit::render_callback::{self, data};
1818use coreaudio:: audio_unit:: { AudioUnit , Element , Scope } ;
1919use 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} ;
2425use objc2_core_audio:: kAudioDevicePropertyDeviceUID;
2526use objc2_core_audio:: kAudioObjectPropertyElementMain;
@@ -34,7 +35,8 @@ use objc2_core_audio::{
3435 AudioObjectPropertyScope , AudioObjectSetPropertyData ,
3536} ;
3637use objc2_core_audio_types:: {
37- AudioBuffer , AudioBufferList , AudioStreamBasicDescription , AudioTimeStamp , AudioValueRange ,
38+ kAudio_ParamError, AudioBuffer , AudioBufferList , AudioStreamBasicDescription , AudioTimeStamp ,
39+ AudioValueRange ,
3840} ;
3941use objc2_core_foundation:: CFString ;
4042use 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