summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorZhang, Zhenjiang <[email protected]>2026-08-14 11:10:23 +0800
committerZhang, Zhenjiang <[email protected]>2026-08-14 11:17:00 +0800
commitc950109bcef6404971c3609a6e4caeb0421120b8 (patch)
treedb2c23a460ff6887395140d458c5f552a5322204 /examples
parente9578eb103a90d5ab059a067b2e17ca1ddcedee5 (diff)
feat(class/audio): rework TUH_AUDIO into a WASAPI/ALSA-like stream API
Provide a high-level audio streaming API over UAC 1.0 devices while keeping the USB topology private: applications select supported {format, sample_rate, channels} configurations per logical stream, and the driver owns the mapping to AS interface, alternate setting, and endpoint. - One logical stream per direction per instance; multiple AS interfaces and alternate settings in a direction are merged into the stream's configuration list (discrete tuples; continuous ranges exposed as a single configuration at the top rate) - Asynchronous tuh_audio_configure(): SET_INTERFACE to the selected alternate setting, open/reconfigure the endpoint, set the sampling frequency when supported, initialize the FIFO and packet scheduler, then invoke the completion callback - Frame-based FIFO streaming: tuh_audio_read()/tuh_audio_write() queue whole frames; the driver owns transfer replenishment and fractional packet scheduling (44.1 kHz pays back the 0.1 frame/ms remainder via an accumulator for exact average pacing) - tuh_audio_start()/tuh_audio_stop() activate/deactivate the stream interface through SET_INTERFACE (alt n / alt 0) Driver correctness fixes: - Parse only the AC header's interface collection; MIDI Streaming and other subclasses are skipped - Keep every discrete format as a separate configuration; endpoints are opened only for the alternate setting selected by tuh_audio_configure() - Check tuh_interface_set() return values and SET_INTERFACE transfer results instead of ignoring failures - Validate instance state, direction, buffers, and frame counts in every transfer API - Feature Unit requests use the control's real width (mute/AGC/loudness 1 byte, others 2 bytes) and convert multibyte values to host order - Failed/stalled/aborted isochronous transfers reach only the error callback, never the capture/playback callbacks The audio_host example uses the new API: 48 kHz stereo by default, automatic stream restart on error callbacks, a sine test tone on the playback stream, and periodic mic-only / spk-only / echo phase switching.
Diffstat (limited to 'examples')
-rw-r--r--examples/host/CMakeLists.txt1
-rw-r--r--examples/host/audio_host/README.md67
-rw-r--r--examples/host/audio_host/src/app.h5
-rw-r--r--examples/host/audio_host/src/audio_app.c558
-rw-r--r--examples/host/audio_host/src/main.c32
-rw-r--r--examples/host/audio_host/src/tusb_config.h8
6 files changed, 469 insertions, 202 deletions
diff --git a/examples/host/CMakeLists.txt b/examples/host/CMakeLists.txt
index 7c74e3c73..0e877cb78 100644
--- a/examples/host/CMakeLists.txt
+++ b/examples/host/CMakeLists.txt
@@ -7,6 +7,7 @@ family_initialize_project(tinyusb_host_examples ${CMAKE_CURRENT_LIST_DIR})
# family_add_subdirectory will filter what to actually add based on selected FAMILY
set(EXAMPLE_LIST
+ audio_host
bare_api
cdc_msc_hid
cdc_msc_hid_freertos
diff --git a/examples/host/audio_host/README.md b/examples/host/audio_host/README.md
index 072adbbf9..2ad3f40d6 100644
--- a/examples/host/audio_host/README.md
+++ b/examples/host/audio_host/README.md
@@ -1,22 +1,25 @@
# USB Audio Host Example
-This example demonstrates how to use TinyUSB's USB Audio Host driver (TUH_AUDIO) to communicate with a UAC 1.0 compatible USB Audio Device.
+This example demonstrates how to use TinyUSB's USB Audio Host driver (TUH_AUDIO) to capture audio from a UAC 1.0 compatible 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.
## Features
- Enumerates and mounts USB Audio Class 1.0 devices
-- Receives audio data from IN endpoint (e.g., microphone)
-- Sends audio data to OUT endpoint (e.g., speaker)
-- Sets sampling frequency via control requests
-- Demonstrates isochronous transfer handling
+- Discovers the device's logical streams (capture/playback) and their supported configurations (discrete tuples only)
+- Configures and starts an S16_LE capture stream (48 kHz preferred, 44.1 kHz fallback; stereo preferred, mono accepted)
+- Echoes captured audio to an S16_LE playback stream at the same sample rate (same channel count preferred, mono/stereo conversion otherwise)
+- Frame-based FIFO API: `tuh_audio_read()` / `tuh_audio_write()` queue frames; the driver schedules the 1 ms isochronous transfers
+- Cycles the streams through three phases (5 s each): mic-only (capture, data dropped), spk-only (sine test tone), and echo (capture looped back to playback)
## Supported Devices
-This example supports any UAC 1.0 compliant USB Audio device, such as:
+This example supports any UAC 1.0 compliant USB audio device with a discrete sampling-frequency capture stream, such as:
- USB microphones
-- USB speakers/headphones
+- USB headsets (mono microphone + speaker)
- USB audio interfaces
+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` (48 kHz stereo by default). Continuous sampling-frequency ranges are exposed as a single configuration at the range's highest frequency (e.g. a 8000–48000 Hz speaker appears as 48000 Hz); non-PCM formats are rejected by the driver.
+
## Building
### Using CMake (recommended)
@@ -53,46 +56,40 @@ make BOARD=<your_board> flash
2. Connect a USB Audio device (UAC 1.0) to the USB host port
3. Open a serial terminal to view output
4. The example will:
- - Print device information when mounted
- - Set sampling frequency based on the device's advertised capabilities
- - Receive audio samples from the device (IN endpoint)
- - Loop back received audio to the device (OUT endpoint) for testing
+ - Print each stream's supported configurations when mounted
+ - Look for an S16_LE capture configuration at a preferred sample rate (48 kHz first, 44.1 kHz fallback; stereo preferred, mono accepted) and configure it
+ - Echo captured audio to an S16_LE playback configuration at the same sample rate (same channel count preferred, converted otherwise)
+ - Drain the capture FIFO in `audio_app_task_read()` and queue the frames into the playback FIFO; a sine test tone plays on the playback stream when no capture stream is echoing
+ - Cycle through the three phases (mic-only / spk-only / echo, 5 s each) with `tuh_audio_start()` / `tuh_audio_stop()`; a failed stream is restarted automatically 100 ms after the error callback
## Serial Output Example
```
TinyUSB Host USB Audio Example
Connect a USB Audio Device (UAC 1.0) to test
-Audio device mounted: idx=0, daddr=1
- --- Microphone ---
- IN EP: 0x81 (max size: 192)
- Input Terminal: ID=1, Type=0x0201, Channels=1
- Format Type: 1, Channels: 1, SubFrameSize: 2, BitResolution: 16
- Sampling Freq: Discrete, count=4
- Freq[0]: 44100 Hz
- Freq[1]: 48000 Hz
- Freq[2]: 96000 Hz
- Freq[3]: 192000 Hz
- --- Speaker ---
- OUT EP: 0x02 (max size: 192)
- Output Terminal: ID=2, Type=0x0301
- Format Type: 1, Channels: 2, SubFrameSize: 2, BitResolution: 16
- Sampling Freq: Continuous range 8000 Hz - 48000 Hz
- Feature Unit: ID=3, SourceID=1
- Setting IN sampling frequency to 48000 Hz
- Setting OUT sampling frequency to 48000 Hz
- Sampling frequency set OK, ready for isochronous transfer
+Audio device mounted: idx=0 addr=1
+ capture stream 1 configurations: 2
+ [0] format=1 rate=44100 channels=2
+ [1] format=1 rate=48000 channels=2
+ playback stream 0 configurations: 2
+ [0] format=1 rate=44100 channels=2
+ [1] format=1 rate=48000 channels=2
+ Configuring 48 kHz S16_LE capture (2 channels)
+ Microphone configured, starting capture
+ Configuring 48 kHz S16_LE playback (2 channels)
+ Speaker configured, starting playback
```
## Configuration
Edit `src/tusb_config.h` to modify:
- `CFG_TUH_AUDIO_MAX`: Maximum number of audio devices supported
-- `CFG_TUH_AUDIO_EPIN_BUFSIZE`: IN endpoint buffer size
-- `CFG_TUH_AUDIO_EPOUT_BUFSIZE`: OUT endpoint buffer size
+- `CFG_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 submits
+- `CFG_TUH_AUDIO_STREAM_BUFSIZE`: Per-stream FIFO depth in bytes (default 1024, i.e. four 256 B packets)
## Notes
-- This example uses isochronous transfers which require precise timing
-- For production applications, synchronize audio transfers with the device's audio clock
-- The example sends a simple sine wave for testing; replace with actual audio data in real applications
+- While a stream is running, the driver keeps one isochronous transfer in flight and re-submits on completion, so transfers are naturally paced at the 1 ms USB frame rate. `tuh_audio_capture_cb()` / `tuh_audio_playback_cb()` report each completed transfer; `tuh_audio_err_cb()` reports failures. The example restarts the failed stream automatically 100 ms after the error callback.
+- `tuh_audio_read()` / `tuh_audio_write()` are non-blocking FIFO operations: they return the number of whole frames actually queued/read (0 when the FIFO is empty/full or the stream is not running), and `tuh_audio_read_available()` / `tuh_audio_write_available()` report the FIFO occupancy in frames.
+- Isochronous transfers require the host to poll `tuh_task()` continuously; the capture FIFO absorbs short scheduling gaps, but frames are dropped when it overflows.
diff --git a/examples/host/audio_host/src/app.h b/examples/host/audio_host/src/app.h
index a807aeaa6..3ebb5c16d 100644
--- a/examples/host/audio_host/src/app.h
+++ b/examples/host/audio_host/src/app.h
@@ -21,6 +21,7 @@
#include <stdbool.h>
#include <stdint.h>
-void audio_app_task(void);
-
+void audio_app_task_read(void);
+void audio_app_task_write(void);
+void defer_queue_task(void);
#endif
diff --git a/examples/host/audio_host/src/audio_app.c b/examples/host/audio_host/src/audio_app.c
index d9134f089..40ad620bf 100644
--- a/examples/host/audio_host/src/audio_app.c
+++ b/examples/host/audio_host/src/audio_app.c
@@ -15,6 +15,7 @@
*/
#include <stdio.h>
+#include <string.h>
#include "bsp/board_api.h"
#include "tusb.h"
#include "app.h"
@@ -23,199 +24,488 @@
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
//--------------------------------------------------------------------+
-static bool audio_mounted = false;
-static uint8_t audio_dev_addr = 0xFF;
-static volatile bool audio_ready = false; // Wait for sampling freq set before starting isochronous transfer
-static volatile bool audio_rx_busy = false; // Track IN endpoint transfer state
-static volatile bool audio_tx_busy = false; // Track OUT endpoint transfer state
-static uint8_t audio_idx = 0xFF;
-static uint8_t audiostream_in_idx = 0xFF;
-static uint8_t audiostream_out_idx = 0xFF;
-static uint32_t sampling_freq = 48000; // Default sampling frequency (Hz)
-static uint8_t audio_mic_channels = 1;
+// Default configuration of this example, adjust to the target device:
+// - AUDIO_MAX_FRAME_COUNT: buffer holds up to 48 frames (1 ms of 48 kHz)
+// - AUDIO_MAX_CHANNELS: maximum channels of the capture/playback stream
+// - SAMPLE_RATES: sample rates tried in order, first match wins (44.1 kHz stereo by default)
+#define AUDIO_MAX_FRAME_COUNT 48
+#define AUDIO_MAX_CHANNELS 2
+#define SAMPLE_RATES {48000, 44100}
+static uint8_t audio_idx = TUSB_INDEX_INVALID_8; // index of the selected audio device
+static uint8_t cap_stream_idx = TUSB_INDEX_INVALID_8; // capture stream index
+static uint8_t spk_stream_idx = TUSB_INDEX_INVALID_8; // playback stream index
+static bool mic_ready = false; // capture stream is running
+static bool spk_ready = false; // playback stream is running
+static int16_t mic_samples[AUDIO_MAX_FRAME_COUNT * AUDIO_MAX_CHANNELS]; // capture FIFO read buffer
+static int16_t spk_samples[AUDIO_MAX_FRAME_COUNT * AUDIO_MAX_CHANNELS]; // playback FIFO write buffer
+static tuh_audio_stream_config_t mic_config; // selected capture configuration
+static tuh_audio_stream_config_t spk_config; // selected playback configuration
+static uint32_t audio_frame_count = AUDIO_MAX_FRAME_COUNT; // frames per ms of the selected rate
+static uint32_t spk_cb_count = 0; // count of playback callbacks (for debug)
+static uint32_t mic_cb_count = 0; // count of capture callbacks (for debug)
+static uint32_t err_cb_count = 0; // count of error callbacks (for debug)
-static uint8_t audio_rx_buffer[CFG_TUH_AUDIO_EPIN_BUFSIZE] __attribute__((aligned(4)));
-static uint8_t audio_tx_buffer[CFG_TUH_AUDIO_EPOUT_BUFSIZE] __attribute__((aligned(4)));
//--------------------------------------------------------------------+
// Helper Functions
//--------------------------------------------------------------------+
-// Mono (96 bytes, 48 samples) -> Stereo (192 bytes)
-static void mono_to_stereo(const uint8_t *mono, uint8_t *stereo, uint16_t mono_samples) {
- for (uint16_t i = 0; i < mono_samples; i++) {
- // Copy 2 bytes (one int16 sample) to left channel
- stereo[i * 4] = mono[i * 2];
- stereo[i * 4 + 1] = mono[i * 2 + 1];
- // Copy same 2 bytes to right channel
- stereo[i * 4 + 2] = mono[i * 2];
- stereo[i * 4 + 3] = mono[i * 2 + 1];
+//--------------------------------------------------------------------+
+// Async Deferred Call Queue
+//--------------------------------------------------------------------+
+// Schedules one-shot callbacks to be invoked after a given delay in ms.
+// Processed by defer_queue_task() in the main loop, no dynamic allocation.
+
+#define APP_DEFER_QUEUE_SZ 4
+
+typedef void (*app_defer_func_t)(uintptr_t param);
+
+typedef struct {
+ app_defer_func_t func;
+ uintptr_t arg;
+ uint32_t at_ms;
+} app_defer_t;
+
+static app_defer_t _defer_q[APP_DEFER_QUEUE_SZ];
+
+// Clear all pending deferred callbacks.
+static void app_defer_queue_clear(void) {
+ memset(_defer_q, 0, sizeof(_defer_q));
+}
+
+// Schedule func to be called after 'ms' milliseconds, returns false if queue is full
+static bool app_defer_ms_async(uint32_t ms, app_defer_func_t func, uintptr_t arg) {
+ for (uint8_t i = 0; i < APP_DEFER_QUEUE_SZ; i++) {
+ if (_defer_q[i].func == NULL) {
+ _defer_q[i].func = func;
+ _defer_q[i].arg = arg;
+ // add one to ensure we wait at least 'ms' milliseconds
+ _defer_q[i].at_ms = tusb_time_millis_api() + ms + 1;
+ return true;
+ }
}
+ return false; // queue full
}
-// Print sampling frequency info for an AS interface
-static void print_sampling_freq(const tuh_audio_as_info_t *as) {
- if (as->sam_freq_type == 0) {
- printf(" Sampling Freq: Continuous range %lu Hz - %lu Hz\r\n", (unsigned long)as->sam_freq_lower,
- (unsigned long)as->sam_freq_upper);
- } else {
- printf(" Sampling Freq: Discrete, count=%u\r\n", as->sam_freq_type);
- for (uint8_t j = 0; j < as->sam_freq_type && j < CFG_TUH_AUDIO_MAX_SAM_FREQ; j++) {
- printf(" Freq[%u]: %lu Hz\r\n", j, (unsigned long)as->sam_freq[j]);
+// Invoke all callbacks whose delay has expired, must be called periodically from main loop
+void defer_queue_task(void) {
+ const uint32_t now_ms = tusb_time_millis_api();
+ for (uint8_t i = 0; i < APP_DEFER_QUEUE_SZ; i++) {
+ if (_defer_q[i].func != NULL && (int32_t)(_defer_q[i].at_ms - now_ms) <= 0) {
+ const app_defer_func_t func = _defer_q[i].func;
+ const uintptr_t arg = _defer_q[i].arg;
+ _defer_q[i].func = NULL; // free slot before invoking, callback may re-schedule
+ func(arg);
}
}
}
-// Print all AS interface info
-static void print_as_interfaces(uint8_t idx) {
- tuh_audio_as_info_t as = {};
- uint8_t as_count = tuh_audio_as_get_count(idx);
- for (uint8_t i = 0; i < as_count; i++) {
- tuh_audio_as_get_info(idx, i, &as);
- if (as.ep_dir == TUSB_DIR_IN) {
- // Save microphone channel count for mono-to-stereo conversion
- audio_mic_channels = as.num_channels;
- printf(" --- Microphone (AS %u) ---\r\n", i);
- printf(" IN EP: 0x%02x (max size: %u)\r\n", as.ep_addr, as.ep_size);
- } else {
- printf(" --- Speaker (AS %u) ---\r\n", i);
- printf(" OUT EP: 0x%02x (max size: %u)\r\n", as.ep_addr, as.ep_size);
+// Duplicate each mono sample to both channels (mono mic -> stereo speaker)
+static void mono_to_stereo(const int16_t *mono, int16_t *stereo, uint32_t frames) {
+ for (uint32_t i = 0; i < frames; i++) {
+ stereo[i * 2] = mono[i];
+ stereo[i * 2 + 1] = mono[i];
+ }
+}
+
+// Average both channels into one sample (stereo mic -> mono speaker)
+static void stereo_to_mono(const int16_t *stereo, int16_t *mono, uint32_t frames) {
+ for (uint32_t i = 0; i < frames; i++) {
+ mono[i] = (int16_t)(((int32_t)stereo[i * 2] + stereo[i * 2 + 1]) / 2);
+ }
+}
+
+// One period of an 8 kHz sine (6 samples at 48 kHz), scaled to ~8-bit
+// amplitude. The test tone plays only when no capture stream is echoing.
+static const int16_t sine_period[6] = {0, 221, 221, 0, -221, -221};
+
+// Precompute a sine wave into the playback buffer
+static void spk_init_sine(void) {
+ for (uint32_t i = 0; i < AUDIO_MAX_FRAME_COUNT; i++) {
+ const int16_t sample = sine_period[i % 6];
+ for (uint8_t ch = 0; ch < spk_config.channels; ch++) {
+ spk_samples[i * AUDIO_MAX_CHANNELS + ch] = sample;
}
- printf(" Interface: %u, Alt: %u\r\n", as.interface_num, as.alt_setting);
- printf(" Format Type: %u, Channels: %u, SubFrameSize: %u, BitResolution: %u\r\n", as.format_type,
- as.num_channels, as.sub_frame_size, as.bit_resolution);
- print_sampling_freq(&as);
}
}
+// Frames to queue this millisecond at the given sample rate: rate / 1000,
+// with the fractional remainder (0.1 frame per ms at 44.1 kHz) accumulated
+// and paid back as one extra frame, matching the driver's playback pacing.
+static uint32_t frame_rem_acc = 0;
+static uint32_t audio_frames_this_ms(uint32_t sample_rate) {
+ uint32_t frames = sample_rate / 1000;
+ frame_rem_acc += sample_rate % 1000;
+ if (frame_rem_acc >= 1000) {
+ frame_rem_acc -= 1000;
+ frames++;
+ }
+ return frames;
+}
+
//--------------------------------------------------------------------+
-// Application Task
+// Periodic Stream Switching
//--------------------------------------------------------------------+
-void audio_app_task(void) {
- if (!audio_mounted || !audio_ready) {
- return;
+// Cycles through three phases with tuh_audio_start()/stop(). The driver
+// activates/deactivates the stream's interface (SET_INTERFACE alt setting)
+// on each switch.
+// 1. mic only (3 s): capture runs, captured data is dropped
+// 2. spk only (5 s): playback plays the sine test tone
+// 3. echo (5 s): both streams run, captured audio is echoed back
+#define APP_PHASE_MIC_ONLY_MS 5000
+#define APP_PHASE_SPK_ONLY_MS 5000
+#define APP_PHASE_ECHO_MS 5000
+
+enum {
+ APP_PHASE_MIC_ONLY = 0,
+ APP_PHASE_SPK_ONLY,
+ APP_PHASE_ECHO,
+ APP_PHASE_COUNT
+};
+
+static uint8_t app_audio_phase = APP_PHASE_MIC_ONLY;
+static const uint32_t app_phase_ms[APP_PHASE_COUNT] = {APP_PHASE_MIC_ONLY_MS, APP_PHASE_SPK_ONLY_MS, APP_PHASE_ECHO_MS};
+
+// Start or stop the capture/playback streams according to the current phase.
+// The app tasks already behave per phase: with mic_ready false the sine tone
+// plays, with the playback stream stopped the echo write returns 0 (dropped).
+static void app_audio_phase_apply(void) {
+ switch (app_audio_phase) {
+ case APP_PHASE_MIC_ONLY:
+ if (!mic_ready) {
+ mic_ready = tuh_audio_start(audio_idx, cap_stream_idx);
+ }
+ if (spk_ready) {
+ spk_ready = !tuh_audio_stop(audio_idx, spk_stream_idx);
+ }
+ printf(" Phase %u: mic on, spk off (data dropped)\r\n", app_audio_phase);
+ break;
+ case APP_PHASE_SPK_ONLY:
+ if (mic_ready) {
+ mic_ready = !tuh_audio_stop(audio_idx, cap_stream_idx);
+ }
+ if (!spk_ready) {
+ spk_ready = tuh_audio_start(audio_idx, spk_stream_idx);
+ }
+ printf(" Phase %u: mic off, spk on (sine)\r\n", app_audio_phase);
+ break;
+ case APP_PHASE_ECHO:
+ if (!mic_ready) {
+ mic_ready = tuh_audio_start(audio_idx, cap_stream_idx);
+ }
+ if (!spk_ready) {
+ spk_ready = tuh_audio_start(audio_idx, spk_stream_idx);
+ }
+ printf(" Phase %u: mic + spk on (echo)\r\n", app_audio_phase);
+ break;
+ default:
+ break;
}
+}
- if (!audio_rx_busy) {
- if (tuh_audio_receive(audio_idx, audiostream_in_idx, audio_rx_buffer, CFG_TUH_AUDIO_EPIN_BUFSIZE)) {
- audio_rx_busy = true;
- }
+// Enter a phase, then schedule the next switch after this phase's duration
+static void app_audio_phase_enter(uintptr_t phase) {
+ app_audio_phase = (uint8_t)phase;
+ // Cancel stale deferred callbacks (e.g. a stream restart scheduled on a
+ // transfer error) so they cannot re-start a stream this phase stops.
+ app_defer_queue_clear();
+ app_audio_phase_apply();
+ const uint8_t next_phase = (uint8_t)((app_audio_phase + 1) % APP_PHASE_COUNT);
+ app_defer_ms_async(app_phase_ms[app_audio_phase], (app_defer_func_t)app_audio_phase_enter, next_phase);
+}
+
+
+//--------------------------------------------------------------------+
+// Blinking Task
+//--------------------------------------------------------------------+
+void led_blinking_task(void) {
+ const uint32_t interval_ms = 1000;
+ static uint32_t start_ms = 0;
+
+ static bool led_state = false;
+
+ // Blink every interval ms
+ if (tusb_time_millis_api() - start_ms < interval_ms) {
+ return; // not enough time
}
+ start_ms += interval_ms;
+
+ board_led_write(led_state);
+ led_state = 1 - led_state; // toggle
+#if 1
+ printf(" MIC CB=%lu SPK CB=%lu ERR CB=%lu\r\n", (unsigned long)mic_cb_count, (unsigned long)spk_cb_count,
+ (unsigned long)err_cb_count);
+ mic_cb_count = 0;
+ spk_cb_count = 0;
+ err_cb_count = 0;
+
+#endif
+#if 0
+ // Print the current Feature Unit volume, which is set to 0x0600 in mic_configured() and can be changed by the device.
+ uint16_t volume = 0x0001;
+ tuh_audio_feature_unit_get_sync(audio_idx, AUDIO10_FU_CTRL_VOLUME, 0, &volume);
+ printf(" Feature Unit volume get: 0x%04x\r\n", (unsigned int)volume);
+ uint16_t mute = 0x0000;
+ tuh_audio_feature_unit_get_sync(audio_idx, AUDIO10_FU_CTRL_MUTE, 0, &mute);
+ mute=!mute; // toggle mute for demonstration
+ tuh_audio_feature_unit_set_sync(audio_idx, AUDIO10_FU_CTRL_MUTE, 0, mute);
+ printf(" Feature Unit mute set: 0x%04x\r\n", (unsigned int)mute);
+#endif
}
//--------------------------------------------------------------------+
-// TinyUSB Callbacks
+// Application Task
//--------------------------------------------------------------------+
-void tuh_audio_mount_cb(uint8_t idx) {
- if (idx >= CFG_TUH_AUDIO_MAX) {
- printf("Audio device mount failed: idx=%u exceeds max=%u\r\n", idx, CFG_TUH_AUDIO_MAX);
+// Echo the captured audio back to the playback stream: drain the capture
+// FIFO into mic_samples, convert, and queue the frames into the playback
+// FIFO. The driver schedules the actual isochronous transfers.
+
+void audio_app_task_read(void) {
+ if (!mic_ready) {
return;
}
- print_as_interfaces(idx);
+ const uint32_t frames =
+ tuh_audio_read(audio_idx, cap_stream_idx, mic_samples, audio_frames_this_ms(mic_config.sample_rate));
+ if (frames == 0) {
+ return;
+ }
- // Save device info
- audio_dev_addr = tuh_audio_get_dev_addr(idx);
- audio_idx = idx;
- audio_mounted = true;
+ if (spk_config.channels == mic_config.channels) {
+ memcpy(spk_samples, mic_samples, frames * mic_config.channels * sizeof(int16_t));
+ } else if (mic_config.channels == 1 && spk_config.channels == 2) {
+ mono_to_stereo(mic_samples, spk_samples, frames);
+ } else {
+ stereo_to_mono(mic_samples, spk_samples, frames);
+ }
- // Find endpoints and IN sampling frequency
- tuh_audio_as_info_t as;
- for (uint8_t i = 0; i < tuh_audio_as_get_count(idx); i++) {
+ (void)tuh_audio_write(audio_idx, spk_stream_idx, spk_samples, frames);
+}
- tuh_audio_as_get_info(idx, i, &as);
- if (as.ep_dir == TUSB_DIR_IN) {
- audiostream_in_idx = i;
- if (as.sam_freq_type > 0) {
- sampling_freq = as.sam_freq[0];
- }
- } else {
- audiostream_out_idx = i;
- }
+void audio_app_task_write(void) {
+ // Fallback: the sine test tone when no capture stream is echoing
+ if (mic_ready || !spk_ready) {
+ return;
}
- // Set IN sampling frequency before starting isochronous transfer
- if (audiostream_in_idx != 0xFF && sampling_freq != 0) {
- printf(" Setting IN sampling frequency to %lu Hz\r\n", (unsigned long)sampling_freq);
- // tuh_audio_set_sampling_freq(audio_idx, audiostream_in_idx, sampling_freq, in_sampling_freq_set_cb, 0);
+ const uint32_t frames = audio_frames_this_ms(spk_config.sample_rate);
+ if (tuh_audio_write_available(audio_idx, spk_stream_idx) >= frames) {
+ (void)tuh_audio_write(audio_idx, spk_stream_idx, spk_samples, frames);
+ }
+}
- tusb_xfer_result_t result;
- result = tuh_audio_set_sampling_freq_sync(audio_idx, audiostream_in_idx, sampling_freq);
- if (result == XFER_RESULT_SUCCESS) {
- tuh_audio_get_sampling_freq_sync(audio_idx, audiostream_in_idx, &sampling_freq);
- printf(" IN sampling frequency set to %lu Hz\r\n", (unsigned long)sampling_freq);
- if (audiostream_out_idx != 0xFF) {
- printf(" Setting OUT sampling frequency to %lu Hz\r\n", (unsigned long)sampling_freq);
- result = tuh_audio_set_sampling_freq_sync(audio_idx, audiostream_out_idx, sampling_freq);
- if (result == XFER_RESULT_SUCCESS) {
- tuh_audio_get_sampling_freq_sync(audio_idx, audiostream_out_idx, &sampling_freq);
- printf(" OUT sampling frequency set to %lu Hz\r\n", (unsigned long)sampling_freq);
- } else {
- printf(" Setting OUT sampling frequency FAILED: result=%u\r\n", result);
- }
- }
- } else {
- printf(" Setting IN sampling frequency FAILED: result=%u\r\n", result);
+// Invoked when an isochronous IN transfer completes: the captured data is
+// already queued into the capture FIFO and drained by audio_app_task_read().
+void tuh_audio_capture_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
+ (void)idx;
+ (void)stream_idx;
+ (void)xferred_bytes;
+ mic_cb_count++;
+}
+
+// Invoked when an isochronous OUT transfer completes: the next queued packet
+// is submitted from the playback FIFO by the driver.
+void tuh_audio_playback_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
+ (void)idx;
+ (void)stream_idx;
+ (void)xferred_bytes;
+ spk_cb_count++;
+}
+
+// Re-open a stream stopped by a transfer error: the driver keeps the stream
+// configured, so tuh_audio_start() resumes it. Invoked deferred so repeated
+// errors cannot stall the main loop.
+static void audio_app_restart_stream(uintptr_t param) {
+ const uint8_t idx = (uint8_t)(param >> 8);
+ const uint8_t stream_idx = (uint8_t)param;
+ if (!tuh_audio_mounted(idx)) {
+ return; // device is gone
+ }
+ if (stream_idx == cap_stream_idx) {
+ printf(" Restarting capture stream %u\r\n", stream_idx);
+ mic_ready = tuh_audio_start(idx, stream_idx);
+ } else if (stream_idx == spk_stream_idx) {
+ printf(" Restarting playback stream %u\r\n", stream_idx);
+ spk_ready = tuh_audio_start(idx, stream_idx);
+ }
+}
+
+// Invoked when an isochronous transfer fails: the stream was stopped by the
+// driver, re-open it after a short delay so the device can recover.
+void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
+ (void)xferred_bytes;
+ err_cb_count++;
+ printf(" AUDIO transfer error: addr=%u stream=%u xferred_bytes=%u\r\n", idx, stream_idx, (unsigned)xferred_bytes);
+ app_defer_ms_async(100, (app_defer_func_t)audio_app_restart_stream, ((uintptr_t)idx << 8) | stream_idx);
+}
+
+//--------------------------------------------------------------------+
+// TinyUSB Callbacks
+//--------------------------------------------------------------------+
+
+// Print all supported stream configurations
+static void print_stream_configs(uint8_t idx, uint8_t stream_idx) {
+ const tuh_audio_direction_t dir = tuh_audio_stream_direction(idx, stream_idx);
+ const char *dir_name = (dir == TUH_AUDIO_STREAM_CAPTURE) ? "capture" : "playback";
+ printf(" %s stream %u configurations: %u\r\n", dir_name, stream_idx, tuh_audio_config_count(idx, stream_idx));
+ for (uint8_t i = 0; i < tuh_audio_config_count(idx, stream_idx); i++) {
+ tuh_audio_stream_config_t config;
+ if (tuh_audio_config_get(idx, stream_idx, i, &config)) {
+ printf(" [%u] format=%u rate=%lu channels=%u\r\n", i, (unsigned)config.format,
+ (unsigned long)config.sample_rate, (unsigned)config.channels);
}
- uint16_t volume = 0x0600;
+ }
+}
+
+// Invoked when the configuration selected by tuh_audio_configure() completes
+static void mic_configured(uint8_t idx, uint8_t stream_idx, tusb_xfer_result_t result, uintptr_t user_data) {
+ (void)user_data;
+
+ if (idx == audio_idx && stream_idx == cap_stream_idx && result == XFER_RESULT_SUCCESS) {
+ printf(" Microphone configured, starting capture\r\n");
+ mic_ready = tuh_audio_start(idx, stream_idx);
- result = tuh_audio_feature_unit_set_sync(audio_idx, AUDIO10_FU_CTRL_VOLUME, 0, volume);
+ uint16_t volume = 0x0600;
+ result = tuh_audio_feature_unit_set_sync(idx, AUDIO10_FU_CTRL_VOLUME, 0, volume);
if (result == XFER_RESULT_SUCCESS) {
printf(" Feature Unit volume set:volume 0x%04x\r\n", (unsigned int)volume);
- tuh_audio_feature_unit_get_sync(audio_idx, AUDIO10_FU_CTRL_VOLUME, 0, &volume);
+ tuh_audio_feature_unit_get_sync(idx, AUDIO10_FU_CTRL_VOLUME, 0, &volume);
printf(" Feature Unit volume get: 0x%04x\r\n", (unsigned int)volume);
} else {
printf(" Setting Feature Unit volume FAILED: result=%u\r\n", result);
}
+ } else {
+ printf(" Microphone configuration failed: result=%u\r\n", result);
}
- audio_ready = true;
}
+// Invoked when the playback configuration selected by tuh_audio_configure() completes
+static void spk_configured(uint8_t idx, uint8_t stream_idx, tusb_xfer_result_t result, uintptr_t user_data) {
+ (void)user_data;
+ if (idx == audio_idx && stream_idx == spk_stream_idx && result == XFER_RESULT_SUCCESS) {
+ printf(" Speaker configured, starting playback\r\n");
+ spk_ready = tuh_audio_start(idx, stream_idx);
+ // playback-only device: set the frame cadence from the selected rate
+ audio_frame_count = spk_config.sample_rate / 1000;
+ spk_init_sine(); // fallback test tone while no capture stream is echoing
+
+ // both streams running: start the periodic phase switching demo
+ if (mic_ready && spk_ready) {
+ app_audio_phase_enter(APP_PHASE_MIC_ONLY);
+ }
+ } else {
+ printf(" Speaker configuration failed: result=%u\r\n", result);
+ }
+}
// Invoked when device with Audio interface is un-mounted
void tuh_audio_umount_cb(uint8_t idx) {
printf("Audio device unmounted: idx=%u\r\n", idx);
- if (audio_mounted && audio_idx == idx) {
- audio_mounted = false;
- audio_ready = false;
- audio_rx_busy = false;
- audio_tx_busy = false;
- audio_dev_addr = 0;
- audio_idx = 0;
- audiostream_in_idx = 0xFF;
- audiostream_out_idx = 0xFF;
+ if (idx == audio_idx) {
+ app_defer_queue_clear();
+ audio_idx = TUSB_INDEX_INVALID_8;
+ cap_stream_idx = TUSB_INDEX_INVALID_8;
+ spk_stream_idx = TUSB_INDEX_INVALID_8;
+ mic_ready = false;
+ spk_ready = false;
}
}
-// Invoked when an isochronous IN transfer is complete
-void tuh_audio_rx_cb(uint8_t dev_addr, uint8_t ep_addr, uint16_t xferred_bytes) {
- (void)dev_addr;
- (void)ep_addr;
- audio_rx_busy = false;
+void tuh_audio_mount_async(uintptr_t param) {
+ uint8_t idx = (uint8_t)param;
+ if (idx >= CFG_TUH_AUDIO_MAX) {
+ printf("Audio device mount failed: idx=%u exceeds max=%u\r\n", idx, CFG_TUH_AUDIO_MAX);
+ return;
+ }
- if (xferred_bytes > 0 && audiostream_out_idx != 0xFF && !audio_tx_busy) {
- bool ok;
- if (audio_mic_channels == 1) {
- // Mono microphone, convert to stereo and send to OUT endpoint
- uint16_t samples = xferred_bytes / 2;
- mono_to_stereo(audio_rx_buffer, audio_tx_buffer, samples);
- ok = tuh_audio_send(audio_idx, audiostream_out_idx, audio_tx_buffer, xferred_bytes * 2);
- } else {
- // Stereo microphone, send directly to OUT endpoint
- ok = tuh_audio_send(audio_idx, audiostream_out_idx, audio_rx_buffer, xferred_bytes);
+ printf("Audio device mounted: idx=%u addr=%u\r\n", idx, tuh_audio_get_dev_addr(idx));
+
+ // Inspect every stream and print its supported configurations
+ for (uint8_t stream_idx = 0; stream_idx < tuh_audio_stream_count(idx); stream_idx++) {
+ if (!tuh_audio_stream_exists(idx, stream_idx)) {
+ continue;
+ }
+ print_stream_configs(idx, stream_idx);
+ }
+
+ // Select a supported 48 kHz S16_LE capture configuration without
+ // accessing USB interfaces, alternate settings, or endpoint addresses.
+ // Sample rates are tried in SAMPLE_RATES order (44.1 kHz first), stereo is
+ // preferred, mono is accepted.
+ static const uint32_t sample_rates[] = SAMPLE_RATES;
+ bool capture_found = false;
+ for (uint8_t r = 0; r < TU_ARRAY_SIZE(sample_rates) && !capture_found; r++) {
+ const uint32_t sample_rate = sample_rates[r];
+ for (uint8_t stream_idx = 0; stream_idx < tuh_audio_stream_count(idx) && !capture_found; stream_idx++) {
+ // Only consider capture streams, ignore playback streams
+ if (tuh_audio_stream_direction(idx, stream_idx) != TUH_AUDIO_STREAM_CAPTURE) {
+ continue;
+ }
+ for (uint8_t ch = AUDIO_MAX_CHANNELS; ch >= 1 && !capture_found; ch--) {
+ for (uint8_t i = 0; i < tuh_audio_config_count(idx, stream_idx); i++) {
+ tuh_audio_stream_config_t config;
+ // Check for a matching sample rate S16_LE configuration with the desired channel count
+ if (tuh_audio_config_get(idx, stream_idx, i, &config) && config.format == TUH_AUDIO_FORMAT_S16_LE &&
+ config.sample_rate == sample_rate && config.channels == ch) {
+ audio_idx = idx;
+ cap_stream_idx = stream_idx;
+ mic_config = config;
+ // one ms of audio at the selected rate, rounded down to whole frames
+ audio_frame_count = sample_rate / 1000;
+ printf(" Configuring %u S16_LE capture (%u channels)\r\n", (unsigned)sample_rate, config.channels);
+ // Configure the selected capture stream and start it through the callback.
+ (void)tuh_audio_configure(idx, stream_idx, i, mic_configured, 0);
+ capture_found = true;
+ break;
+ }
+ }
+ }
}
+ }
+ if (!capture_found) {
+ printf(" No supported 48/44.1 kHz S16_LE capture configuration found\r\n");
+ }
- if (ok) {
- audio_tx_busy = true;
+ // The echo needs a playback stream at the capture sample rate (or at any
+ // preferred rate when no capture stream exists, for the sine fallback).
+ // Prefer the same channel count as the capture stream (direct echo), then
+ // the other one (converted).
+ uint8_t playback_config_idx = TUSB_INDEX_INVALID_8;
+ for (uint8_t r = 0; r < TU_ARRAY_SIZE(sample_rates) && playback_config_idx == TUSB_INDEX_INVALID_8; r++) {
+ const uint32_t sample_rate = capture_found ? mic_config.sample_rate : sample_rates[r];
+ for (uint8_t stream_idx = 0;
+ stream_idx < tuh_audio_stream_count(idx) && playback_config_idx == TUSB_INDEX_INVALID_8; stream_idx++) {
+ // Only consider playback streams, ignore capture streams
+ if (tuh_audio_stream_direction(idx, stream_idx) != TUH_AUDIO_STREAM_PLAYBACK) {
+ continue;
+ }
+ for (uint8_t n = 0; n < 2 && playback_config_idx == TUSB_INDEX_INVALID_8; n++) {
+ const uint8_t ch = (n == 0) ? mic_config.channels : (uint8_t)(mic_config.channels == 1 ? 2 : 1);
+ for (uint8_t i = 0; i < tuh_audio_config_count(idx, stream_idx); i++) {
+ tuh_audio_stream_config_t config;
+ if (tuh_audio_config_get(idx, stream_idx, i, &config) && config.format == TUH_AUDIO_FORMAT_S16_LE &&
+ config.sample_rate == sample_rate && config.channels == ch) {
+ spk_stream_idx = stream_idx;
+ spk_config = config;
+ playback_config_idx = i;
+ break;
+ }
+ }
+ }
}
}
+ if (playback_config_idx == TUSB_INDEX_INVALID_8) {
+ printf(" No supported %u S16_LE playback configuration, echo disabled\r\n",
+ (unsigned)(capture_found ? mic_config.sample_rate : sample_rates[0]));
+ return;
+ }
+ printf(" Configuring %u S16_LE playback (%u channels)\r\n", (unsigned)spk_config.sample_rate, spk_config.channels);
+ // Configure the selected playback stream and start it through the callback.
+ (void)tuh_audio_configure(idx, spk_stream_idx, playback_config_idx, spk_configured, 0);
}
-// Invoked when an isochronous OUT transfer is complete
-void tuh_audio_tx_cb(uint8_t dev_addr, uint8_t ep_addr, uint16_t xferred_bytes) {
- (void)dev_addr;
- (void)ep_addr;
- (void)xferred_bytes;
- audio_tx_busy = false;
+// Invoked when device with Audio interface is mounted
+void tuh_audio_mount_cb(uint8_t idx) {
+ app_defer_ms_async(100, (app_defer_func_t)tuh_audio_mount_async, idx);
}
diff --git a/examples/host/audio_host/src/main.c b/examples/host/audio_host/src/main.c
index b80cd2938..77c28cf41 100644
--- a/examples/host/audio_host/src/main.c
+++ b/examples/host/audio_host/src/main.c
@@ -35,39 +35,17 @@ int main(void) {
printf("Connect a USB Audio Device (UAC 1.0) to test\r\n");
// init host stack on configured roothub port
- tusb_rhport_init_t host_init = {
- .role = TUSB_ROLE_HOST,
- .speed = TUSB_SPEED_AUTO
- };
+ tusb_rhport_init_t host_init = {.role = TUSB_ROLE_HOST, .speed = TUSB_SPEED_AUTO};
tusb_init(BOARD_TUH_RHPORT, &host_init);
board_init_after_tusb();
-
+ uint32_t last_ms = tusb_time_millis_api();
while (1) {
// tinyusb host task
tuh_task();
led_blinking_task();
- audio_app_task();
+ audio_app_task_read();
+ audio_app_task_write();
+ defer_queue_task();
}
}
-
-//--------------------------------------------------------------------+
-// TinyUSB Callbacks
-//--------------------------------------------------------------------+
-
-//--------------------------------------------------------------------+
-// Blinking Task
-//--------------------------------------------------------------------+
-void led_blinking_task(void) {
- const uint32_t interval_ms = 1000;
- static uint32_t start_ms = 0;
-
- static bool led_state = false;
-
- // Blink every interval ms
- if ( tusb_time_millis_api() - start_ms < interval_ms) return; // not enough time
- start_ms += interval_ms;
-
- board_led_write(led_state);
- led_state = 1 - led_state; // toggle
-}
diff --git a/examples/host/audio_host/src/tusb_config.h b/examples/host/audio_host/src/tusb_config.h
index 4a7a6ad56..9b7f3c94b 100644
--- a/examples/host/audio_host/src/tusb_config.h
+++ b/examples/host/audio_host/src/tusb_config.h
@@ -81,9 +81,9 @@ extern "C" {
#define CFG_TUH_ENUMERATION_BUFSIZE 512
-#define CFG_TUH_HUB 1
+#define CFG_TUH_HUB 0
#define CFG_TUH_CDC 0
-#define CFG_TUH_HID 1
+#define CFG_TUH_HID 0
#define CFG_TUH_MSC 0
#define CFG_TUH_VENDOR 0
#define CFG_TUH_AUDIO 1
@@ -93,8 +93,8 @@ extern "C" {
//------------- Audio Host Config -------------//
#define CFG_TUH_AUDIO_MAX 2
-#define CFG_TUH_AUDIO_EPIN_BUFSIZE 192
-#define CFG_TUH_AUDIO_EPOUT_BUFSIZE 192
+#define CFG_TUH_AUDIO_EPIN_BUFSIZE 256 // max capture transfer the application submits
+#define CFG_TUH_AUDIO_EPOUT_BUFSIZE 256 // max playback transfer the application submits
#ifdef __cplusplus
}