Summary
Using AudioHardwareCreateProcessTap + AudioHardwareCreateAggregateDevice for system audio capture. During long sessions, the AudioDeviceIOProc callback continues firing normally but every PCM sample is exactly 0.0f — while the system is producing audible output.
Environment
| macOS | 26.5 Beta |
| Hardware | MacBook Air (M2) |
| API | AudioHardwareCreateProcessTap + AudioHardwareCreateAggregateDevice |
| Tap | CATapDescription, processes = [], .unmuted, private |
| Format | 48,000 Hz, Float32, interleaved stereo |
| Aggregate anchor | kAudioAggregateDeviceMainSubDeviceKey = current default output UID |
Observed behavior
After running normally for several minutes, the stream transitions into an all-zero state:
AudioDeviceIOProccontinues to fire at expected cadence- Frame count, timestamps (
mHostTime,mSampleTime), andmDataByteSizeall look normal AudioBufferListpointers are valid- Every sample in every buffer is exactly
0.0f - Other apps are still producing audible output through the same output device
- The condition may self-recover or persist until the session is stopped
Confirmed via RMS logging both inside the IOProc and after the ring buffer consumer — data is zero on delivery, not introduced downstream.
Example: 51-minute session on MacBook Air M2
Segment 1 (~7 min): Three all-zero periods: 60 s, 53 s, 141 s. Real PCM briefly returned between them.
Segment 2 (~44 min): Two all-zero periods: 16 min 3 s, 3 min 8 s. IOProc cadence, timestamp deltas, default output UID, and kAudioDevicePropertyDeviceIsRunningSomewhere all remained normal throughout.
What I have ruled out
- Actual silence: User was in an active video call and could hear participants through the output device.
- Default output device change: Monitored
kAudioHardwarePropertyDefaultOutputDevice— no change during affected periods. - IOProc stall: Heartbeat and
kAudioDevicePropertyDeviceIsRunningSomewhereremained normal. - Aggregate device destroyed:
AudioObjectGetPropertyDataon the aggregate UID continued returning the expected device. - Tap descriptor misconfiguration: The same tap produces valid PCM earlier in the same session, so this is not a startup-time issue.
Why detection is hard
All-zero buffers from a broken tap are indistinguishable from legitimate silence (muted participant, waiting room, paused media). kAudioProcessPropertyIsRunningOutput reports whether a process has active output IO, not whether it is contributing non-zero samples — a muted Zoom call still reports true.
Possible correlations
- Sample-rate renegotiation on the output device (44.1 kHz ↔ 48 kHz) when another app changes output
- Bluetooth device state changes (AirPods sleep/wake) where UID stays the same
- MacBook Air more frequently affected than MacBook Pro
- Always occurs after extended uptime — first few minutes are consistently clean
Current workaround
Full teardown and rebuild restores real PCM. Restarting the IOProc alone or recreating only the aggregate device is not reliable — both the Process Tap and Aggregate Device must be destroyed and recreated.
1. AudioDeviceStop
2. AudioDeviceDestroyIOProcID
3. AudioHardwareDestroyAggregateDevice
4. AudioHardwareDestroyProcessTap
5. AudioHardwareCreateProcessTap
6. AudioHardwareCreateAggregateDevice
7. Create + start new IOProc
Applying this automatically is risky because it cannot be reliably distinguished from legitimate silence.
Questions
-
Expected failure mode? Can a Process Tap continue delivering zero-filled buffers while the system output is audible? Is this expected under certain device or routing conditions?
-
Detection signal? Is there any HAL property, notification, or diagnostic counter that distinguishes "sources are genuinely silent" from "the tap data path has stopped receiving the real mix"?
-
Targeted recovery? Is there a supported way to re-anchor or reset the tap data path without destroying and recreating both objects?
-
Full rebuild as intended workaround? If so, it would help to confirm this so developers can converge on a consistent approach.
-
Mixer activity signal?
kAudioProcessPropertyIsRunningOutputreflects IO registration, not sample contribution. Is there anyAudioProcessproperty that indicates a process is currently delivering non-zero audio to the system mixer?