Skip to content

audiounit: output is silent when the device delivers more frames per slice than the requested latency (kAudioUnitErr_TooManyFramesToProcess) #862

Description

@drosskopp

Environment

  • cubeb e13d9c8 (also reproduced with an older build bundled by an application)
  • C++ audiounit backend
  • macOS 27.0, MacBook Air M5, AppleClang 21
  • Output device: AirPods Pro 3 (three different pairs)

Problem
audiounit_configure_output() sets kAudioDevicePropertyBufferFrameSize to
stm->latency_frames and then sets kAudioUnitProperty_MaximumFramesPerSlice to
the same value, without checking which buffer size the device actually runs
with. If the device delivers larger slices, AUHAL rejects every render call and
the stream stays silent. No error is reported to the application: the stream
is started and stays in CUBEB_STATE_STARTED.

Observed
An application (the Astris emulator) opens an output stream on the default
device with a latency of 240 frames (48 kHz). On the built-in speakers and on
HomePods this works. On AirPods Pro 3 the device delivers 256-frame slices and
the system log shows, once per second:

(CoreAudio) kAudioUnitErr_TooManyFramesToProcess : inFramesToProcess=256, mMaxFramesPerSlice=240

The same happens when the stream is reinitialized after a default device
change to the AirPods. Switching back to another device restores audio, so the
stream itself is alive.

Fix
Do not cap MaximumFramesPerSlice at the requested latency. The patch below uses
the largest of: the buffer size the device reports, the requested latency, and
SAFE_MAX_LATENCY_FRAMES. The data callback already handles arbitrary frame
counts.

Verified by building this revision with the patch as a shared library and
replacing the application's bundled libcubeb.dylib: switching between speakers,
HomePod and AirPods Pro 3 now works in every direction and the error is gone.

--- a/src/cubeb_audiounit.cpp
+++ b/src/cubeb_audiounit.cpp
@@ -2560,10 +2560,33 @@ audiounit_configure_output(cubeb_stream * stm)
     return CUBEB_ERROR;
   }
 
-  /* Frames per buffer in the input callback. */
-  r = AudioUnitSetProperty(
-      stm->output_unit, kAudioUnitProperty_MaximumFramesPerSlice,
-      kAudioUnitScope_Global, AU_OUT_BUS, &stm->latency_frames, sizeof(UInt32));
+  /* The device is free to run with a buffer size other than the one requested
+   * above. Observed with AirPods: 240 frames requested, 256 frames delivered
+   * per slice. If MaximumFramesPerSlice is smaller than what the device
+   * delivers, every render call fails with
+   * kAudioUnitErr_TooManyFramesToProcess and the stream stays silent. So never
+   * go below the buffer size the device reports, nor below the largest buffer
+   * size we would ever request ourselves. */
+  UInt32 max_frames_per_slice =
+      max<uint32_t>(stm->latency_frames, SAFE_MAX_LATENCY_FRAMES);
+  UInt32 device_buffer_frames = 0;
+  UInt32 device_buffer_frames_size = sizeof(device_buffer_frames);
+  r = AudioUnitGetProperty(stm->output_unit,
+                           kAudioDevicePropertyBufferFrameSize,
+                           kAudioUnitScope_Input, AU_OUT_BUS,
+                           &device_buffer_frames, &device_buffer_frames_size);
+  if (r != noErr) {
+    LOG("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize rv=%d",
+        r);
+  } else if (device_buffer_frames > max_frames_per_slice) {
+    max_frames_per_slice = device_buffer_frames;
+  }
+
+  /* Frames per buffer in the output callback. */
+  r = AudioUnitSetProperty(stm->output_unit,
+                           kAudioUnitProperty_MaximumFramesPerSlice,
+                           kAudioUnitScope_Global, AU_OUT_BUS,
+                           &max_frames_per_slice, sizeof(UInt32));
   if (r != noErr) {
     LOG("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice "
         "rv=%d",

Notes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions