Warning
This project is new and still under active testing. It has been tested on an ESP32-S3 with a miniDSP 2x4 HD and works, but expect breaking changes, behavior changes, and documentation updates as the driver is validated on more hardware.
USB Audio Class 2.0 host driver for ESP32-S3. ESP-IDF component (C).
Generic UAC2 driver for ESP32-S3, currently validated for single-clock UAC2 devices such as the miniDSP 2x4 HD. Primary use case: playing measurement sweeps through a miniDSP 2x4 HD from an ESP32 for automated sub optimization.
v0.1.2 — validated on current hardware, still under active testing. The 12-test harness passes on both the ESP32-to-ESP32 simulator and a real miniDSP 2x4 HD, with recent reruns also passing suspend/resume, duplex-guard, and hot-unplug/replug recovery. Current support is focused on single-clock UAC2 devices. Builds clean with -Werror -Wextra.
See PROGRESS.md for detailed history.
The component itself lives in components/uac2_host. The rest of this repository is development and validation support:
- main is the hardware test harness
- simulators contains ESP32-S3 UAC2 simulator firmware
- ref contains read-only reference code and captured descriptors
- docs contains design notes and publish-prep tracking
The driver is not being published yet. The repo is being prepared so the component can be uploaded later without last-minute packaging work.
- Espressif class driver pattern (
uac2_host_install/uac2_host_uninstall) with internal device discovery - UAC2 descriptor parsing (clock sources, selectors, multipliers, terminals, feature units, AS interfaces)
- Clock topology walk (terminal→selector/multiplier→clock source)
- Clock control: get/set sample rate, query supported ranges, check clock validity
- SET_INTERFACE for proper endpoint activation/deactivation
- Isochronous TX (playback) and RX (capture) with ring buffer
- Feedback endpoint handling (16.16 format, confirmed with real XMOS hardware) with adaptive packet sizing
- Volume/mute control via feature unit with bmaControls validation and range caching
- Suspend/resume without URB/ringbuf reallocation
- First-frame timestamp (microsecond precision, for measurement sync)
- Transfer error limiting (auto-stops after consecutive errors, fires STREAM_ERROR event)
- Endpoint halt/flush/clear on stream stop
- Atomic in-flight URB tracking for crash-free disconnect
- Per-stream spinlock-protected state transitions
#include "usb/uac2_host.h"
// Install the driver (creates internal USB Host client)
uac2_host_driver_config_t config = {
.create_background_task = true,
.callback = device_event_cb,
.callback_arg = NULL,
};
uac2_host_install(&config);
// In the driver callback, record addr/iface_num and notify a worker task.
// The callback runs on the USB Host event task and must not block.
enqueue_open_request(addr, iface_num);
// In a normal task, open the interface and start playback.
uac2_host_device_handle_t dev;
uac2_host_device_open(&open_config, &dev);
uac2_host_device_start(dev, &stream_config);
// Write PCM data (fills ring buffer, blocks if full)
uac2_host_device_write(dev, pcm_data, num_bytes, timeout_ms);
// Stop and close
uac2_host_device_stop(dev);
uac2_host_device_close(dev);
// When done
uac2_host_uninstall();For local development today, add the component directly from this repository:
dependencies:
idf: ">=5.4"
uac2_host:
path: ../../components/uac2_hostYou can also add it via EXTRA_COMPONENT_DIRS:
set(EXTRA_COMPONENT_DIRS "/path/to/esp-uac2-host/components/uac2_host")After the component is eventually published to the ESP Component Registry, the planned install flow will be:
idf.py add-dependency "averyy/usb_host_uac2^0.1.2"The namespace and component name are final, but the component is intentionally not published yet.
A registry-style standalone example is included at components/uac2_host/examples/basic_playback. It waits for a UAC2 playback interface, opens it, and streams a 48 kHz / 24-bit stereo sine wave.
components/uac2_host/ # The driver (ESP-IDF component)
include/usb/uac2_host.h # Public API
include/usb/uac2_desc.h # Descriptor structs
uac2_host.c # Driver implementation
uac2_desc.c # Descriptor parser
Kconfig # Tunable parameters (menuconfig)
idf_component.yml # Component registry manifest
README.md # Component registry page content
examples/ # Standalone component examples
main/ # Test harness (12 automated tests)
main.c # Enumeration + streaming test suite
tone_gen.c/h # Sine wave generator
simulators/
simple/ # Minimal UAC2 simulator (TinyUSB)
minidsp-2x4hd/ # Full miniDSP 2x4 HD simulator (audio + HID + fault injection)
ref/ # Reference code (read-only, not compiled)
docs/ # Design docs and remaining work
- MCU: ESP32-S3-DevKitC-1
- USB: Full Speed (12 Mbps) — sufficient for 48kHz/24-bit/stereo (25% bus utilization)
- Simulator: Second ESP32-S3 running
simulators/minidsp-2x4hd/firmware - Target device: miniDSP 2x4 HD (XMOS XU216, UAC2, VID 0x2752 PID 0x0011)
Requires ESP-IDF v5.4.
# Activate ESP-IDF
export PATH="/opt/homebrew/bin:$PATH" && . ~/esp/esp-idf/export.sh
# Build and flash the host
idf.py build
idf.py -p /dev/cu.usbmodem<SERIAL> flash
# Build and flash the simulator
cd simulators/minidsp-2x4hd
idf.py build
idf.py -p /dev/cu.usbmodem<SERIAL> flashIn sdkconfig.defaults:
CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=512
CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT=y
The test suite runs automatically on boot against any connected UAC2 device (12 tests):
- 48kHz streaming — 10 seconds, 1kHz tone
- Volume/mute control — set/get during streaming
- Stop/restart cycle — 5s stream → stop → 2s pause → 5s stream
- 44.1kHz streaming — 5-second 44.1kHz switch smoke test
- Start time precision — microsecond timestamp verification
- Feedback presence & clock validity — verify expected feedback stabilizes near the target value and the clock reports valid
- 16-bit mode — alternate format streaming
- Rapid measurement cycles — 9x rapid start/stop
- Volume/mute channel exploration — per-channel set/get/readback/restore checks
- Sample rate switch stress — repeated 48kHz↔44.1kHz switch attempts while streaming
- Ring buffer starvation/recovery — underrun and recovery test
- Long-running stability — configurable soak duration via
CONFIG_UAC2_TEST12_DURATION_SEC(Kconfig default 1 hour; the checked-in reposdkconfigcurrently pins this to 20 seconds for smoke reruns; 766s sustained run verified, repeated hot-unplug/replug recovery verified)
For short local smoke runs on the ESP32-to-ESP32 simulator setup, build the host app with:
idf.py -B build-smoke -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.smoke.defaults" build flash
- The boot-time harness is automated on device startup, but real miniDSP hot-unplug/replug validation is still manual hardware testing rather than CI automation.
- Recent real miniDSP reruns also pass the harness's live suspend/resume and duplex-guard checks after the 12 numbered tests.
- Active miniDSP hot-unplug/replug during isochronous playback is verified working on ESP-IDF v5.4 with this driver's teardown path. The driver still retains
usb_host_interface_release()retry handling for ESP-IDF bug#17707, but normal close no longer emits repeated retry warnings.
- ESP32-S3 cannot do simultaneous playback and capture for typical UAC2 packet sizes because of USB FIFO limits. The driver now rejects opposite-direction stream activation at runtime.
- Public support is currently limited to single-clock UAC2 devices.
- This component is UAC2-only. It does not provide a UAC1 fallback path.
MIT