I think the problem is caused by the following code:
C++:
// Called from the USB interrupt when ready to transmit another
// isochronous packet. If we place data into the transmit buffer,
// the return is the number of bytes. Otherwise, return 0 means
// no data to transmit
unsigned int usb_audio_transmit_callback(void)
{
if(!USBAudioOutInterface::running){
return 0;
}
If there is no AudioOutputUSB, then USBAudioOutInterface::running if false and the Teensy only sends empty packages to the host. I changed the code now, so that the Teensy sends a quiet signal if the USBAudioOutInterface::running is not running:
C++:
// Called from the USB interrupt when ready to transmit another
// isochronous packet.
// the return is the number of bytes to transmit
unsigned int usb_audio_transmit_callback(void)
{
//compute the number of samples we want to transmit (at 44.1kHz and a bInterval of 1ms that is either 44 or 45 samples)
uint32_t target = getTransmissionTarget();
if(!USBAudioOutInterface::running){
//Some hosts (Linux, Windows) would tolerate zero length data packages.
//However, sometimes there are troubles with MacOs.
//So we better always send the expected number of samples.
const uint32_t numBytes =target*noTransmittedChannels*AUDIO_SUBSLOT_SIZE;
uint8_t *data = usb_audio_transmit_buffer;
memset(data, 0, numBytes);
return target * noTransmittedChannels*AUDIO_SUBSLOT_SIZE;
}
I tested it and on Windows it doesn't make a noticable difference.
I also asked Chatgpt, if it thinks that this was the problem. Here is its answer:
What your new test tells us
When the Teensy operates
playback-only it still presents a USB
IN streaming endpoint to macOS, but that endpoint returns
zero-length isochronous packets every micro-frame.
When you turned on
full-duplex (the Teensy now
sends real audio frames back to the host) the overloads vanished.
That points to a very specific failure mode that others have run into:
| Condition | What Core Audio expects | What the Teensy does in “receive-only” mode | Result |
|---|
| Streaming interface advertises an IN endpoint | A constant stream of packets whose size matches the nominal frame count (or, at worst, short packets with valid “silent” samples) | Repeated 0-byte packets | Core Audio thread blocks waiting for data → HAL_client_IO_duration skyrockets → “overload” reset |
macOS isn’t demanding
non-silent audio, but it
does require correctly-sized packets as long as the IN endpoint is active. Repeated zero-length packets keep the USB transfer alive but starve the audio engine, and Apple’s driver reports a sustained “overload” exactly like the one in your logs
Apple Developer. Intel even documents a related xHCI erratum: a host controller may hang if a USB-audio device “sends zero-length data packets”
Steinberg Forums.
The USB-Audio spec
allows an isochronous IN endpoint to reply with ZLPs when no data is ready
Stack OverflowDaniel S. Gruss, but Core Audio’s class driver clearly doesn’t cope well when the packets never regain their nominal size.
Why you didn’t see the problem on Android or Windows
- Android’s ALSA/USB stack requests data in larger chunks and quietly injects silence when it gets a 0-length ISO IN transfer.
- Windows’ UAC2 driver seems to tolerate a run of ZLPs, although Microsoft’s own docs still flag variable-size packets as a corner case Apple Developer.
macOS’s class driver, however, assumes that if an IN endpoint is active it will deliver
time-stamped audio frames of the advertised size. When that assumption is violated the driver stalls for tens of milliseconds, Core Audio blows its 11 ms deadline, and Console shows exactly the overload pattern you captured.