This example demonstrates how to use TinyUSB's USB Audio Host driver (TUH_AUDIO) to capture audio from a UAC 1.0 or UAC 2.0 USB microphone and echo it back to the speaker, using a WASAPI/ALSA-like high-level API. The application never touches USB interfaces, alternate settings, or endpoint addresses — it only selects supported {format, sample_rate, channels} configurations by stream index.
This example supports UAC1 devices whose Type I Format descriptor lists discrete sampling frequencies (bSamFreqType > 0) and UAC2 devices using a directly connected Clock Source, such as:
The echo needs a matching S16_LE playback stream at the capture sample rate; devices without one run capture-only. The sample rate and channel preferences are configured by the SAMPLE_RATES / AUDIO_MAX_CHANNELS macros in src/audio_app.c (44.1 kHz stereo by default). Non-PCM formats are rejected by the driver.
bSamFreqType == 0 are unsupported; the driver requires a list of discrete sampling frequencies.CFG_TUH_AUDIO_MAX_SAM_FREQ discrete configurations. A read-only Clock Source exposes only its current frequency.MaxPacketsOnly endpoint attribute is not supported. OUT transfers are not padded to wMaxPacketSize, and padding in IN transfers is not removed from the reported audio data.cd examples/host/audio_host mkdir -p build && cd build cmake -DBOARD=<your_board> -G Ninja .. cmake --build .
Replace <your_board> with your target board name (e.g., raspberry_pi_pico, stm32f407disco, etc.)
cd examples/host/audio_host make BOARD=<your_board> all
# Using CMake: list the board-specific flash targets, then select one ninja -t targets ninja audio_host-jlink # example for a board with J-Link support # Using Make make BOARD=<your_board> flash
audio_app_task() at their half-full/half-drained watermarks; a sine test tone plays on the playback stream when no capture stream is echoingtuh_audio_start() / tuh_audio_stop(); their asynchronous results are printed from tuh_audio_event_cb(), and a failed stream is restarted automatically after 100 msTinyUSB Host USB Audio Example
Connect a USB Audio Device (UAC 1.0 or 2.0) to test
Audio device mounted: idx=0 addr=1
capture stream 1, configurations: 2
master mute supported
volume range: min=-23040 max=1536 res=256 (1/256 dB)
[0] format=1 rate=44100 channels=2
[1] format=1 rate=48000 channels=2
playback stream 0, configurations: 2
master mute supported
volume range: min=-23040 max=1536 res=256 (1/256 dB)
[0] format=1 rate=44100 channels=2
[1] format=1 rate=48000 channels=2
Configuring 44100 S16_LE capture (2 channels)
Microphone configured
Microphone master mute: off
Microphone master volume: 0 (1/256 dB)
Microphone volume set: -1536 (1/256 dB)
Configuring 44100 S16_LE playback (2 channels)
Speaker configured
Speaker master mute: off
Speaker master volume: 0 (1/256 dB)
Speaker volume set: -1536 (1/256 dB)
Edit src/tusb_config.h to modify:
CFG_TUH_AUDIO_MAX: Maximum number of audio devices supportedCFG_TUH_AUDIO_PROTOCOLS: Bitmask selecting UAC1 and/or UAC2 support; the example enables bothCFG_TUH_AUDIO_MAX_SAM_FREQ: Maximum number of discrete frequencies retained per alternate setting or UAC2 Clock SourceCFG_TUH_AUDIO_EPIN_BUFSIZE: Maximum size of one capture transfer the driver submits (configurations needing a larger per-poll-interval packet are rejected)CFG_TUH_AUDIO_EPOUT_BUFSIZE: Maximum size of one playback transfer the driver submitsCFG_TUH_AUDIO_STREAM_BUFSIZE: Per-stream FIFO depth in bytes (default 1024, i.e. four 256 B packets); capture overwrites the oldest frames when fulltuh_audio_descriptor_cb() exposes the validated Audio Control descriptor block during enumeration. Applications that need raw entity controls must copy the required entity IDs or descriptor fields before the callback returns, then use tuh_audio_control_xfer() after the device mounts.bInterval. tuh_audio_capture_cb() / tuh_audio_playback_cb() only count completed transfers; audio_app_task() services the FIFOs independently from the main loop. tuh_audio_event_cb() reports asynchronous start/stop results and unrecoverable transfer failures. The example restarts a failed stream automatically 100 ms later.tuh_audio_read() / tuh_audio_write() are non-blocking FIFO operations: they return the number of whole frames actually read/queued. tuh_audio_read_available() reports captured frames ready to read; tuh_audio_write_available() reports free playback capacity. tuh_audio_write() only queues data; the playback transfer-completion chain sends it, or sends silence when the FIFO does not contain a complete polling interval without consuming the partial data.tuh_task() continuously; the capture FIFO absorbs short scheduling gaps and overwrites the oldest frames when full.