#6935·embassy

ESP32-S3 UAC1 AudioSource capture XRUN / ISO IN endpoint becomes unavailable with `embassy-usb-synopsys-otg`

Author: segfau-yamaCreated Sep 2, 2026Updated Sep 2, 2026

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;
  • arecord enters 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:

  1. a bug in the Synopsys OTG driver,
  2. a problem in the current embassy-usb UAC1 AudioSource descriptor,
  3. an ESP32-S3 / esp-hal integration problem,
  4. 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.0
  • esp-rtos 0.4.0
  • Linux / ALSA
  • direct ALSA capture using arecord

Embassy revision currently being tested:

cd7570483a7036f23a9925a339152396bec4c041

Repository:

https://github.com/segfau-yama/dxxk_mouse

The standalone reproducer is:

dick_mouse/examples/microphone_sample.rs

Audio format

The standalone microphone uses:

UAC1
48000 Hz
16-bit PCM
USB Audio Source / capture

The 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 0x81

After disconnecting and reconnecting:

usb 1-3: new full-speed USB device
Product: UAC1 microphone sample

and 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   : 0

This 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 0

There are no successful ISO IN completions in the failing section.

As far as I understand:

-2 = ENOENT

and 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:

rust
embassy_usb::class::uac1::source::AudioSource

At revision:

cd7570483a7036f23a9925a339152396bec4c041

AudioSource::create_streaming_iface_active() allocates:

Isochronous IN: audio data
Isochronous IN: feedback

and describes the audio endpoint as:

rust
SynchronizationType::Asynchronous,
UsageType::DataEndpoint,

with:

bSynchAddress = feedback IN endpoint

The feedback endpoint itself is another IN endpoint.

The relevant topology therefore appears to be:

Device
 ├── ISO IN audio
 │    └── asynchronous data endpoint
 │
 └── ISO IN feedback

I 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:

rust
0x01 // sampling frequency control

which 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 Transfer

To 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 git

The 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 Transfer

instead.

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 EPDISD

instead 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:

rust
StateStorage::new(CriticalSectionRawMutex::new())

and:

rust
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-otg

can 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:

  1. Is the current AudioSource descriptor topology intentional?

    Specifically:

    asynchronous ISO IN audio endpoint
    +
    ISO IN feedback endpoint
  2. Should a UAC1 asynchronous capture/source endpoint have an explicit feedback IN endpoint at all?

  3. Is bSynchAddress pointing from the asynchronous capture IN endpoint to another IN endpoint expected?

  4. Is bmAttributes = 0x01 / Sampling Frequency Control intentional for the current AudioSource endpoint?

  5. Does the newer IISOIXFR recovery fix target the same kind of failure where an ESP32-S3 ISO IN endpoint becomes unavailable after an incomplete transfer?

  6. Is there currently a recommended way to use the git version of embassy-usb-synopsys-otg with esp-hal 1.2.0, or does esp-hal need to be updated first?

  7. 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 1

I expect:

EP 0x81 enabled
        ↓
host queues ISO IN URBs
        ↓
audio.write() sends PCM packets
        ↓
arecord receives continuous audio

without 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 -110

Additional 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 AudioSource

or:

embassy-usb-synopsys-otg / ESP32-S3 DWC2 handling

before adding more application-side workarounds.

Thanks.