Operating System
Windows 11
Commit SHA
0.20.1 (as bundled in arduino-esp32 3.3.10)
Board
ESP32-S2 (generic ESP32-S2 Dev Module), Synopsys dwc2 DCD, USB-OTG Full-Speed
Firmware
Custom UAC2 speaker (sink): CFG_TUD_AUDIO=1, EP OUT + explicit async feedback EP, 48kHz/16-bit/stereo, using the stock audio_device.c.
Note: arduino-esp32 does not ship CFG_TUD_AUDIO (see espressif/arduino-esp32#12053), so audio_device.c here is user-supplied; but the root cause below is in dcd_dwc2.c which IS shipped.
What happened ?
A UAC2 speaker (sink) on ESP32-S2 stalls ~5 s every time the host re-opens the audio streaming interface (SET_INTERFACE alt=0 then alt=1) -- e.g. when resuming playback after the host closed the stream on idle/pause.
audiod_set_interface() calls usbd_edpt_iso_activate() again on the ISO OUT (and feedback) endpoints. On dwc2, dcd_edpt_iso_activate() disables the EP first:
bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) {
edpt_disable(rhport, p_endpoint_desc->bEndpointAddress, false);
edpt_activate(rhport, p_endpoint_desc);
return true;
}
edpt_disable() for an OUT endpoint busy-waits on the disable handshake:
dwc2->dctl |= DCTL_SGONAK;
while ((dwc2->gintsts & GINTSTS_BOUTNAKEFF_Msk) == 0) {}
dep->doepctl |= DOEPCTL_EPDIS | stall_mask;
while ((dep->doepint & DOEPINT_EPDISD_Msk) == 0) {}
When re-activating an IDLE ISO OUT EP (no tokens arrive during the brief alt toggle), this handshake does not complete promptly -- measured ~5000 ms per call on ESP32-S2 -- and it runs in the usbd task, so the device stops servicing ALL USB (control + every EP) for that window. The host restart is alt=1 -> alt=0 -> alt=1, so total user-visible stall is ~10 s.
Real-world symptom: ESP32-S2 USB DAC set as the Windows default output. Pause YouTube in Chrome for >~10 s (Windows audio engine then closes the stream). Press play -> audio and the A/V-synced video freeze ~10 s before resuming. A built-in sound card never does this close/reopen cycle, so it never stalls.
Confirmed it is NOT USB selective suspend: reproduced with the device kept awake (CDC interface held open), tud_suspended() == false throughout, device millis() never resets.
Tested workaround (application side): with TUP_DCD_EDPT_ISO_ALLOC, the alt=0 close path does not deactivate the EP hardware, so re-activation is both redundant and triggers the slow disable. Activating each ISO EP only once eliminates the stall:
// in audiod_set_interface() EP-open loop
volatile bool* act = (ep_addr & 0x80) ? &iso_act_in : &iso_act_out;
if (!*act) { TU_ASSERT(usbd_edpt_iso_activate(rhport, desc_ep)); *act = true; }
// flags reset in audiod_reset()
Resume latency dropped from >9.7 s to ~0.5 s.
Questions for maintainers:
- Should dcd_edpt_iso_activate() skip edpt_disable() when the EP is not currently enabled, or bound the disable busy-wait with a timeout?
- Should audio_device.c avoid re-activating an ISO EP that was never deactivated (the alt=0 path does not call usbd_edpt_close under ISO_ALLOC)?
Code quoted from src/portable/synopsys/dwc2/dcd_dwc2.c @ master. Happy to test patches.
How to reproduce ?
- Build a UAC2 speaker on ESP32-S2 (CFG_TUD_AUDIO, async ISO EP OUT + explicit feedback EP), 48 kHz / 16-bit / stereo. Set it as the Windows default playback device.
- Start audio playback (e.g. YouTube in Chrome).
- Pause long enough for the host to issue SET_INTERFACE alt=0 (Windows audio-engine idle timeout, ~10 s of silence).
- Resume playback -> host issues SET_INTERFACE alt=1.
=> Playback freezes ~5-10 s before audio resumes.
Debug Log as txt file (LOG/CFG_TUSB_DEBUG=2)
Device-side event trace (millis captured INSIDE the TinyUSB callbacks, so independent of host timing):
ms=1998 SET_INTERFACE(alt=1) entered audiod_set_interface()
ms=6998 tud_audio_set_itf_cb() reached <-- exactly 5000 ms later
ms=30038 SET_INTERFACE(alt=1) entered
ms=35039 tud_audio_set_itf_cb() reached <-- 5001 ms
ms=35040 SET_INTERFACE(alt=1) entered
ms=40043 tud_audio_set_itf_cb() reached <-- 5003 ms
No control requests are serviced during each ~5 s gap; millis() keeps advancing (no reset/reboot). After the activate-once workaround, the same alt=1 sequence completes within the same millisecond.
Screenshots
No response
I have checked existing issues, discussion and documentation
Operating System
Windows 11
Commit SHA
0.20.1 (as bundled in arduino-esp32 3.3.10)
Board
ESP32-S2 (generic ESP32-S2 Dev Module), Synopsys dwc2 DCD, USB-OTG Full-Speed
Firmware
Custom UAC2 speaker (sink): CFG_TUD_AUDIO=1, EP OUT + explicit async feedback EP, 48kHz/16-bit/stereo, using the stock audio_device.c.
Note: arduino-esp32 does not ship CFG_TUD_AUDIO (see espressif/arduino-esp32#12053), so audio_device.c here is user-supplied; but the root cause below is in dcd_dwc2.c which IS shipped.
What happened ?
A UAC2 speaker (sink) on ESP32-S2 stalls ~5 s every time the host re-opens the audio streaming interface (SET_INTERFACE alt=0 then alt=1) -- e.g. when resuming playback after the host closed the stream on idle/pause.
audiod_set_interface() calls usbd_edpt_iso_activate() again on the ISO OUT (and feedback) endpoints. On dwc2, dcd_edpt_iso_activate() disables the EP first:
edpt_disable() for an OUT endpoint busy-waits on the disable handshake:
When re-activating an IDLE ISO OUT EP (no tokens arrive during the brief alt toggle), this handshake does not complete promptly -- measured ~5000 ms per call on ESP32-S2 -- and it runs in the usbd task, so the device stops servicing ALL USB (control + every EP) for that window. The host restart is alt=1 -> alt=0 -> alt=1, so total user-visible stall is ~10 s.
Real-world symptom: ESP32-S2 USB DAC set as the Windows default output. Pause YouTube in Chrome for >~10 s (Windows audio engine then closes the stream). Press play -> audio and the A/V-synced video freeze ~10 s before resuming. A built-in sound card never does this close/reopen cycle, so it never stalls.
Confirmed it is NOT USB selective suspend: reproduced with the device kept awake (CDC interface held open), tud_suspended() == false throughout, device millis() never resets.
Tested workaround (application side): with TUP_DCD_EDPT_ISO_ALLOC, the alt=0 close path does not deactivate the EP hardware, so re-activation is both redundant and triggers the slow disable. Activating each ISO EP only once eliminates the stall:
Resume latency dropped from >9.7 s to ~0.5 s.
Questions for maintainers:
Code quoted from src/portable/synopsys/dwc2/dcd_dwc2.c @ master. Happy to test patches.
How to reproduce ?
=> Playback freezes ~5-10 s before audio resumes.
Debug Log as txt file (LOG/CFG_TUSB_DEBUG=2)
Device-side event trace (millis captured INSIDE the TinyUSB callbacks, so independent of host timing):
No control requests are serviced during each ~5 s gap; millis() keeps advancing (no reset/reboot). After the activate-once workaround, the same alt=1 sequence completes within the same millisecond.
Screenshots
No response
I have checked existing issues, discussion and documentation