Skip to content

USB audio (UAC)

Boards with a USB OTG port can present themselves to an attached computer as a stereo USB speaker. Audio sent by the host plays through the same output path AirPlay uses, sharing the DAC's DSP, EQ and volume control.

Needs USB OTG and a device-role port

Only the ESP32-S2, S3 and P4 have a USB OTG peripheral. The D+/D- pins must also be routed to a connector wired for device role (CC pulldowns). The original ESP32 cannot do this at all.

The board enumerates as a composite device: a USB Audio Class speaker plus an HID consumer-control interface that sends media keys back to the host.

How it works

  • AirPlay and USB audio are mutually exclusive at runtime, much like Bluetooth
  • AirPlay is suspended as soon as the host starts streaming
  • The output is handed back once the host stream has been idle for CONFIG_USB_AUDIO_SINK_IDLE_MS, 2000 ms by default
  • Host volume and mute are applied to the DAC, so the computer's own volume slider works
  • Hardware buttons send play/pause, track skip, volume and mute to the host over HID, since UAC itself carries no transport controls

Sample rate

USB audio runs at 48 kHz. CONFIG_UAC_SAMPLE_RATE must equal CONFIG_OUTPUT_SAMPLE_RATE_HZ, because nothing resamples on this path and the descriptor advertises a single fixed rate.

44100 Hz does not work

usb_device_uac derives its FIFO drain rate from sample_rate / 1000, an integer division. At 44100 that truncates 44.1 frames per millisecond to 44. The feedback endpoint uses AUDIO_FEEDBACK_METHOD_FIFO_COUNT, so that FIFO is the control variable and the host settles at 44000 frames/s while I2S consumes 44100. The resulting deficit of roughly 100 frames per second slowly drains the buffer and then glitches continuously. 48000 divides into 1 ms frames exactly.

TAS58xx boards default to 48 kHz for this reason, and because the driver's EQ coefficients are computed for 48 kHz. AirPlay's 44.1 kHz stream is resampled on the way out.

Build environments

Environment Board
esp32s3-uac ESP32-S3 + PCM5102A
esparagus-audio-brick-dual-uac Esparagus Audio Brick Dual

USB audio is enabled by layering config/sdkconfig.defaults.uac onto a board's defaults. To add it to a custom board, put that file last in your SDKCONFIG_DEFAULTS chain so it can override the sample rate.

Device name

CONFIG_USB_AUDIO_SINK_PRODUCT sets the name the host displays. It is used both for the product string and for the audio interface, which is what Windows Device Manager shows for a composite function.

Windows caches the name

Windows stores descriptor strings per VID/PID/revision, so after changing the name it may keep showing the old one. Bump bcdDevice in main/usb/usb_descriptors.c to force a re-read.

Caveats

  • macOS needs CONFIG_UAC_SUPPORT_MACOS=y. A single descriptor cannot satisfy macOS and Windows/Linux simultaneously, so this is a build-time choice.
  • No USB console. TinyUSB claims the USB PHY, so USB-Serial-JTAG cannot also act as a console. Logs stay on UART0, and you must hold BOOT while resetting to reflash over USB. OTA updates avoid the problem entirely.