ESP32-S3 UAC1 AudioSource capture XRUN / ISO IN endpoint becomes unavailable with `embassy-usb-synopsys-otg`
Summary
I am trying to implement a USB Audio Class 1 microphone on an ESP32-S3 using embassy-usb and embassy-usb-synopsys-otg.
The device enumerates successfully as a USB microphone, but starting capture with ALSA arecord frequently fails immediately.
The observable behavior is:
- the device enumerates correctly;
- ALSA opens the UAC1 capture interface;
- the isochronous IN stream does not start correctly;
arecordenters XRUN/overrun state without receiving audio data;- usbmon shows ISO IN URBs failing with
-ENOENT; - the kernel sometimes reports
usb_set_interface failed (-110); - the problem can happen before the first microphone
audio.write()completes.
I originally reproduced this using embassy-usb-synopsys-otg 0.3.3.
While investigating, I found several recent changes in embassy-usb-synopsys-otg that look directly related to ISO IN recovery, especially the switch from EOPF-based recovery to IISOIXFR.
I am not sure whether this is:
- a bug in the Synopsys OTG driver,
- a problem in the current
embassy-usbUAC1AudioSourcedescriptor, - an ESP32-S3 /
esp-halintegration problem, - or a misunderstanding in my UAC1 implementation.
I would appreciate some guidance.
Environment
Hardware:
- ESP32-S3
- native USB Full Speed peripheral
- INMP441 I2S microphone
- 48 kHz audio
Software:
- Rust / Embassy
esp-hal 1.2.0esp-rtos 0.4.0- Linux / ALSA
- direct ALSA capture using
arecord
Embassy revision currently being tested:
cd7570483a7036f23a9925a339152396bec4c041Repository:
https://github.com/segfau-yama/dxxk_mouse
The standalone reproducer is:
dick_mouse/examples/microphone_sample.rsAudio format
The standalone microphone uses:
UAC1
48000 Hz
16-bit PCM
USB Audio Source / captureThe microphone input itself comes from an INMP441 over I2S.
For debugging purposes, the USB problem does not appear to depend on the actual microphone samples. The failure happens around stream startup / endpoint activation.
Host-side symptoms
A typical kernel log contains:
usb 1-3: 1:0: usb_set_interface failed (-110)
usb 1-3: 1:1: cannot set freq 48000 to ep 0x81After disconnecting and reconnecting:
usb 1-3: new full-speed USB device
Product: UAC1 microphone sampleand later:
usb 1-3: 1:1: cannot set freq 48000 to ep 0x81
usb 1-3: 1:0: usb_set_interface failed (-110)Direct ALSA recording enters XRUN state immediately.
For example, the PCM state was approximately:
state : XRUN
trigger_time: 0.000000
delay : 0
avail : 0
avail_max : 0This does not look like a normal capture overrun where data filled the ALSA buffer.
It looks like the PCM stream never successfully started.
usbmon result
During the failure I see ISO IN submissions followed by errors such as:
S Zi:1:013:1 -115:1:895 1 -18:0:96 96 <
E Zi:1:013:1 -2 0
S Zi:1:013:1 -115:1:896 1 -18:0:96 96 <
E Zi:1:013:1 -2 0There are no successful ISO IN completions in the failing section.
As far as I understand:
-2 = ENOENTand snd-usb-audio effectively sees the endpoint as unavailable/not enabled.
Interesting behavior
PipeWire / PulseAudio compatibility paths have sometimes been able to capture from the same device while direct arecord using the ALSA hardware device fails.
This makes me suspect that endpoint activation timing or an altsetting transition may be involved rather than the descriptor always being completely unusable.
AudioSource descriptor question
I am currently using Embassy's standard:
embassy_usb::class::uac1::source::AudioSourceAt revision:
cd7570483a7036f23a9925a339152396bec4c041AudioSource::create_streaming_iface_active() allocates:
Isochronous IN: audio data
Isochronous IN: feedbackand describes the audio endpoint as:
SynchronizationType::Asynchronous,
UsageType::DataEndpoint,with:
bSynchAddress = feedback IN endpointThe feedback endpoint itself is another IN endpoint.
The relevant topology therefore appears to be:
Device
├── ISO IN audio
│ └── asynchronous data endpoint
│
└── ISO IN feedbackI may be misunderstanding UAC1 synchronization semantics, but my understanding is that an asynchronous source communicates its rate implicitly through the amount of data sent per USB frame.
Explicit feedback is normally needed for an asynchronous sink, while an adaptive source would use an associated synchronization OUT endpoint.
If that understanding is correct, is an asynchronous Audio Source with a feedback IN endpoint intentional here?
I also noticed that the class-specific endpoint descriptor currently advertises:
0x01 // sampling frequency controlwhich causes Linux to attempt endpoint sample-rate control.
Is this also intentional for AudioSource?
Feedback endpoint observation
Initially I was writing feedback values from the microphone task.
Because the microphone feedback endpoint is an IN endpoint, I considered the following sequence:
SET_INTERFACE alt 1
↓
audio and feedback endpoints become enabled
↓
feedback task calls feedback.write()
↓
Linux does not queue/poll that endpoint for async capture
↓
Incomplete Isochronous IN TransferTo remove this variable I stopped submitting microphone feedback transfers.
The feedback endpoint still exists in the standard AudioSource descriptor, but the application does not call feedback_ep_in.write().
The capture startup issue has still been difficult to diagnose.
embassy-usb-synopsys-otg investigation
While investigating the driver I compared:
0.3.3
0.4.0
current gitThe ISO IN recovery code in 0.3.3 / 0.4.0 appears to use EOPF.
A newer Embassy commit changed this to use:
IISOIXFR
Incomplete Isochronous IN Transferinstead.
The commit message is particularly interesting because it mentions that the previous behavior could make:
a microphone go silent
The new driver also appears to have more robust IN endpoint abort handling:
SNAK
↓
wait for INEPNE
↓
EPDIS
↓
wait for EPDISDinstead of assuming the endpoint stopped immediately.
This looks very relevant to the behavior I am seeing.
Difficulty testing current embassy-usb-synopsys-otg
I tried replacing the released Synopsys OTG dependency with the git revision above.
However, esp-hal 1.2.0 currently targets the released API and does not compile directly against the current Embassy implementation.
For example:
error[E0063]: missing field `tx_fifo_count` in initializer of `OtgInstance`
error[E0061]: this function takes 1 argument but 0 arguments were supplied
StateStorage::new()
error[E0061]: this function takes 1 argument but 0 arguments were supplied
HostStateStorage::new()The current Synopsys driver requires newer API elements such as:
StateStorage::new(CriticalSectionRawMutex::new())and:
OtgInstance {
...
tx_fifo_count: ...
}I therefore started implementing a small ESP32-S3-specific adapter in my application so that:
esp-hal
→ released embassy-usb-synopsys-otg
my USB device adapter
→ current git embassy-usb-synopsys-otgcan coexist.
However, at this point I am concerned that I am debugging the integration layer instead of the original USB Audio problem.
Questions
I would appreciate guidance on the following:
Is the current
AudioSourcedescriptor topology intentional?Specifically:
asynchronous ISO IN audio endpoint + ISO IN feedback endpointShould a UAC1 asynchronous capture/source endpoint have an explicit feedback IN endpoint at all?
Is
bSynchAddresspointing from the asynchronous capture IN endpoint to another IN endpoint expected?Is
bmAttributes = 0x01/ Sampling Frequency Control intentional for the currentAudioSourceendpoint?Does the newer
IISOIXFRrecovery fix target the same kind of failure where an ESP32-S3 ISO IN endpoint becomes unavailable after an incomplete transfer?Is there currently a recommended way to use the git version of
embassy-usb-synopsys-otgwithesp-hal 1.2.0, or doesesp-halneed to be updated first?If the UAC1 descriptor is correct, what would be the best way to instrument the Synopsys OTG driver to determine why the ISO IN endpoint becomes disabled?
Expected behavior
After the host selects the active AudioStreaming alternate setting:
SET_INTERFACE → alt 1I expect:
EP 0x81 enabled
↓
host queues ISO IN URBs
↓
audio.write() sends PCM packets
↓
arecord receives continuous audiowithout the endpoint entering a disabled state.
Actual behavior
What I currently observe is closer to:
SET_INTERFACE / stream start
↓
ISO IN endpoint becomes unavailable
↓
host ISO URBs fail with ENOENT
↓
no PCM reaches ALSA
↓
ALSA enters XRUN
↓
sometimes usb_set_interface times out with -110Additional note
I am happy to reduce the reproducer further if needed.
The most important goal for me right now is to determine whether the failure is in:
embassy-usb UAC1 AudioSourceor:
embassy-usb-synopsys-otg / ESP32-S3 DWC2 handlingbefore adding more application-side workarounds.
Thanks.
Source: embassy-rs/embassy