summaryrefslogtreecommitdiff
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
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.
-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
-rw-r--r--src/class/audio/audio_host.c1566
-rw-r--r--src/class/audio/audio_host.h245
8 files changed, 1706 insertions, 776 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
}
diff --git a/src/class/audio/audio_host.c b/src/class/audio/audio_host.c
index c74a95d48..f943a5b2f 100644
--- a/src/class/audio/audio_host.c
+++ b/src/class/audio/audio_host.c
@@ -6,23 +6,43 @@
*/
/*
- * This driver implements a USB Audio Host (UAC 1.0) class driver.
- * It supports multiple Audio Streaming (AS) interfaces with independent format storage.
- * Each AS interface can have its own sample rate, channel count, bit resolution,
- * and endpoint configuration.
+ * This driver implements a USB Audio Host (UAC 1.0) class driver with a
+ * WASAPI/ALSA-like high-level streaming API. The USB Audio topology (Audio
+ * Control interface, Audio Streaming interfaces, alternate settings, and
+ * endpoints) is kept private to the driver.
*
- * The driver handles:
- * 1. Audio Control (AC) interface parsing — Input Terminal, Output Terminal,
- * and Feature Unit descriptors.
- * 2. Audio Streaming (AS) interface enumeration — multiple AS interfaces with
- * alternate settings, each storing its own format information.
- * 3. Isochronous IN/OUT endpoint management for audio data transfer.
- * 4. Asynchronous control transfers for sample frequency get/set.
+ * Each instance (Audio Control interface) provides at most one logical stream
+ * per direction:
+ * - capture stream (TUSB_DIR_IN): device -> host, filled by isochronous IN
+ * transfers scheduled by the driver into a FIFO, drained by the application
+ * with tuh_audio_read()
+ * - playback stream (TUSB_DIR_OUT): host -> device, drained by isochronous
+ * OUT transfers from a FIFO filled by the application with tuh_audio_write()
*
- * In case you need to adjust the number of supported AS interfaces, change
- * CFG_TUH_AUDIO_MAX_AS in your tusb_config.h.
+ * While a stream is running, the driver keeps one isochronous transfer in
+ * flight (a natural 1 ms frame cadence) and re-submits on completion. The
+ * FIFO + endpoint-claim pattern is modeled after the tu_edpt_stream helper
+ * used by the MIDI host driver: the application's frame-based read/write is
+ * decoupled from the USB transfer cadence, and only whole frames are ever
+ * queued or transferred. Completion of each transfer is reported through
+ * tuh_audio_capture_cb()/tuh_audio_playback_cb(), failures through
+ * tuh_audio_err_cb().
*
- * */
+ * The supported configurations of all Audio Streaming interfaces and alternate
+ * settings in one direction are combined into a flat list of discrete
+ * {format, sample_rate, channels} tuples. The driver keeps the mapping from
+ * each configuration to its interface, alternate setting, and endpoint, and
+ * applies it when the application calls tuh_audio_configure().
+ *
+ * Non-PCM formats are rejected explicitly during enumeration. A continuous
+ * sampling-frequency range is exposed as a single configuration at the
+ * range's highest sampling frequency.
+ *
+ * The driver owns:
+ * 1. Endpoint selection and opening (only the alternate setting selected by
+ * tuh_audio_configure() is ever activated).
+ * 2. Endpoint sampling-frequency control (SET_CUR, 3 bytes little-endian).
+ */
#include "tusb_option.h"
@@ -43,7 +63,6 @@
// Weak stubs: invoked if no strong implementation is available
//--------------------------------------------------------------------+
-
TU_ATTR_WEAK void tuh_audio_mount_cb(uint8_t idx) {
(void)idx;
}
@@ -52,58 +71,128 @@ TU_ATTR_WEAK void tuh_audio_umount_cb(uint8_t idx) {
(void)idx;
}
-TU_ATTR_WEAK void tuh_audio_rx_cb(uint8_t idx, uint8_t ep_addr, uint16_t xferred_bytes) {
+TU_ATTR_WEAK void tuh_audio_capture_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
(void)idx;
- (void)ep_addr;
+ (void)stream_idx;
(void)xferred_bytes;
}
-TU_ATTR_WEAK void tuh_audio_tx_cb(uint8_t idx, uint8_t ep_addr, uint16_t xferred_bytes) {
+TU_ATTR_WEAK void tuh_audio_playback_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
(void)idx;
- (void)ep_addr;
+ (void)stream_idx;
(void)xferred_bytes;
}
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
+ (void)idx;
+ (void)stream_idx;
+ (void)xferred_bytes;
+}
+
+ //--------------------------------------------------------------------+
+ // MACRO CONSTANT TYPEDEF
+ //--------------------------------------------------------------------+
+
+ // Maximum number of supported configurations per stream (per direction)
+ #define AUDIOH_MAX_CONFIGS (CFG_TUH_AUDIO_MAX_AS * CFG_TUH_AUDIO_MAX_SAM_FREQ)
+
+ // Maximum number of interfaces in the AC header's interface collection
+ #define AUDIOH_MAX_COLLECTION 16
+// Stream state machine
+enum {
+ STREAM_STATE_IDLE = 0, // not configured, no configuration in progress
+ STREAM_STATE_CONFIG, // tuh_audio_configure() sequence in progress
+ STREAM_STATE_READY // configured, ready to start/stop
+};
-// Per-interface storage
+// Hardware mapping of one supported configuration
typedef struct {
- uint8_t daddr; // device address
- uint8_t ac_itf_num; // Audio Control interface number
- uint8_t itf_count; // number of interfaces (AC + AS)
+ uint8_t itf_num; // Audio Streaming interface number
+ uint8_t alt_setting; // alternate setting that provides this configuration
+ uint8_t ep_addr; // isochronous endpoint address
+ uint16_t ep_size; // endpoint max packet size
+ uint8_t ep_interval; // endpoint bInterval
+ uint8_t ep_sync; // bmAttributes sync type
+ uint8_t ep_usage; // bmAttributes usage type
+ bool sam_freq_ctrl; // endpoint supports sampling-frequency control
+} audioh_stream_map_t;
- // Terminal info (from Audio Control Interface)
- uint16_t input_terminal_type; // wTerminalType of Input Terminal
- uint8_t input_terminal_id; // bTerminalID of Input Terminal
- uint8_t input_terminal_channels; // bNrChannels of Input Terminal
- uint16_t output_terminal_type; // wTerminalType of Output Terminal
- uint8_t output_terminal_id; // bTerminalID of Output Terminal
+// One logical stream (capture or playback)
+typedef struct {
+ // instance info (set at init, preserved across close/open)
+ uint8_t idx; // instance index
+ uint8_t stream_idx; // logical stream index within the instance
+ tusb_dir_t dir; // TUSB_DIR_IN = capture, TUSB_DIR_OUT = playback
- // Feature Unit info
- uint8_t feature_unit_id; // bUnitID of Feature Unit (0 = none)
- uint8_t feature_unit_source_id; // bSourceID of Feature Unit
+ // device owning this stream (0 = no device)
+ uint8_t daddr;
+
+ // Supported configurations (parsed during enumeration)
+ uint8_t config_count;
+ tuh_audio_stream_config_t config[AUDIOH_MAX_CONFIGS];
+ audioh_stream_map_t map[AUDIOH_MAX_CONFIGS];
+
+ // Active stream state
+ uint8_t active_config; // index into config[]/map[], TUSB_INDEX_INVALID_8 when not configured
+ uint8_t state; // STREAM_STATE_*
+ bool running; // tuh_audio_start() called, transfers may be submitted
+
+ // Size in bytes of one frame (all channels) of the active configuration
+ uint8_t frame_bytes;
+
+ // Playback pacing: frames the device consumes per USB frame
+ // (sample_rate / 1000), with the fractional remainder (0.1 frame per ms at
+ // 44.1 kHz) accumulated on each submission and paid back as one extra frame
+ uint16_t frames_per_ms;
+ uint16_t frames_rem;
+ uint16_t rem_acc;
+
+ // Configure state machine
+ tuh_audio_configure_cb_t complete_cb;
+ uintptr_t user_data;
+
+ // FIFO + endpoint transfer helper (see tu_edpt_stream, used by the MIDI
+ // host driver): the FIFO decouples the application's frame-based read/write
+ // from the 1 ms isochronous transfer cadence. ep_buf is bound at init from
+ // _audioh_epbuf[], the endpoint is bound by tu_edpt_stream_open() when the
+ // stream is configured.
+ tu_edpt_stream_t edpt;
+ uint8_t ff_buf[CFG_TUH_AUDIO_STREAM_BUFSIZE];
+
+ TUH_EPBUF_DEF(ctrl, 4); // sampling-frequency SET data
+} tuh_audio_stream_t;
+
+// Per-instance (Audio device) storage
+typedef struct {
+ uint8_t daddr; // device address (0 = free slot)
+ uint8_t ac_itf_num; // Audio Control interface number
- // Multiple AS interfaces support
- uint8_t as_count;
- uint8_t as_set_idx;
+ // Logical streams: playback first, then capture (stream index order)
+ tuh_audio_stream_t out_stream;
+ tuh_audio_stream_t in_stream;
+ uint8_t stream_count; // number of streams with supported configurations
- // Per-AS interface independent storage (new)
- tuh_audio_as_info_t as[CFG_TUH_AUDIO_MAX_AS]; // Array of Audio Streaming interface info structures
+ // Feature Unit info
+ uint8_t feature_unit_id; // bUnitID of Feature Unit (0 = none)
bool mounted;
} audioh_interface_t;
typedef struct {
- TUH_EPBUF_DEF(epin, CFG_TUH_AUDIO_EPIN_BUFSIZE);
- TUH_EPBUF_DEF(epout, CFG_TUH_AUDIO_EPOUT_BUFSIZE);
- TUH_EPBUF_DEF(ctrl, 8);
+ TUH_EPBUF_DEF(ctrl, 8); // feature-unit SET data
+ TUH_EPBUF_DEF(epin, CFG_TUH_AUDIO_EPIN_BUFSIZE); // capture transfer buffer
+ TUH_EPBUF_DEF(epout, CFG_TUH_AUDIO_EPOUT_BUFSIZE); // playback transfer buffer
+ // Feature-unit GET chain state: only one GET in flight per device
+ tuh_xfer_cb_t complete_cb;
+ uintptr_t user_data;
+ uint16_t *value;
+ uint8_t width;
} audioh_epbuf_t;
static audioh_interface_t _audioh_itf[CFG_TUH_AUDIO_MAX];
static audioh_epbuf_t _audioh_epbuf[CFG_TUH_AUDIO_MAX];
+
//--------------------------------------------------------------------+
// Helper
//--------------------------------------------------------------------+
@@ -116,29 +205,266 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t find_new_audio_index(void) {
return TUSB_INDEX_INVALID_8;
}
-static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) {
+static tuh_audio_stream_t *audioh_get_stream(audioh_interface_t *p_audio, tusb_dir_t direction) {
+ switch (direction) {
+ case TUSB_DIR_IN:
+ return &p_audio->in_stream;
+ case TUSB_DIR_OUT:
+ return &p_audio->out_stream;
+ default:
+ return NULL;
+ }
+}
+
+// Look up a stream by its logical index within the instance
+static tuh_audio_stream_t *audioh_get_stream_by_idx(audioh_interface_t *p_audio, uint8_t stream_idx) {
+ for (uint8_t i = 0; i < 2; i++) {
+ tuh_audio_stream_t *s = (i == 0) ? &p_audio->out_stream : &p_audio->in_stream;
+ if (s->config_count > 0 && s->stream_idx == stream_idx) {
+ return s;
+ }
+ }
+ return NULL;
+}
+
+// Map a UAC 1.0 (subframe size, bit resolution) pair to a supported format
+static bool audioh_format_from_uac1(uint8_t subframe_size, uint8_t bit_resolution, tuh_audio_format_t *format) {
+ if (subframe_size == 1 && bit_resolution == 8) {
+ *format = TUH_AUDIO_FORMAT_S8;
+ } else if (subframe_size == 2 && bit_resolution == 16) {
+ *format = TUH_AUDIO_FORMAT_S16_LE;
+ } else if (subframe_size == 3 && bit_resolution == 24) {
+ *format = TUH_AUDIO_FORMAT_S24_3LE;
+ } else if (subframe_size == 4 && bit_resolution == 24) {
+ *format = TUH_AUDIO_FORMAT_S24_LE;
+ } else if (subframe_size == 4 && bit_resolution == 32) {
+ *format = TUH_AUDIO_FORMAT_S32_LE;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+// Endpoint poll interval in microseconds: full-speed bInterval is in 1 ms
+// frames, high-speed isochronous bInterval is a power-of-2 exponent of
+// 125 us microframes
+static uint32_t audioh_interval_us(uint8_t ep_interval, uint8_t daddr) {
+ if (tuh_speed_get(daddr) == TUSB_SPEED_HIGH) {
+ return ((uint32_t)1u << (ep_interval - 1)) * 125u;
+ }
+ return (uint32_t)ep_interval * 1000u;
+}
+
+// UAC 1.0 feature-unit control value width: mute/AGC/loudness are 1 byte, the rest 2 bytes
+static uint8_t audioh_fu_control_width(uint8_t control_selector) {
+ switch (control_selector) {
+ case AUDIO10_FU_CTRL_MUTE:
+ case AUDIO10_FU_CTRL_AGC:
+ case AUDIO10_FU_CTRL_LOUDNESS:
+ return 1;
+ default:
+ return 2;
+ }
+}
+
+// Reset a stream to its unconfigured state (keeps idx, dir, and FIFO configuration)
+static void audioh_stream_reset(tuh_audio_stream_t *s) {
+ s->daddr = 0;
+ s->stream_idx = TUSB_INDEX_INVALID_8;
+ s->config_count = 0;
+ s->active_config = TUSB_INDEX_INVALID_8;
+ s->state = STREAM_STATE_IDLE;
+ s->running = false;
+ s->frame_bytes = 0;
+ s->frames_per_ms = 0;
+ s->frames_rem = 0;
+ s->rem_acc = 0;
+ s->complete_cb = NULL;
+ tu_edpt_stream_close(&s->edpt);
+ tu_edpt_stream_clear(&s->edpt);
+}
+
+// Find the stream owning an endpoint (used to dispatch transfer completion)
+static tuh_audio_stream_t *audioh_find_stream(uint8_t dev_addr, uint8_t ep_addr) {
for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) {
- const audioh_interface_t *p_audio = &_audioh_itf[idx];
- if (p_audio->daddr == daddr) {
- for (uint8_t as_idx = 0; as_idx < p_audio->as_count; as_idx++) {
- if (p_audio->as[as_idx].ep_addr == ep_addr) {
- return idx;
- }
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ for (uint8_t s = 0; s < 2; s++) {
+ tuh_audio_stream_t *stream = (s == 0) ? &p_audio->in_stream : &p_audio->out_stream;
+ if (stream->daddr == dev_addr && stream->active_config != TUSB_INDEX_INVALID_8 &&
+ stream->map[stream->active_config].ep_addr == ep_addr) {
+ return stream;
}
}
}
- return TUSB_INDEX_INVALID_8;
+ return NULL;
}
-static uint8_t audioh_get_ep_addr_by_dir(const audioh_interface_t *p_audio, uint8_t dir) {
- for (uint8_t as_idx = 0; as_idx < p_audio->as_count; as_idx++) {
- const tuh_audio_as_info_t *as = &p_audio->as[as_idx];
- if (as->ep_addr != 0 && as->ep_dir == dir) {
- return as->ep_addr;
- }
+//--------------------------------------------------------------------+
+// Packet scheduler
+//--------------------------------------------------------------------+
+
+// Re-arm the capture endpoint: request one full packet (the device sends at
+// most its max packet size per poll interval). Only submit while the whole
+// packet fits into the FIFO — otherwise the frame is lost anyway and the
+// transfer would be wasted; the stream resumes when tuh_audio_read() frees
+// FIFO space.
+static void audioh_stream_capture_xfer(tuh_audio_stream_t *s) {
+ TU_VERIFY(s->state == STREAM_STATE_READY && s->running, );
+
+ const audioh_stream_map_t *map = &s->map[s->active_config];
+ TU_VERIFY(tu_fifo_remaining(&s->edpt.ff) >= map->ep_size, );
+ TU_VERIFY(usbh_edpt_claim(s->daddr, map->ep_addr), ); // one transfer in flight
+
+ // ep_size is guaranteed <= CFG_TUH_AUDIO_EPIN_BUFSIZE by enumeration
+ TU_ASSERT(usbh_edpt_xfer(s->daddr, map->ep_addr, s->edpt.ep_buf, map->ep_size), );
+}
+
+// Submit the next queued playback packet. The device consumes
+// sample_rate / 1000 frames per USB frame; the fractional remainder
+// (0.1 frame per ms at 44.1 kHz) is accumulated on each successful
+// submission and paid back as one extra frame, keeping the average data
+// rate exactly at the sample rate. Whole frames only, limited by the
+// queued data, one endpoint packet, and the transfer buffer.
+static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) {
+ TU_VERIFY(s->state == STREAM_STATE_READY && s->running, );
+
+ const audioh_stream_map_t *map = &s->map[s->active_config];
+ TU_VERIFY(usbh_edpt_claim(s->daddr, map->ep_addr), ); // one transfer in flight
+
+ uint16_t frames = s->frames_per_ms;
+ s->rem_acc += s->frames_rem;
+ if (s->rem_acc >= 1000) {
+ s->rem_acc -= 1000;
+ frames++;
}
- return 0;
+ frames = TU_MIN(frames, (uint16_t)(tu_fifo_count(&s->edpt.ff) / s->frame_bytes));
+ frames = TU_MIN(frames, (uint16_t)(map->ep_size / s->frame_bytes));
+ frames = TU_MIN(frames, (uint16_t)(CFG_TUH_AUDIO_EPOUT_BUFSIZE / s->frame_bytes));
+ if (frames == 0) {
+ // nothing queued: the stream stays idle until the application writes again
+ usbh_edpt_release(s->daddr, map->ep_addr);
+ return;
+ }
+
+ const uint16_t bytes = frames * s->frame_bytes;
+ tu_fifo_read_n(&s->edpt.ff, s->edpt.ep_buf, bytes);
+ TU_ASSERT(usbh_edpt_xfer(s->daddr, map->ep_addr, s->edpt.ep_buf, bytes), );
+}
+
+//--------------------------------------------------------------------+
+// Configure state machine
+//--------------------------------------------------------------------+
+
+static void audioh_stream_fail(tuh_audio_stream_t *s, tusb_xfer_result_t result) {
+ s->state = STREAM_STATE_IDLE;
+ s->active_config = TUSB_INDEX_INVALID_8;
+ s->running = false;
+
+ tuh_audio_configure_cb_t cb = s->complete_cb;
+ uintptr_t user_data = s->user_data;
+ s->complete_cb = NULL;
+ if (cb != NULL) {
+ cb(s->idx, s->stream_idx, result, user_data);
+ }
+}
+
+static void audioh_stream_ready(tuh_audio_stream_t *s) {
+ s->state = STREAM_STATE_READY;
+
+ tuh_audio_configure_cb_t cb = s->complete_cb;
+ uintptr_t user_data = s->user_data;
+ s->complete_cb = NULL;
+ if (cb != NULL) {
+ cb(s->idx, s->stream_idx, XFER_RESULT_SUCCESS, user_data);
+ }
+}
+
+static void audioh_stream_set_freq_complete(tuh_xfer_t *xfer) {
+ tuh_audio_stream_t *s = (tuh_audio_stream_t *)xfer->user_data;
+ if (s->daddr != xfer->daddr || s->state != STREAM_STATE_CONFIG) {
+ return; // device is gone or configuration was aborted
+ }
+
+ if (xfer->result != XFER_RESULT_SUCCESS) {
+ TU_LOG_DRV(" AUDIO set sampling frequency failed: result=%u\r\n", xfer->result);
+ audioh_stream_fail(s, xfer->result);
+ return;
+ }
+ audioh_stream_ready(s);
+}
+
+// Set the endpoint sampling frequency (3 bytes little-endian) when supported
+static void audioh_stream_set_freq(tuh_audio_stream_t *s) {
+ const audioh_stream_map_t *map = &s->map[s->active_config];
+ const tuh_audio_stream_config_t *cfg = &s->config[s->active_config];
+
+ s->ctrl[0] = (uint8_t)(cfg->sample_rate & 0xFF);
+ s->ctrl[1] = (uint8_t)((cfg->sample_rate >> 8) & 0xFF);
+ s->ctrl[2] = (uint8_t)((cfg->sample_rate >> 16) & 0xFF);
+
+ const tusb_control_request_t request =
+ {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_ENDPOINT, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT},
+ .bRequest = AUDIO10_CS_REQ_SET_CUR,
+ .wValue = tu_htole16(tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0)), // control selector, channel 0
+ .wIndex = tu_htole16(map->ep_addr),
+ .wLength = 3};
+
+ tuh_xfer_t xfer = {.daddr = s->daddr,
+ .ep_addr = 0,
+ .setup = &request,
+ .buffer = s->ctrl,
+ .complete_cb = audioh_stream_set_freq_complete,
+ .user_data = (uintptr_t)s};
+ if (!tuh_control_xfer(&xfer)) {
+ audioh_stream_fail(s, XFER_RESULT_FAILED);
+ }
+}
+
+// Reconstruct the endpoint descriptor of the selected configuration and open it
+static void audioh_stream_open_ep(tuh_audio_stream_t *s) {
+ const audioh_stream_map_t *map = &s->map[s->active_config];
+
+ const tusb_desc_endpoint_t desc_ep = {.bLength = sizeof(tusb_desc_endpoint_t),
+ .bDescriptorType = TUSB_DESC_ENDPOINT,
+ .bEndpointAddress = map->ep_addr,
+ .bmAttributes = {.xfer = TUSB_XFER_ISOCHRONOUS,
+ .sync = map->ep_sync,
+ .usage = map->ep_usage},
+ .wMaxPacketSize = tu_htole16(map->ep_size),
+ .bInterval = map->ep_interval};
+
+ if (!tuh_edpt_open(s->daddr, &desc_ep)) {
+ TU_LOG_DRV(" AUDIO open endpoint failed: addr=%u ep=%02x\r\n", s->daddr, map->ep_addr);
+ audioh_stream_fail(s, XFER_RESULT_FAILED);
+ return;
+ }
+
+ // Bind the transfer helper to the endpoint and start with an empty FIFO
+ const uint16_t xfer_len = (s->dir == TUSB_DIR_IN) ? CFG_TUH_AUDIO_EPIN_BUFSIZE : CFG_TUH_AUDIO_EPOUT_BUFSIZE;
+ tu_edpt_stream_open(&s->edpt, s->daddr, &desc_ep, xfer_len);
+ tu_edpt_stream_clear(&s->edpt);
+
+ if (map->sam_freq_ctrl) {
+ audioh_stream_set_freq(s);
+ } else {
+ audioh_stream_ready(s);
+ }
+}
+
+static void audioh_stream_set_interface_complete(tuh_xfer_t *xfer) {
+ tuh_audio_stream_t *s = (tuh_audio_stream_t *)xfer->user_data;
+ if (s->daddr != xfer->daddr || s->state != STREAM_STATE_CONFIG) {
+ return; // device is gone or configuration was aborted
+ }
+
+ if (xfer->result != XFER_RESULT_SUCCESS) {
+ TU_LOG_DRV(" AUDIO SET_INTERFACE failed: itf=%u alt=%u result=%u\r\n", s->map[s->active_config].itf_num,
+ s->map[s->active_config].alt_setting, xfer->result);
+ audioh_stream_fail(s, xfer->result);
+ return;
+ }
+ audioh_stream_open_ep(s);
}
//--------------------------------------------------------------------+
@@ -146,44 +472,352 @@ static uint8_t audioh_get_ep_addr_by_dir(const audioh_interface_t *p_audio, uint
//--------------------------------------------------------------------+
bool audioh_init(void) {
tu_memclr(&_audioh_itf, sizeof(_audioh_itf));
+
+ for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) {
+ tuh_audio_stream_t *in = &_audioh_itf[idx].in_stream;
+ tuh_audio_stream_t *out = &_audioh_itf[idx].out_stream;
+
+ in->idx = idx;
+ in->dir = TUSB_DIR_IN;
+ out->idx = idx;
+ out->dir = TUSB_DIR_OUT;
+
+ // Bind FIFO buffer and transfer buffer (see tu_edpt_stream_init)
+ TU_VERIFY(tu_edpt_stream_init(&in->edpt, true, false, false, in->ff_buf, CFG_TUH_AUDIO_STREAM_BUFSIZE,
+ _audioh_epbuf[idx].epin));
+ TU_VERIFY(tu_edpt_stream_init(&out->edpt, true, true, false, out->ff_buf, CFG_TUH_AUDIO_STREAM_BUFSIZE,
+ _audioh_epbuf[idx].epout));
+
+ audioh_stream_reset(in);
+ audioh_stream_reset(out);
+ }
return true;
}
bool audioh_deinit(void) {
+ for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) {
+ tu_edpt_stream_deinit(&_audioh_itf[idx].in_stream.edpt);
+ tu_edpt_stream_deinit(&_audioh_itf[idx].out_stream.edpt);
+ }
return true;
}
void audioh_close(uint8_t daddr) {
for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) {
audioh_interface_t *p_audio = &_audioh_itf[idx];
- if (p_audio->daddr == daddr) {
- TU_LOG_DRV(" AUDIO close addr = %u index = %u\r\n", daddr, idx);
+ if (p_audio->daddr != daddr) {
+ continue;
+ }
+
+ TU_LOG_DRV(" AUDIO close addr = %u index = %u\r\n", daddr, idx);
+ if (p_audio->mounted) {
tuh_audio_umount_cb(idx);
+ }
- p_audio->ac_itf_num = 0;
- p_audio->daddr = 0;
- p_audio->mounted = false;
- p_audio->as_count = 0;
- p_audio->as_set_idx = 0;
- tu_memclr(p_audio->as, sizeof(p_audio->as));
+ // Abort a configuration in progress so the application callback still fires
+ for (uint8_t s = 0; s < 2; s++) {
+ tuh_audio_stream_t *stream = (s == 0) ? &p_audio->in_stream : &p_audio->out_stream;
+ if (stream->state == STREAM_STATE_CONFIG && stream->complete_cb != NULL) {
+ audioh_stream_fail(stream, XFER_RESULT_ABORTED);
+ }
+ audioh_stream_reset(stream);
}
+
+ _audioh_epbuf[idx].complete_cb = NULL; // drop a pending feature-unit GET
+
+ p_audio->stream_count = 0;
+ p_audio->daddr = 0;
+ p_audio->mounted = false;
}
}
bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
- (void)result;
- if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
- tuh_audio_rx_cb(dev_addr, ep_addr, (uint16_t)xferred_bytes);
- } else {
- tuh_audio_tx_cb(dev_addr, ep_addr, (uint16_t)xferred_bytes);
+ tuh_audio_stream_t *s = audioh_find_stream(dev_addr, ep_addr);
+ if (s == NULL) {
+ return false;
}
+ // Failed, stalled, or aborted transfers never carry valid audio data
+ if (result != XFER_RESULT_SUCCESS) {
+ TU_LOG_DRV(" AUDIO transfer failed: addr=%u ep=%02x result=%u\r\n", dev_addr, ep_addr, result);
+ s->running = false;
+ tu_edpt_stream_clear(&s->edpt); // discard queued data
+ tuh_audio_err_cb(s->idx, s->stream_idx, (uint16_t)xferred_bytes);
+ return true;
+ }
+
+ // Stopped stream: the in-flight transfer completes and its data is discarded
+ if (!s->running) {
+ return true;
+ }
+
+ if (s->dir == TUSB_DIR_IN) {
+ // Capture: move the received bytes into the FIFO (whole frames only),
+ // notify, then re-arm for the next packet
+ const uint16_t bytes = (uint16_t)(xferred_bytes - (xferred_bytes % s->frame_bytes));
+ if (bytes > 0) {
+ tu_fifo_write_n(&s->edpt.ff, s->edpt.ep_buf, bytes);
+ }
+ tuh_audio_capture_cb(s->idx, s->stream_idx, (uint16_t)xferred_bytes);
+ audioh_stream_capture_xfer(s);
+ } else {
+ // Playback: notify, then submit the next queued packet
+ tuh_audio_playback_cb(s->idx, s->stream_idx, (uint16_t)xferred_bytes);
+ audioh_stream_playback_xfer(s);
+ }
return true;
}
//--------------------------------------------------------------------+
// Enumeration
//--------------------------------------------------------------------+
+
+// AC header interface collection (baInterfaceNr) bounds-checked
+typedef struct TU_ATTR_PACKED {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubType;
+ uint16_t bcdADC;
+ uint16_t wTotalLength;
+ uint8_t bInCollection;
+ uint8_t baInterfaceNr[AUDIOH_MAX_COLLECTION];
+} audioh_ac_header_t;
+
+static bool audioh_itf_in_collection(const audioh_ac_header_t *header, uint8_t itf_num) {
+ for (uint8_t i = 0; i < header->bInCollection; i++) {
+ if (header->baInterfaceNr[i] == itf_num) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Parse one Audio Streaming interface alternate setting and register its
+// supported configurations into the matching stream. Returns the descriptor
+// pointer of the next interface.
+static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_desc_interface_t *desc_itf,
+ const uint8_t *p_desc, const uint8_t *desc_end) {
+ const uint8_t itf_num = desc_itf->bInterfaceNumber;
+ const uint8_t alt = desc_itf->bAlternateSetting;
+
+ p_desc = tu_desc_next(p_desc);
+
+ // Alternate setting 0 has no endpoints: nothing to stream
+ if (alt == 0 || desc_itf->bNumEndpoints == 0) {
+ while (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) != TUSB_DESC_INTERFACE) {
+ p_desc = tu_desc_next(p_desc);
+ }
+ return p_desc;
+ }
+
+ // Parse the class-specific and endpoint descriptors of this alternate setting
+ uint16_t format_tag = 0;
+ uint8_t num_channels = 0;
+ uint8_t subframe_size = 0;
+ uint8_t bit_res = 0;
+ uint8_t sam_freq_type = 0;
+ uint8_t sam_freq_count = 0; // 1 for a continuous range
+ uint32_t sam_freq[CFG_TUH_AUDIO_MAX_SAM_FREQ] = {0};
+
+ // An alternate setting can expose an endpoint in each direction
+ typedef struct {
+ uint8_t ep_addr;
+ uint16_t ep_size;
+ uint8_t ep_interval;
+ uint8_t ep_sync;
+ uint8_t ep_usage;
+ bool sam_freq_ctrl;
+ } audioh_ep_info_t;
+ audioh_ep_info_t ep_info[2] = {0};
+ uint8_t ep_count = 0;
+ // The CS_ENDPOINT descriptor carries the sampling-frequency control bit of
+ // its endpoint. Devices differ in whether it precedes or follows the
+ // standard endpoint descriptor, so attribute it in either order.
+ bool pending_sam_freq_ctrl = false; // CS_ENDPOINT seen, applies to the next endpoint
+ bool unassigned_ep = false; // endpoint seen, applies to the next CS_ENDPOINT
+
+ while (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) != TUSB_DESC_INTERFACE) {
+ switch (tu_desc_type(p_desc)) {
+ case TUSB_DESC_CS_INTERFACE: {
+ switch (tu_desc_subtype(p_desc)) {
+ case AUDIO10_CS_AS_INTERFACE_AS_GENERAL: {
+ const audio10_desc_cs_as_interface_t *desc_as_general = (const audio10_desc_cs_as_interface_t *)p_desc;
+ if (desc_as_general->bLength >= 5) {
+ format_tag = tu_le16toh(desc_as_general->wFormatTag);
+ }
+ break;
+ }
+ case AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE: {
+ TU_ASSERT(p_desc[0] >= 8, p_desc);
+ if (p_desc[3] != AUDIO10_FORMAT_TYPE_I) {
+ break; // only Type I (PCM) is supported
+ }
+ num_channels = p_desc[4];
+ subframe_size = p_desc[5];
+ bit_res = p_desc[6];
+ sam_freq_type = p_desc[7];
+ if (sam_freq_type == 0) {
+ // Continuous range: expose a single configuration at the
+ // highest supported sampling frequency (tSamFreq[0] is the
+ // lower bound, tSamFreq[1] the upper bound)
+ if (p_desc[0] >= 14) {
+ sam_freq_count = 1;
+ sam_freq[0] = ((uint32_t)p_desc[11] | ((uint32_t)p_desc[12] << 8) | ((uint32_t)p_desc[13] << 16));
+ TU_LOG_DRV(" AUDIO AS itf %u: continuous range %lu-%lu Hz, using %lu Hz\r\n", itf_num,
+ (unsigned long)((uint32_t)p_desc[8] | ((uint32_t)p_desc[9] << 8) |
+ ((uint32_t)p_desc[10] << 16)),
+ (unsigned long)sam_freq[0], (unsigned long)sam_freq[0]);
+ }
+ } else {
+ sam_freq_count = TU_MIN(sam_freq_type, CFG_TUH_AUDIO_MAX_SAM_FREQ);
+ for (uint8_t i = 0; i < sam_freq_count && (8 + i * 3 + 2) < p_desc[0]; i++) {
+ sam_freq[i] = ((uint32_t)p_desc[8 + i * 3] | ((uint32_t)p_desc[9 + i * 3] << 8) |
+ ((uint32_t)p_desc[10 + i * 3] << 16));
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case TUSB_DESC_CS_ENDPOINT: {
+ if (tu_desc_subtype(p_desc) == AUDIO10_CS_EP_SUBTYPE_GENERAL && p_desc[0] >= 4) {
+ const audio10_desc_cs_as_iso_data_ep_t *desc_ep = (const audio10_desc_cs_as_iso_data_ep_t *)p_desc;
+ const bool sam_freq_ctrl = (desc_ep->bmAttributes & 0x01) != 0;
+ if (unassigned_ep) {
+ // Standard order: the CS_ENDPOINT follows its endpoint descriptor
+ ep_info[ep_count - 1].sam_freq_ctrl = sam_freq_ctrl;
+ unassigned_ep = false;
+ } else {
+ // Non-standard order: the CS_ENDPOINT precedes its endpoint descriptor
+ pending_sam_freq_ctrl = sam_freq_ctrl;
+ }
+ }
+ break;
+ }
+ case TUSB_DESC_ENDPOINT: {
+ const tusb_desc_endpoint_t *desc_endpoint = (const tusb_desc_endpoint_t *)p_desc;
+ if (desc_endpoint->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS && ep_count < 2) {
+ audioh_ep_info_t *ep = &ep_info[ep_count];
+ ep->ep_addr = desc_endpoint->bEndpointAddress;
+ ep->ep_size = tu_edpt_packet_size(desc_endpoint);
+ ep->ep_interval = desc_endpoint->bInterval;
+ // bInterval must be in [1, 16] for isochronous endpoints
+ if (ep->ep_interval == 0 || ep->ep_interval > 16) {
+ ep->ep_interval = 1;
+ }
+ ep->ep_sync = desc_endpoint->bmAttributes.sync;
+ ep->ep_usage = desc_endpoint->bmAttributes.usage;
+ ep->sam_freq_ctrl = pending_sam_freq_ctrl;
+ pending_sam_freq_ctrl = false;
+ unassigned_ep = !ep->sam_freq_ctrl;
+ ep_count++;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ if (ep_count == 0) {
+ return p_desc;
+ }
+
+ // Reject unsupported formats explicitly
+ if (format_tag != AUDIO10_DATA_FORMAT_TYPE_I_PCM) {
+ TU_LOG_DRV(" AUDIO AS itf %u: format tag 0x%04x not supported\r\n", itf_num, format_tag);
+ return p_desc;
+ }
+ tuh_audio_format_t format;
+ if (!audioh_format_from_uac1(subframe_size, bit_res, &format)) {
+ TU_LOG_DRV(" AUDIO AS itf %u: subframe %u bits %u not supported\r\n", itf_num, subframe_size, bit_res);
+ return p_desc;
+ }
+ if (num_channels == 0) {
+ TU_LOG_DRV(" AUDIO AS itf %u: zero channels not supported\r\n", itf_num);
+ return p_desc;
+ }
+
+ // Register one configuration per (endpoint, discrete sampling frequency)
+ const uint8_t frame_bytes = num_channels * tuh_audio_format_bytes(format);
+ for (uint8_t e = 0; e < ep_count; e++) {
+ const audioh_ep_info_t *ep = &ep_info[e];
+ tuh_audio_stream_t *stream = audioh_get_stream(p_audio, tu_edpt_dir(ep->ep_addr));
+ if (stream == NULL) {
+ continue;
+ }
+
+ const uint16_t epbuf_size = (stream->dir == TUSB_DIR_IN) ? CFG_TUH_AUDIO_EPIN_BUFSIZE : CFG_TUH_AUDIO_EPOUT_BUFSIZE;
+
+ // Capture: the device can deliver up to its max packet size per poll
+ // interval, the transfer buffer must fit it
+ if (stream->dir == TUSB_DIR_IN && ep->ep_size > epbuf_size) {
+ TU_LOG_DRV(" AUDIO AS itf %u alt %u: capture ep size %u exceeds transfer buffer %u\r\n", itf_num, alt,
+ ep->ep_size, epbuf_size);
+ continue;
+ }
+
+ for (uint8_t i = 0; i < sam_freq_count; i++) {
+ if (sam_freq[i] == 0) {
+ continue;
+ }
+
+ // Playback: the device accepts any packet up to its max packet size
+ // (often advertised larger than the audio rate needs), but the largest
+ // scheduled packet must still fit the transfer buffer
+ if (stream->dir == TUSB_DIR_OUT) {
+ const uint64_t per_interval =
+ (uint64_t)sam_freq[i] * frame_bytes * audioh_interval_us(ep->ep_interval, p_audio->daddr);
+ const uint32_t need = (uint32_t)((per_interval + 999999u) / 1000000u);
+ if (need > epbuf_size) {
+ TU_LOG_DRV(" AUDIO AS itf %u alt %u: playback needs %u B per interval, transfer buffer is %u\r\n", itf_num,
+ alt, (unsigned)need, epbuf_size);
+ continue;
+ }
+ }
+
+ // Skip duplicate configurations
+ bool duplicate = false;
+ for (uint8_t j = 0; j < stream->config_count; j++) {
+ if (stream->config[j].format == format && stream->config[j].sample_rate == sam_freq[i] &&
+ stream->config[j].channels == num_channels) {
+ duplicate = true;
+ break;
+ }
+ }
+ if (duplicate) {
+ continue;
+ }
+
+ if (stream->config_count >= AUDIOH_MAX_CONFIGS) {
+ TU_LOG_DRV(" AUDIO AS itf %u alt %u: reach max configurations %u\r\n", itf_num, alt, AUDIOH_MAX_CONFIGS);
+ return p_desc;
+ }
+
+ stream->config[stream->config_count].dir =
+ (stream->dir == TUSB_DIR_IN) ? TUH_AUDIO_STREAM_CAPTURE : TUH_AUDIO_STREAM_PLAYBACK;
+ stream->config[stream->config_count].format = format;
+ stream->config[stream->config_count].sample_rate = sam_freq[i];
+ stream->config[stream->config_count].channels = num_channels;
+ stream->map[stream->config_count].itf_num = itf_num;
+ stream->map[stream->config_count].alt_setting = alt;
+ stream->map[stream->config_count].ep_addr = ep->ep_addr;
+ stream->map[stream->config_count].ep_size = ep->ep_size;
+ stream->map[stream->config_count].ep_interval = ep->ep_interval;
+ stream->map[stream->config_count].ep_sync = ep->ep_sync;
+ stream->map[stream->config_count].ep_usage = ep->ep_usage;
+ stream->map[stream->config_count].sam_freq_ctrl = ep->sam_freq_ctrl;
+ stream->config_count++;
+ }
+ }
+
+ return p_desc;
+}
+
uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len) {
(void)rhport;
@@ -197,40 +831,44 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface
const uint8_t idx = find_new_audio_index();
TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, 0);
audioh_interface_t *p_audio = &_audioh_itf[idx];
- p_audio->itf_count = 0;
+ p_audio->daddr = dev_addr;
+ p_audio->ac_itf_num = desc_itf->bInterfaceNumber;
+ audioh_stream_reset(&p_audio->in_stream);
+ audioh_stream_reset(&p_audio->out_stream);
+ p_audio->in_stream.daddr = dev_addr;
+ p_audio->out_stream.daddr = dev_addr;
- // Parse Audio Control Interface
TU_LOG_DRV("AUDIO opening AC Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr);
- p_audio->ac_itf_num = desc_itf->bInterfaceNumber;
- p_audio->itf_count = 1;
- // Parse Audio Control interface descriptors (Input Terminal, Output Terminal, Feature Unit, etc.)
+ // Parse the Audio Control interface descriptors and the interface collection
+ audioh_ac_header_t header = {0};
+ bool have_header = false;
+
p_desc = tu_desc_next(p_desc);
while (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) != TUSB_DESC_INTERFACE) {
if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE) {
switch (tu_desc_subtype(p_desc)) {
- case AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL: {
- const audio10_desc_input_terminal_t *desc_input_terminal = (const audio10_desc_input_terminal_t *)p_desc;
- p_audio->input_terminal_type = tu_le16toh(desc_input_terminal->wTerminalType);
- p_audio->input_terminal_id = desc_input_terminal->bTerminalID;
- p_audio->input_terminal_channels = desc_input_terminal->bNrChannels;
- TU_LOG_DRV(" Input Terminal: ID=%u, Type=0x%04x, Channels=%u\r\n", desc_input_terminal->bTerminalID,
- tu_le16toh(desc_input_terminal->wTerminalType), desc_input_terminal->bNrChannels);
- break;
- }
- case AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL: {
- const audio10_desc_output_terminal_t *desc_output_terminal = (const audio10_desc_output_terminal_t *)p_desc;
- p_audio->output_terminal_type = tu_le16toh(desc_output_terminal->wTerminalType);
- p_audio->output_terminal_id = desc_output_terminal->bTerminalID;
- TU_LOG_DRV(" Output Terminal: ID=%u, Type=0x%04x\r\n", desc_output_terminal->bTerminalID,
- tu_le16toh(desc_output_terminal->wTerminalType));
+ case AUDIO10_CS_AC_INTERFACE_HEADER: {
+ const audioh_ac_header_t *desc_header = (const audioh_ac_header_t *)p_desc;
+ if (desc_header->bLength >= 8) {
+ header.bInCollection = desc_header->bInCollection;
+ // The collection array must not extend past the descriptor itself
+ const uint8_t max_collection = TU_MIN((uint8_t)(desc_header->bLength - 8), (uint8_t)AUDIOH_MAX_COLLECTION);
+ if (header.bInCollection > max_collection) {
+ TU_LOG_DRV(" AUDIO AC header collection truncated to %u interfaces\r\n", max_collection);
+ header.bInCollection = max_collection;
+ }
+ if (header.bInCollection > 0) {
+ memcpy(header.baInterfaceNr, desc_header->baInterfaceNr, header.bInCollection);
+ // An empty collection falls back to the interface-class heuristic
+ have_header = true;
+ }
+ }
break;
}
case AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT: {
- const uint8_t *desc_feature_unit = p_desc;
- p_audio->feature_unit_id = desc_feature_unit[3]; // bUnitID
- p_audio->feature_unit_source_id = desc_feature_unit[4]; // bSourceID
- TU_LOG_DRV(" Feature Unit: ID=%u, SourceID=%u\r\n", desc_feature_unit[3], desc_feature_unit[4]);
+ p_audio->feature_unit_id = p_desc[3]; // bUnitID
+ TU_LOG_DRV(" Feature Unit: ID=%u\r\n", p_audio->feature_unit_id);
break;
}
default:
@@ -240,246 +878,72 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface
p_desc = tu_desc_next(p_desc);
}
- // Parse all remaining descriptors in this configuration looking for Audio Streaming interfaces
+ // Parse the Audio Streaming interfaces of this audio function. Interfaces
+ // outside the AC header's collection (e.g. MIDI Streaming interfaces) are
+ // left for other class drivers.
while (tu_desc_in_bounds(p_desc, desc_end)) {
- if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) {
- const tusb_desc_interface_t *desc_interface = (const tusb_desc_interface_t *)p_desc;
- // Stop at the first non-Audio interface so we don't claim the rest of the configuration
- if (desc_interface->bInterfaceClass != TUSB_CLASS_AUDIO) {
- break;
- }
- if (desc_interface->bInterfaceSubClass == AUDIO_SUBCLASS_STREAMING) {
- // Found Audio Streaming Interface
- TU_LOG_DRV(" Found AS Interface %u (alt = %u)\r\n", desc_interface->bInterfaceNumber,
- desc_interface->bAlternateSetting);
-
- if (desc_interface->bAlternateSetting == 0) {
- // Interface descriptor with alt setting 0 (no endpoints)
- // Add to AS entries
- if (p_audio->as_count < CFG_TUH_AUDIO_MAX_AS) {
- p_audio->as[p_audio->as_count].interface_num = desc_interface->bInterfaceNumber;
- p_audio->as[p_audio->as_count].alt_setting = 0;
- p_audio->as_count++;
- } else {
- TU_LOG_DRV(" Skip AS Interface %u: reach CFG_TUH_AUDIO_MAX_AS=%u\r\n", desc_interface->bInterfaceNumber,
- CFG_TUH_AUDIO_MAX_AS);
- }
- } else if (desc_interface->bNumEndpoints > 0) {
- // Interface descriptor with alt setting > 0 (has endpoints)
- // Find matching AS entry and set alt_setting
- uint8_t as_entry_idx = CFG_TUH_AUDIO_MAX_AS;
- for (uint8_t i = 0; i < p_audio->as_count; i++) {
- if (p_audio->as[i].interface_num == desc_interface->bInterfaceNumber) {
- as_entry_idx = i;
- break;
- }
- }
- if (as_entry_idx >= CFG_TUH_AUDIO_MAX_AS && p_audio->as_count < CFG_TUH_AUDIO_MAX_AS) {
- as_entry_idx = p_audio->as_count;
- p_audio->as[as_entry_idx].interface_num = desc_interface->bInterfaceNumber;
- p_audio->as_count++;
- }
- if (as_entry_idx < CFG_TUH_AUDIO_MAX_AS) {
- p_audio->as[as_entry_idx].alt_setting = desc_interface->bAlternateSetting;
- }
+ if (tu_desc_type(p_desc) != TUSB_DESC_INTERFACE) {
+ p_desc = tu_desc_next(p_desc);
+ continue;
+ }
- // Parse the interface's descriptors
- p_desc = tu_desc_next(p_desc);
- // Temporary variables to hold format info until endpoint direction is known
- uint8_t tmp_format_type = 0;
- uint8_t tmp_num_channels = 0;
- uint8_t tmp_sub_frame_size = 0;
- uint8_t tmp_bit_resolution = 0;
- uint8_t tmp_sam_freq_type = 0;
- uint32_t tmp_sam_freq[CFG_TUH_AUDIO_MAX_SAM_FREQ] = {0};
- uint32_t tmp_sam_freq_lower = 0;
- uint32_t tmp_sam_freq_upper = 0;
- while (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) != TUSB_DESC_INTERFACE) {
- switch (tu_desc_type(p_desc)) {
- case TUSB_DESC_CS_INTERFACE: {
- switch (tu_desc_subtype(p_desc)) {
- case AUDIO10_CS_AS_INTERFACE_AS_GENERAL: {
- TU_LOG_DRV(" AS General descriptor\r\n");
- break;
- }
- case AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE: {
- TU_LOG_DRV(" Format Type descriptor\r\n");
- TU_ASSERT(p_desc[0] >= 8, 0);
- // Parse UAC 1.0 Format Type I descriptor fields into temporary variables
- tmp_format_type = p_desc[3]; // bFormatType
- tmp_num_channels = p_desc[4]; // bNrChannels
- tmp_sub_frame_size = p_desc[5]; // bSubFrameSize
- tmp_bit_resolution = p_desc[6]; // bBitResolution
+ const tusb_desc_interface_t *desc_interface = (const tusb_desc_interface_t *)p_desc;
+ const bool in_collection = have_header ? audioh_itf_in_collection(&header, desc_interface->bInterfaceNumber)
+ : desc_interface->bInterfaceClass == TUSB_CLASS_AUDIO;
+ if (!in_collection) {
+ break;
+ }
- // Parse sampling frequencies
- uint8_t bLength = p_desc[0];
- if (bLength >= 8) {
- tmp_sam_freq_type = p_desc[7]; // bSamFreqType
- if (tmp_sam_freq_type == 0) {
- // Continuous range: tLowerSamFreq, tUpperSamFreq (3 bytes each)
- if (bLength >= 14) {
- tmp_sam_freq_lower =
- ((uint32_t)p_desc[8] | ((uint32_t)p_desc[9] << 8) | ((uint32_t)p_desc[10] << 16));
- tmp_sam_freq_upper =
- ((uint32_t)p_desc[11] | ((uint32_t)p_desc[12] << 8) | ((uint32_t)p_desc[13] << 16));
- }
- } else {
- // Discrete sampling frequencies
- uint8_t max_freqs = tmp_sam_freq_type < CFG_TUH_AUDIO_MAX_SAM_FREQ ? tmp_sam_freq_type
- : CFG_TUH_AUDIO_MAX_SAM_FREQ;
- for (uint8_t i = 0; i < max_freqs && (8 + i * 3 + 2) < bLength; i++) {
- tmp_sam_freq[i] = ((uint32_t)p_desc[8 + i * 3] | ((uint32_t)p_desc[9 + i * 3] << 8) |
- ((uint32_t)p_desc[10 + i * 3] << 16));
- }
- }
- }
- break;
- }
- default:
- break;
- }
- break;
- }
- case TUSB_DESC_ENDPOINT: {
- const tusb_desc_endpoint_t *desc_endpoint = (const tusb_desc_endpoint_t *)p_desc;
- if (desc_endpoint->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) {
- TU_LOG_DRV(" Isochronous EP %02x\r\n", desc_endpoint->bEndpointAddress);
- if (tu_edpt_dir(desc_endpoint->bEndpointAddress) == TUSB_DIR_IN) {
- // Save to per-AS storage
- if (as_entry_idx < CFG_TUH_AUDIO_MAX_AS) {
- tuh_audio_as_info_t *as = &p_audio->as[as_entry_idx];
- as->ep_addr = desc_endpoint->bEndpointAddress;
- as->ep_size = tu_edpt_packet_size(desc_endpoint);
- as->ep_dir = TUSB_DIR_IN;
- as->format_type = tmp_format_type;
- as->num_channels = tmp_num_channels;
- as->sub_frame_size = tmp_sub_frame_size;
- as->bit_resolution = tmp_bit_resolution;
- as->sam_freq_type = tmp_sam_freq_type;
- as->sam_freq_lower = tmp_sam_freq_lower;
- as->sam_freq_upper = tmp_sam_freq_upper;
- for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX_SAM_FREQ; i++) {
- as->sam_freq[i] = tmp_sam_freq[i];
- }
- }
- } else {
- // Save to per-AS storage
- if (as_entry_idx < CFG_TUH_AUDIO_MAX_AS) {
- tuh_audio_as_info_t *as = &p_audio->as[as_entry_idx];
- as->ep_addr = desc_endpoint->bEndpointAddress;
- as->ep_size = tu_edpt_packet_size(desc_endpoint);
- as->ep_dir = TUSB_DIR_OUT;
- as->format_type = tmp_format_type;
- as->num_channels = tmp_num_channels;
- as->sub_frame_size = tmp_sub_frame_size;
- as->bit_resolution = tmp_bit_resolution;
- as->sam_freq_type = tmp_sam_freq_type;
- as->sam_freq_lower = tmp_sam_freq_lower;
- as->sam_freq_upper = tmp_sam_freq_upper;
- for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX_SAM_FREQ; i++) {
- as->sam_freq[i] = tmp_sam_freq[i];
- }
- }
- }
- TU_ASSERT(tuh_edpt_open(dev_addr, desc_endpoint), 0);
- }
- break;
- }
- default:
- break;
- }
- p_desc = tu_desc_next(p_desc);
- }
- // Continue to parse other AS interfaces (don't break, device may have both IN and OUT)
- // break; // Removed: allow parsing multiple AS interfaces (e.g. mic + speaker)
- continue;
- }
- p_audio->itf_count++;
- } else if (desc_interface->bInterfaceClass == TUSB_CLASS_AUDIO &&
- desc_interface->bInterfaceSubClass == AUDIO_SUBCLASS_CONTROL) {
- // Another Audio Control interface (shouldn't happen in normal UAC 1.0)
- p_audio->itf_count++;
- }
+ if (desc_interface->bInterfaceSubClass == AUDIO_SUBCLASS_STREAMING) {
+ TU_LOG_DRV(" Found AS Interface %u (alt = %u)\r\n", desc_interface->bInterfaceNumber,
+ desc_interface->bAlternateSetting);
+ p_desc = audioh_parse_as(p_audio, desc_interface, p_desc, desc_end);
+ } else {
+ // MIDI Streaming or another subclass: not our interface
+ break;
}
- p_desc = tu_desc_next(p_desc);
}
- p_audio->daddr = dev_addr;
+ // Assign stream indices: playback first, then capture, so the application
+ // can iterate [0, stream_count) without gaps
+ uint8_t stream_idx = 0;
+ if (p_audio->out_stream.config_count > 0) {
+ p_audio->out_stream.stream_idx = stream_idx++;
+ }
+ if (p_audio->in_stream.config_count > 0) {
+ p_audio->in_stream.stream_idx = stream_idx++;
+ }
+ p_audio->stream_count = stream_idx;
return (uint16_t)((uintptr_t)p_desc - (uintptr_t)desc_start);
}
-static void _audioh_mount(uint8_t dev_addr, uint8_t idx);
-
-static void audioh_set_interface_complete(tuh_xfer_t *xfer) {
- uint8_t idx = (uint8_t)xfer->user_data;
- audioh_interface_t *p_audio = &_audioh_itf[idx];
-
- // Send SET_INTERFACE for next AS interface if any
- p_audio->as_set_idx++;
- if (p_audio->as_set_idx < p_audio->as_count) {
- uint8_t as_idx = p_audio->as_set_idx;
- uint8_t itf = p_audio->as[as_idx].interface_num;
- uint8_t alt = p_audio->as[as_idx].alt_setting;
- if (alt > 0) {
- TU_LOG_DRV("AUDIO Set Interface %u Alt %u (addr = %u)\r\n", itf, alt, xfer->daddr);
- tuh_interface_set(xfer->daddr, itf, alt, audioh_set_interface_complete, idx);
- return;
+//--------------------------------------------------------------------+
+// Set Configuration
+//--------------------------------------------------------------------+
+bool audioh_set_config(uint8_t dev_addr, uint8_t itf_num) {
+ uint8_t idx = TUSB_INDEX_INVALID_8;
+ for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX; i++) {
+ if (_audioh_itf[i].daddr == dev_addr && _audioh_itf[i].ac_itf_num == itf_num) {
+ idx = i;
+ break;
}
}
- // All SET_INTERFACE done, mount the device
- _audioh_mount(xfer->daddr, idx);
-}
-
-static void _audioh_mount(uint8_t dev_addr, uint8_t idx) {
- audioh_interface_t *p_audio = &_audioh_itf[idx];
- p_audio->mounted = true;
-
-
- tuh_audio_mount_cb(idx);
-
- usbh_driver_set_config_complete(dev_addr, p_audio->ac_itf_num);
-}
-
-bool audioh_set_config(uint8_t dev_addr, uint8_t itf_num) {
- uint8_t idx = tuh_audio_itf_get_index(dev_addr, itf_num);
-
- // If not found, check if this is an AS interface that belongs to a known AC interface
- if (idx >= CFG_TUH_AUDIO_MAX) {
- for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX; i++) {
- const audioh_interface_t *p_audio = &_audioh_itf[i];
- if (p_audio->daddr == dev_addr) {
- for (uint8_t as_idx = 0; as_idx < p_audio->as_count; as_idx++) {
- if (p_audio->as[as_idx].interface_num == itf_num) {
- // AS interface: configuration is driven by the AC interface, so just pass through
- usbh_driver_set_config_complete(dev_addr, itf_num);
- return true;
- }
- }
- }
- }
- // Not an Audio interface we own; pass through so enumeration can continue
+ if (idx == TUSB_INDEX_INVALID_8) {
+ // Audio Streaming interface (or another driver's interface): nothing to do at mount.
+ // Alternate settings are activated by tuh_audio_configure().
usbh_driver_set_config_complete(dev_addr, itf_num);
return true;
}
audioh_interface_t *p_audio = &_audioh_itf[idx];
- TU_VERIFY(p_audio->as_count <= CFG_TUH_AUDIO_MAX_AS, false);
+ p_audio->mounted = true;
+ TU_LOG_DRV(" AUDIO mounted: addr = %u index = %u\r\n", dev_addr, idx);
- // Send SET_INTERFACE for all AS interfaces with alt_setting > 0
- if (p_audio->as_count > 0) {
- p_audio->as_set_idx = 0;
- uint8_t itf = p_audio->as[0].interface_num;
- uint8_t alt = p_audio->as[0].alt_setting;
- if (alt > 0) {
- TU_LOG_DRV("AUDIO Set Interface %u Alt %u (addr = %u)\r\n", itf, alt, dev_addr);
- tuh_interface_set(dev_addr, itf, alt, audioh_set_interface_complete, idx);
- return true;
- }
- }
+ tuh_audio_mount_cb(idx);
- _audioh_mount(dev_addr, idx);
+ usbh_driver_set_config_complete(dev_addr, itf_num);
return true;
}
@@ -488,143 +952,308 @@ bool audioh_set_config(uint8_t dev_addr, uint8_t itf_num) {
//--------------------------------------------------------------------+
bool tuh_audio_mounted(uint8_t idx) {
TU_VERIFY(idx < CFG_TUH_AUDIO_MAX);
- audioh_interface_t *p_audio = &_audioh_itf[idx];
- return p_audio->mounted;
+ return _audioh_itf[idx].mounted;
}
uint8_t tuh_audio_get_dev_addr(uint8_t idx) {
- audioh_interface_t *p_audio = &_audioh_itf[idx];
- return p_audio->daddr;
+ TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, 0);
+ return _audioh_itf[idx].daddr;
}
-
uint8_t tuh_audio_get_feature_unit_id(uint8_t idx) {
- audioh_interface_t *p_audio = &_audioh_itf[idx];
- return p_audio->feature_unit_id;
+ TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, 0);
+ return _audioh_itf[idx].feature_unit_id;
}
-uint8_t tuh_audio_itf_get_index(uint8_t daddr, uint8_t itf_num) {
- for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) {
- const audioh_interface_t *p_audio = &_audioh_itf[idx];
- if (p_audio->daddr == daddr && p_audio->ac_itf_num == itf_num) {
- return idx;
+uint8_t tuh_audio_stream_count(uint8_t dev_idx) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, 0);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->daddr != 0, 0);
+ return p_audio->stream_count;
+}
+
+bool tuh_audio_stream_exists(uint8_t dev_idx, uint8_t stream_idx) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, false);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->daddr != 0, false);
+ return audioh_get_stream_by_idx(p_audio, stream_idx) != NULL;
+}
+
+tuh_audio_direction_t tuh_audio_stream_direction(uint8_t dev_idx, uint8_t stream_idx) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, TUH_AUDIO_STREAM_DIRECTION_COUNT);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->daddr != 0, TUH_AUDIO_STREAM_DIRECTION_COUNT);
+
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ TU_VERIFY(s, TUH_AUDIO_STREAM_DIRECTION_COUNT);
+ return (s->dir == TUSB_DIR_IN) ? TUH_AUDIO_STREAM_CAPTURE : TUH_AUDIO_STREAM_PLAYBACK;
+}
+
+uint8_t tuh_audio_config_count(uint8_t dev_idx, uint8_t stream_idx) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, 0);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->daddr != 0, 0);
+
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ TU_VERIFY(s, 0);
+ return s->config_count;
+}
+uint8_t tuh_audio_active_config(uint8_t dev_idx, uint8_t stream_idx) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, TUSB_INDEX_INVALID_8);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->daddr != 0, TUSB_INDEX_INVALID_8);
+
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ TU_VERIFY(s, TUSB_INDEX_INVALID_8);
+ return s->active_config;
+}
+bool tuh_audio_config_get(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx, tuh_audio_stream_config_t *config) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, false);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->daddr != 0, false);
+
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ TU_VERIFY(s && config, false);
+ TU_VERIFY(config_idx < s->config_count, false);
+
+ *config = s->config[config_idx];
+ return true;
+}
+
+bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx, tuh_audio_configure_cb_t complete_cb,
+ uintptr_t user_data) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, false);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->mounted, false);
+
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ TU_VERIFY(s && complete_cb, false);
+ TU_VERIFY(config_idx < s->config_count, false);
+ // Reconfiguration is allowed from a stopped stream; only one configuration
+ // may be in progress
+ TU_VERIFY(s->state != STREAM_STATE_CONFIG && !s->running, false);
+ if (s->state == STREAM_STATE_READY) {
+ // Wait for any in-flight transfer to complete and be discarded
+ TU_VERIFY(!usbh_edpt_busy(s->daddr, s->map[s->active_config].ep_addr), false);
+ }
+
+ // A shared AS interface must not be left in two different alternate settings
+ tuh_audio_stream_t *other = (s == &p_audio->out_stream) ? &p_audio->in_stream : &p_audio->out_stream;
+ if (other->active_config != TUSB_INDEX_INVALID_8) {
+ const audioh_stream_map_t *m1 = &s->map[config_idx];
+ const audioh_stream_map_t *m2 = &other->map[other->active_config];
+ if (m1->itf_num == m2->itf_num && m1->alt_setting != m2->alt_setting) {
+ TU_LOG_DRV(" AUDIO configure failed: shared AS itf %u in conflicting alt settings\r\n", m1->itf_num);
+ return false;
}
}
- return TUSB_INDEX_INVALID_8;
-}
-bool tuh_audio_itf_get_info(uint8_t idx, tuh_itf_info_t *info) {
- audioh_interface_t *p_audio = &_audioh_itf[idx];
- TU_VERIFY(p_audio && info);
+ s->active_config = config_idx;
+ s->frame_bytes = (uint8_t)tuh_audio_config_frame_size(&s->config[config_idx]);
+ s->frames_per_ms = (uint16_t)(s->config[config_idx].sample_rate / 1000);
+ s->frames_rem = (uint16_t)(s->config[config_idx].sample_rate % 1000);
+ s->rem_acc = 0;
+ s->complete_cb = complete_cb;
+ s->user_data = user_data;
+ s->state = STREAM_STATE_CONFIG;
- info->daddr = p_audio->daddr;
+ const audioh_stream_map_t *map = &s->map[config_idx];
+ TU_LOG_DRV(" AUDIO configure %s stream %u: itf %u alt %u ep %02x\r\n",
+ (s->dir == TUSB_DIR_IN) ? "capture" : "playback", s->stream_idx, map->itf_num, map->alt_setting,
+ map->ep_addr);
- // re-construct descriptor
- tusb_desc_interface_t *desc_interface = &info->desc;
- desc_interface->bLength = sizeof(tusb_desc_interface_t);
- desc_interface->bDescriptorType = TUSB_DESC_INTERFACE;
+ if (!tuh_interface_set(s->daddr, map->itf_num, map->alt_setting, audioh_stream_set_interface_complete,
+ (uintptr_t)s)) {
+ audioh_stream_fail(s, XFER_RESULT_FAILED);
+ return false;
+ }
+ return true;
+}
- uint8_t ep_in = audioh_get_ep_addr_by_dir(p_audio, TUSB_DIR_IN);
- uint8_t ep_out = audioh_get_ep_addr_by_dir(p_audio, TUSB_DIR_OUT);
+// Invoked when the SET_INTERFACE activating the stream's interface completes:
+// the interface is active, start submitting transfers
+static void audioh_stream_start_complete(tuh_xfer_t *xfer) {
+ tuh_audio_stream_t *s = (tuh_audio_stream_t *)xfer->user_data;
+ if (s->daddr != xfer->daddr || !s->running) {
+ return; // device is gone or the stream was stopped meanwhile
+ }
+ if (xfer->result != XFER_RESULT_SUCCESS) {
+ TU_LOG_DRV(" AUDIO SET_INTERFACE activate failed: result=%u\r\n", xfer->result);
+ s->running = false;
+ return;
+ }
+ if (s->dir == TUSB_DIR_IN) {
+ audioh_stream_capture_xfer(s); // feed the capture endpoint
+ } else {
+ audioh_stream_playback_xfer(s); // flush queued frames, if any
+ }
+}
+
+bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, false);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->mounted, false);
- desc_interface->bInterfaceNumber = p_audio->ac_itf_num;
- desc_interface->bAlternateSetting = 0;
- desc_interface->bNumEndpoints = (uint8_t)((ep_in ? 1u : 0u) + (ep_out ? 1u : 0u));
- desc_interface->bInterfaceClass = TUSB_CLASS_AUDIO;
- desc_interface->bInterfaceSubClass = AUDIO_SUBCLASS_CONTROL;
- desc_interface->bInterfaceProtocol = 0;
- desc_interface->iInterface = 0;
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ TU_VERIFY(s, false);
+ TU_VERIFY(s->state == STREAM_STATE_READY && !s->running, false);
+ // Wait for any in-flight transfer to complete and be discarded
+ TU_VERIFY(!usbh_edpt_busy(s->daddr, s->map[s->active_config].ep_addr), false);
+ // Activate the interface's alternate setting asynchronously: transfers
+ // begin once SET_INTERFACE completes (audioh_stream_start_complete)
+ s->running = true;
+ const audioh_stream_map_t *map = &s->map[s->active_config];
+ if (!tuh_interface_set(s->daddr, map->itf_num, map->alt_setting, audioh_stream_start_complete, (uintptr_t)s)) {
+ s->running = false;
+ return false;
+ }
return true;
}
-//--------------------------------------------------------------------+
-// Control Endpoint API
-//--------------------------------------------------------------------+
-bool tuh_audio_set_sampling_freq(uint8_t idx, uint8_t as_idx, uint32_t sampling_freq, tuh_xfer_cb_t complete_cb,
- uintptr_t user_data) {
- TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false);
- audioh_interface_t *p_audio = &_audioh_itf[idx];
- TU_VERIFY(p_audio && as_idx < p_audio->as_count, false);
+// Invoked when the SET_INTERFACE deactivating the stream's interface (alt 0)
+// completes
+static void audioh_stream_stop_complete(tuh_xfer_t *xfer) {
+ tuh_audio_stream_t *s = (tuh_audio_stream_t *)xfer->user_data;
+ if (s->daddr != xfer->daddr) {
+ return;
+ }
+ TU_LOG_DRV(" AUDIO SET_INTERFACE deactivate done: result=%u\r\n", xfer->result);
+}
- uint8_t ep_addr = p_audio->as[as_idx].ep_addr;
- uint8_t daddr = p_audio->daddr;
- uint8_t *freq_buf = _audioh_epbuf[idx].ctrl;
+bool tuh_audio_stop(uint8_t dev_idx, uint8_t stream_idx) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, false);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->mounted, false);
- const tusb_control_request_t request =
- {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_ENDPOINT, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT},
- .bRequest = AUDIO10_CS_REQ_SET_CUR,
- .wValue = tu_htole16(tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0)), // Control Selector = Sampling Freq, Channel = 0
- .wIndex = tu_htole16((uint16_t)ep_addr),
- .wLength = 3};
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ TU_VERIFY(s && s->running, false);
- // UAC 1.0 sampling frequency is 3 bytes little-endian
- // uint8_t freq_buf[3] = {
- // (uint8_t)(sampling_freq & 0xFF),
- // (uint8_t)((sampling_freq >> 8) & 0xFF),
- // (uint8_t)((sampling_freq >> 16) & 0xFF)
- // };
- freq_buf[0] = (uint8_t)(sampling_freq & 0xFF);
- freq_buf[1] = (uint8_t)((sampling_freq >> 8) & 0xFF);
- freq_buf[2] = (uint8_t)((sampling_freq >> 16) & 0xFF);
- tuh_xfer_t xfer = {.daddr = daddr,
- .ep_addr = 0,
- .setup = &request,
- .buffer = freq_buf,
- .complete_cb = complete_cb,
- .user_data = user_data};
+ // The in-flight transfer (if any) completes and its data is discarded;
+ // queued frames are dropped as well. The interface is deactivated (alt 0)
+ // so the device stops transferring.
+ s->running = false;
+ tu_edpt_stream_clear(&s->edpt);
+ s->rem_acc = 0; // restart the pacing accumulator on the next tuh_audio_start()
- return tuh_control_xfer(&xfer);
+ const audioh_stream_map_t *map = &s->map[s->active_config];
+ return tuh_interface_set(s->daddr, map->itf_num, 0, audioh_stream_stop_complete, (uintptr_t)s);
}
-bool tuh_audio_get_sampling_freq(uint8_t idx, uint8_t as_idx, uint32_t *sampling_freq, tuh_xfer_cb_t complete_cb,
- uintptr_t user_data) {
- TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false);
- audioh_interface_t *p_audio = &_audioh_itf[idx];
- TU_VERIFY(p_audio && as_idx < p_audio->as_count && sampling_freq, false);
- uint8_t ep_addr = p_audio->as[as_idx].ep_addr;
- uint8_t daddr = p_audio->daddr;
+uint32_t tuh_audio_write(uint8_t dev_idx, uint8_t stream_idx, const void *buffer, uint32_t frame_count) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, 0);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->mounted && buffer, 0);
- *sampling_freq = 0;
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ // Writes are only accepted by the playback stream
+ TU_VERIFY(s && s->dir == TUSB_DIR_OUT, 0);
+ TU_VERIFY(s->state == STREAM_STATE_READY && s->running, 0);
+ TU_VERIFY(frame_count > 0, 0);
- const tusb_control_request_t request =
- {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_ENDPOINT, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN},
- .bRequest = AUDIO10_CS_REQ_GET_CUR,
- .wValue = tu_htole16(tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0)), // Control Selector = Sampling Freq, Channel = 0
- .wIndex = tu_htole16((uint16_t)ep_addr),
- .wLength = 3};
+ // Queue as many whole frames as the FIFO can hold
+ const uint32_t frames = TU_MIN(frame_count, tu_fifo_remaining(&s->edpt.ff) / s->frame_bytes);
+ if (frames == 0) {
+ return 0;
+ }
+ tu_fifo_write_n(&s->edpt.ff, buffer, (uint16_t)(frames * s->frame_bytes));
- // Application needs to parse 3-byte little-endian sampling frequency from buffer
- tuh_xfer_t xfer = {.daddr = daddr,
- .ep_addr = 0,
- .setup = &request,
- .buffer = (uint8_t *)sampling_freq,
- .complete_cb = complete_cb,
- .user_data = user_data};
+ // Flush a packet when the FIFO holds at least one; the scheduler drains
+ // the rest on completion
+ audioh_stream_playback_xfer(s);
- return tuh_control_xfer(&xfer);
+ return frames;
+}
+
+uint32_t tuh_audio_read(uint8_t dev_idx, uint8_t stream_idx, void *buffer, uint32_t frame_count) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, 0);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->mounted && buffer, 0);
+
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ // Reads are only accepted by the capture stream
+ TU_VERIFY(s && s->dir == TUSB_DIR_IN, 0);
+ TU_VERIFY(s->state == STREAM_STATE_READY && s->running, 0);
+ TU_VERIFY(frame_count > 0, 0);
+
+ // Drain as many whole frames as are queued
+ const uint32_t frames = TU_MIN(frame_count, tu_fifo_count(&s->edpt.ff) / s->frame_bytes);
+ if (frames > 0) {
+ tu_fifo_read_n(&s->edpt.ff, buffer, (uint16_t)(frames * s->frame_bytes));
+ audioh_stream_capture_xfer(s); // re-arm: the FIFO has room again
+ }
+ return frames;
+}
+
+uint32_t tuh_audio_write_available(uint8_t dev_idx, uint8_t stream_idx) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, 0);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->daddr != 0, 0);
+
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ TU_VERIFY(s && s->dir == TUSB_DIR_OUT, 0);
+ TU_VERIFY(s->state == STREAM_STATE_READY && s->running, 0);
+ return tu_edpt_stream_write_available(&s->edpt) / s->frame_bytes;
+}
+
+uint32_t tuh_audio_read_available(uint8_t dev_idx, uint8_t stream_idx) {
+ TU_VERIFY(dev_idx < CFG_TUH_AUDIO_MAX, 0);
+ audioh_interface_t *p_audio = &_audioh_itf[dev_idx];
+ TU_VERIFY(p_audio->daddr != 0, 0);
+
+ tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
+ TU_VERIFY(s && s->dir == TUSB_DIR_IN, 0);
+ TU_VERIFY(s->state == STREAM_STATE_READY && s->running, 0);
+ return tu_edpt_stream_read_available(&s->edpt) / s->frame_bytes;
+}
+
+//--------------------------------------------------------------------+
+// Feature Unit Control API
+//--------------------------------------------------------------------+
+
+// Convert the raw control value to host order and chain to the application callback
+static void audioh_fu_get_complete(tuh_xfer_t *xfer) {
+ const uint8_t idx = (uint8_t)xfer->user_data;
+ audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
+ tuh_xfer_cb_t app_cb = epbuf->complete_cb;
+ uintptr_t user_data = epbuf->user_data;
+ uint16_t *value = epbuf->value;
+ const uint8_t width = epbuf->width;
+ epbuf->complete_cb = NULL;
+
+ if (app_cb != NULL && value != NULL && xfer->result == XFER_RESULT_SUCCESS) {
+ const uint8_t *raw = (const uint8_t *)value;
+ // The raw bytes are little-endian on the wire: rebuild the host-order value
+ *value = (width == 1) ? (uint16_t)raw[0] : (uint16_t)((uint16_t)raw[0] | ((uint16_t)raw[1] << 8));
+ }
+
+ xfer->user_data = user_data;
+ if (app_cb != NULL) {
+ app_cb(xfer);
+ }
}
bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t value,
tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false);
- uint8_t daddr = _audioh_itf[idx].daddr;
- uint8_t itf_num = _audioh_itf[idx].ac_itf_num;
- uint8_t unit_id = _audioh_itf[idx].feature_unit_id;
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ TU_VERIFY(p_audio->mounted && p_audio->feature_unit_id != 0, false);
+
+ const uint8_t width = audioh_fu_control_width(control_selector);
const tusb_control_request_t request = {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE,
.type = TUSB_REQ_TYPE_CLASS,
.direction = TUSB_DIR_OUT},
.bRequest = AUDIO10_CS_REQ_SET_CUR,
.wValue = tu_htole16(tu_u16(control_selector, channel)),
- .wIndex = tu_htole16(tu_u16(unit_id, itf_num)),
- .wLength = 2};
+ .wIndex = tu_htole16(tu_u16(p_audio->feature_unit_id, p_audio->ac_itf_num)),
+ .wLength = width};
uint8_t *val_buf = _audioh_epbuf[idx].ctrl;
val_buf[0] = (uint8_t)(value & 0xFF);
val_buf[1] = (uint8_t)((value >> 8) & 0xFF);
- tuh_xfer_t xfer = {.daddr = daddr,
+ tuh_xfer_t xfer = {.daddr = p_audio->daddr,
.ep_addr = 0,
.setup = &request,
.buffer = val_buf,
@@ -634,103 +1263,62 @@ bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t control_selector, uint8_t c
return tuh_control_xfer(&xfer);
}
-bool tuh_audio_feature_unit_get(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t *buffer,
+bool tuh_audio_feature_unit_get(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t *value,
tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false);
- uint8_t daddr = _audioh_itf[idx].daddr;
- uint8_t itf_num = _audioh_itf[idx].ac_itf_num;
- uint8_t unit_id = _audioh_itf[idx].feature_unit_id;
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ TU_VERIFY(p_audio->mounted && p_audio->feature_unit_id != 0 && value, false);
+
+ const uint8_t width = audioh_fu_control_width(control_selector);
const tusb_control_request_t request = {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE,
.type = TUSB_REQ_TYPE_CLASS,
.direction = TUSB_DIR_IN},
.bRequest = AUDIO10_CS_REQ_GET_CUR,
.wValue = tu_htole16(tu_u16(control_selector, channel)),
- .wIndex = tu_htole16(tu_u16(unit_id, itf_num)),
- .wLength = 2};
-
- tuh_xfer_t xfer = {.daddr = daddr,
- .ep_addr = 0,
- .setup = &request,
- .buffer = (uint8_t *)buffer,
- .complete_cb = complete_cb,
- .user_data = user_data};
-
- return tuh_control_xfer(&xfer);
-}
-
-//--------------------------------------------------------------------+
-// Multi-AS interface API
-//--------------------------------------------------------------------+
-uint8_t tuh_audio_as_get_count(uint8_t idx) {
- TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, 0);
- return _audioh_itf[idx].as_count;
-}
-
-bool tuh_audio_as_get_info(uint8_t idx, uint8_t as_idx, tuh_audio_as_info_t *info) {
- TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false);
- TU_VERIFY(as_idx < _audioh_itf[idx].as_count, false);
- TU_VERIFY(info, false);
-
- tuh_audio_as_info_t *as = &_audioh_itf[idx].as[as_idx];
- info->interface_num = as->interface_num;
- info->alt_setting = as->alt_setting;
- info->ep_addr = as->ep_addr;
- info->ep_size = as->ep_size;
- info->ep_dir = as->ep_dir;
- info->format_type = as->format_type;
- info->num_channels = as->num_channels;
- info->sub_frame_size = as->sub_frame_size;
- info->bit_resolution = as->bit_resolution;
- info->sam_freq_type = as->sam_freq_type;
- info->sam_freq_lower = as->sam_freq_lower;
- info->sam_freq_upper = as->sam_freq_upper;
- memcpy(info->sam_freq, as->sam_freq, sizeof(info->sam_freq));
- return true;
-}
+ .wIndex = tu_htole16(tu_u16(p_audio->feature_unit_id, p_audio->ac_itf_num)),
+ .wLength = width};
-//--------------------------------------------------------------------+
-// Isochronous Endpoint API
-//--------------------------------------------------------------------+
-bool tuh_audio_receive(uint8_t idx, uint8_t as_idx, uint8_t *buffer, uint16_t len) {
- TU_VERIFY(idx < CFG_TUH_AUDIO_MAX);
- audioh_interface_t *p_audio = &_audioh_itf[idx];
- tuh_audio_as_info_t *as = &p_audio->as[as_idx];
- TU_VERIFY(as->ep_addr != 0);
-
- return usbh_edpt_xfer(p_audio->daddr, as->ep_addr, buffer, len);
-}
-
-bool tuh_audio_send(uint8_t idx, uint8_t as_idx, uint8_t *buffer, uint16_t len) {
- TU_VERIFY(idx < CFG_TUH_AUDIO_MAX);
- audioh_interface_t *p_audio = &_audioh_itf[idx];
- tuh_audio_as_info_t *as = &p_audio->as[as_idx];
- TU_VERIFY(as->ep_addr != 0);
+ if (complete_cb == NULL) {
+ // Sync (blocking) path: user_data points to a tusb_xfer_result_t, the raw
+ // bytes are converted to host order after the transfer completes
+ tuh_xfer_t xfer = {.daddr = p_audio->daddr,
+ .ep_addr = 0,
+ .setup = &request,
+ .buffer = (uint8_t *)value,
+ .complete_cb = NULL,
+ .user_data = user_data};
+ if (!tuh_control_xfer(&xfer)) {
+ return false;
+ }
+ if (xfer.result == XFER_RESULT_SUCCESS) {
+ const uint8_t *raw = (const uint8_t *)value;
+ *value = (width == 1) ? (uint16_t)raw[0] : (uint16_t)((uint16_t)raw[0] | ((uint16_t)raw[1] << 8));
+ }
+ return true;
+ }
- return usbh_edpt_xfer(p_audio->daddr, as->ep_addr, (uint8_t *)buffer, len);
-}
+ // Async path: chain the host-order conversion to the application callback
+ audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
+ TU_VERIFY(epbuf->complete_cb == NULL, false); // one feature-unit GET in flight per device
-//--------------------------------------------------------------------+
-// Set Interface
-//--------------------------------------------------------------------+
-bool tuh_audio_set_interface(uint8_t daddr, uint8_t itf_num, uint8_t alt_setting, tuh_xfer_cb_t complete_cb,
- uintptr_t user_data) {
- const tusb_control_request_t request = {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE,
- .type = TUSB_REQ_TYPE_STANDARD,
- .direction = TUSB_DIR_OUT},
- .bRequest = TUSB_REQ_SET_INTERFACE,
- .wValue = alt_setting,
- .wIndex = itf_num,
- .wLength = 0};
+ epbuf->complete_cb = complete_cb;
+ epbuf->user_data = user_data;
+ epbuf->value = value;
+ epbuf->width = width;
- tuh_xfer_t xfer = {.daddr = daddr,
+ tuh_xfer_t xfer = {.daddr = p_audio->daddr,
.ep_addr = 0,
.setup = &request,
- .buffer = NULL,
- .complete_cb = complete_cb,
- .user_data = user_data};
+ .buffer = (uint8_t *)value, // raw bytes, converted in audioh_fu_get_complete()
+ .complete_cb = audioh_fu_get_complete,
+ .user_data = (uintptr_t)idx};
- return tuh_control_xfer(&xfer);
+ if (!tuh_control_xfer(&xfer)) {
+ epbuf->complete_cb = NULL;
+ return false;
+ }
+ return true;
}
#endif
diff --git a/src/class/audio/audio_host.h b/src/class/audio/audio_host.h
index aafd58f43..aaa65b671 100644
--- a/src/class/audio/audio_host.h
+++ b/src/class/audio/audio_host.h
@@ -17,11 +17,11 @@ extern "C" {
//--------------------------------------------------------------------+
// Class Driver Configuration
//--------------------------------------------------------------------+
-// Maximum number of Audio interfaces per Audio device
+// Maximum number of Audio devices
#ifndef CFG_TUH_AUDIO_MAX
#define CFG_TUH_AUDIO_MAX 1
#endif
-// Maximum number of Audio Streaming interfaces per Audio device
+// Maximum number of discrete sampling frequencies per Audio Streaming interface
#ifndef CFG_TUH_AUDIO_MAX_SAM_FREQ
#define CFG_TUH_AUDIO_MAX_SAM_FREQ 5
#endif
@@ -30,84 +30,177 @@ extern "C" {
#define CFG_TUH_AUDIO_MAX_AS 4
#endif
+// Maximum size of one capture (IN) isochronous transfer the driver submits.
+// Configurations needing a larger per-poll-interval packet are rejected.
+// 256 covers 2-ch 48 kHz S16_LE (192 B) and common endpoint padding (208 B).
+#ifndef CFG_TUH_AUDIO_EPIN_BUFSIZE
+ #define CFG_TUH_AUDIO_EPIN_BUFSIZE 256
+#endif
+
+// Maximum size of one playback (OUT) isochronous transfer the driver submits.
+// Configurations needing a larger per-poll-interval packet are rejected.
+#ifndef CFG_TUH_AUDIO_EPOUT_BUFSIZE
+ #define CFG_TUH_AUDIO_EPOUT_BUFSIZE 256
+#endif
+
+// Depth in bytes of the per-stream data FIFO. The FIFO decouples the
+// application's read/write calls from the 1 ms isochronous transfer cadence
+// and absorbs rate differences. 1024 bytes hold 4 default (256 B) packets.
+#ifndef CFG_TUH_AUDIO_STREAM_BUFSIZE
+ #define CFG_TUH_AUDIO_STREAM_BUFSIZE 1024
+#endif
+
//--------------------------------------------------------------------+
-// AS Interface Info (per-interface independent storage)
+// Types
//--------------------------------------------------------------------+
+
+// Fixed transfer direction of a logical stream.
+typedef enum {
+ TUH_AUDIO_STREAM_PLAYBACK = 0, // Host -> Device (OUT)
+ TUH_AUDIO_STREAM_CAPTURE = 1, // Device -> Host (IN)
+ TUH_AUDIO_STREAM_DIRECTION_COUNT
+} tuh_audio_direction_t;
+
+// Discrete sample format. Only discrete configurations are supported
+// initially; continuous sample-rate ranges are ignored by the driver.
+typedef enum {
+ TUH_AUDIO_FORMAT_S8 = 0, // signed 8-bit
+ TUH_AUDIO_FORMAT_S16_LE, // signed 16-bit little-endian
+ TUH_AUDIO_FORMAT_S24_3LE, // signed 24-bit packed in 3 bytes, LE
+ TUH_AUDIO_FORMAT_S24_LE, // signed 24-bit in 32-bit container, LE
+ TUH_AUDIO_FORMAT_S32_LE, // signed 32-bit little-endian
+ TUH_AUDIO_FORMAT_COUNT
+} tuh_audio_format_t;
+
+// One complete supported discrete configuration tuple.
+// Each entry is a full (format, sample_rate, channels) combination,
+// avoiding invalid mixes between independent format/rate/channel lists.
+// dir is constant for all configs of a given (dev_idx, stream_idx) and
+// equals the result of tuh_audio_stream_direction().
typedef struct {
- uint8_t interface_num; // AS interface number
- uint8_t alt_setting; // Current alt setting
- uint8_t ep_addr; // Endpoint address
- uint16_t ep_size; // Max packet size
- uint8_t ep_dir; // TUSB_DIR_IN or TUSB_DIR_OUT
+ tuh_audio_direction_t dir;
+ tuh_audio_format_t format;
+ uint32_t sample_rate;
+ uint8_t channels;
+} tuh_audio_stream_config_t;
- // Format info
- uint8_t format_type;
- uint8_t num_channels;
- uint8_t sub_frame_size;
- uint8_t bit_resolution;
- uint8_t sam_freq_type;
- uint32_t sam_freq[CFG_TUH_AUDIO_MAX_SAM_FREQ];
- uint32_t sam_freq_lower;
- uint32_t sam_freq_upper;
-} tuh_audio_as_info_t;
+// Asynchronous completion callback of tuh_audio_configure().
+typedef void (*tuh_audio_configure_cb_t)(uint8_t dev_idx, uint8_t stream_idx, tusb_xfer_result_t result,
+ uintptr_t user_data);
-#ifndef CFG_TUH_AUDIO_EPIN_BUFSIZE
- #define CFG_TUH_AUDIO_EPIN_BUFSIZE 192
-#endif
+//--------------------------------------------------------------------+
+// Stream Enumeration
+//--------------------------------------------------------------------+
-#ifndef CFG_TUH_AUDIO_EPOUT_BUFSIZE
- #define CFG_TUH_AUDIO_EPOUT_BUFSIZE 192
-#endif
+// Number of logical audio streams exposed by one mounted device. The
+// application iterates stream indices [0, tuh_audio_stream_count()) and
+// inspects each with tuh_audio_stream_exists()/tuh_audio_stream_direction().
+uint8_t tuh_audio_stream_count(uint8_t dev_idx);
+
+// True if (dev_idx, stream_idx) identifies an existing stream.
+bool tuh_audio_stream_exists(uint8_t dev_idx, uint8_t stream_idx);
+
+// Fixed transfer direction of the stream.
+tuh_audio_direction_t tuh_audio_stream_direction(uint8_t dev_idx, uint8_t stream_idx);
//--------------------------------------------------------------------+
-// Application API
+// Configuration Enumeration
//--------------------------------------------------------------------+
-// Check if Audio interface is mounted
-bool tuh_audio_mounted(uint8_t idx);
-// Get device address of Audio interface
-uint8_t tuh_audio_get_dev_addr(uint8_t idx);
-// Get Feature Unit ID
-uint8_t tuh_audio_get_feature_unit_id(uint8_t idx);
-// Get Interface index from device address + interface number
-// return TUSB_INDEX_INVALID_8 (0xFF) if not found
-uint8_t tuh_audio_itf_get_index(uint8_t daddr, uint8_t itf_num);
+// Number of supported discrete configurations of the stream.
+uint8_t tuh_audio_config_count(uint8_t dev_idx, uint8_t stream_idx);
-// Get Interface information
-// return true if index is correct and interface is currently mounted
-bool tuh_audio_itf_get_info(uint8_t idx, tuh_itf_info_t *info);
+// Active configuration index of the stream, or TUSB_INDEX_INVALID_8 if none.
+uint8_t tuh_audio_active_config(uint8_t dev_idx, uint8_t stream_idx);
-// Get number of AS interfaces for an audio device
-uint8_t tuh_audio_as_get_count(uint8_t idx);
+// Retrieve one discrete configuration tuple into *config.
+bool tuh_audio_config_get(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx, tuh_audio_stream_config_t *config);
-// Get AS interface info by index
-// as_idx: 0 to (as_count - 1)
-bool tuh_audio_as_get_info(uint8_t idx, uint8_t as_idx, tuh_audio_as_info_t *info);
+//--------------------------------------------------------------------+
+// Configuration (ALSA hw_params analogue, asynchronous)
+//--------------------------------------------------------------------+
-// Set Audio Streaming interface alternate setting (to enable/disable endpoints)
-bool tuh_audio_set_interface(uint8_t daddr, uint8_t itf_num, uint8_t alt_setting, tuh_xfer_cb_t complete_cb,
- uintptr_t user_data);
+// Configure the stream with the discrete configuration identified by
+// config_idx. The driver asynchronously:
+// 1. resolves the AS interface and alternate setting,
+// 2. issues SET_INTERFACE (checking submission and transfer result),
+// 3. opens / reconfigures only the selected endpoint,
+// 4. sets the endpoint sampling frequency when supported,
+// 5. initializes the FIFO and packet scheduler.
+// complete_cb is invoked with the final XFER_RESULT_* status.
+bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx, tuh_audio_configure_cb_t complete_cb,
+ uintptr_t user_data);
//--------------------------------------------------------------------+
-// Control Endpoint API
+// Stream Control / Frame-based Data
//--------------------------------------------------------------------+
-// Set current sampling frequency on an isochronous endpoint (UAC 1.0)
-// Sampling frequency is 3 bytes little-endian
-// In multi-AS scenarios, pass the endpoint address from tuh_audio_as_get_info().
-bool tuh_audio_set_sampling_freq(uint8_t idx, uint8_t as_idx, uint32_t sampling_freq, tuh_xfer_cb_t complete_cb,
- uintptr_t user_data);
+// Start/stop transferring data on a configured stream.
+bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx);
+bool tuh_audio_stop(uint8_t dev_idx, uint8_t stream_idx);
+
+// Frame-based transfer. One frame = channels * bytes per sample.
+// tuh_audio_write() is valid only for TUH_AUDIO_STREAM_PLAYBACK streams,
+// tuh_audio_read() only for TUH_AUDIO_STREAM_CAPTURE streams.
+// Returns the number of frames actually written/read (0 on any error,
+// including wrong direction, unconfigured/stopped stream, or full/empty FIFO).
+uint32_t tuh_audio_write(uint8_t dev_idx, uint8_t stream_idx, const void *buffer, uint32_t frame_count);
+uint32_t tuh_audio_read(uint8_t dev_idx, uint8_t stream_idx, void *buffer, uint32_t frame_count);
-// Get current sampling frequency from an isochronous endpoint (UAC 1.0)
-// In multi-AS scenarios, pass the endpoint address from tuh_audio_as_get_info().
-bool tuh_audio_get_sampling_freq(uint8_t idx, uint8_t as_idx, uint32_t *sampling_freq, tuh_xfer_cb_t complete_cb,
- uintptr_t user_data);
+// FIFO occupancy in frames available for a non-blocking write/read.
+uint32_t tuh_audio_write_available(uint8_t dev_idx, uint8_t stream_idx);
+uint32_t tuh_audio_read_available(uint8_t dev_idx, uint8_t stream_idx);
-// Set current/mute/volume etc. for a feature unit (UAC 1.0)
+//--------------------------------------------------------------------+
+// Helpers
+//--------------------------------------------------------------------+
+
+// Container size in bytes of one sample for a given format.
+static inline uint8_t tuh_audio_format_bytes(tuh_audio_format_t format) {
+ switch (format) {
+ case TUH_AUDIO_FORMAT_S8:
+ return 1;
+ case TUH_AUDIO_FORMAT_S16_LE:
+ return 2;
+ case TUH_AUDIO_FORMAT_S24_3LE:
+ return 3;
+ case TUH_AUDIO_FORMAT_S24_LE:
+ case TUH_AUDIO_FORMAT_S32_LE:
+ return 4;
+ default:
+ return 0;
+ }
+}
+
+// Size in bytes of one frame (all channels) for a configuration.
+static inline uint32_t tuh_audio_config_frame_size(const tuh_audio_stream_config_t *config) {
+ TU_ASSERT(config != NULL);
+ return (uint32_t)tuh_audio_format_bytes(config->format) * config->channels;
+}
+
+//--------------------------------------------------------------------+
+// Device Info
+//--------------------------------------------------------------------+
+
+// Check if Audio device is mounted
+bool tuh_audio_mounted(uint8_t idx);
+// Get device address of Audio device
+uint8_t tuh_audio_get_dev_addr(uint8_t idx);
+// Get Feature Unit ID
+uint8_t tuh_audio_get_feature_unit_id(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Control Request API
+//--------------------------------------------------------------------+
+
+// Set a Feature Unit control (mute, volume, ...) of the Audio device (UAC 1.0)
+// The request length follows the control selector: mute/AGC/loudness are 1 byte, the rest are 2 bytes
bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t value,
tuh_xfer_cb_t complete_cb, uintptr_t user_data);
-// Get current/mute/volume etc. from a feature unit (UAC 1.0)
+// Get a Feature Unit control (mute, volume, ...) of the Audio device (UAC 1.0)
+// The value is converted to host byte order before complete_cb is invoked.
+// Only one feature unit GET may be in flight per device.
bool tuh_audio_feature_unit_get(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t *value,
tuh_xfer_cb_t complete_cb, uintptr_t user_data);
@@ -116,16 +209,6 @@ bool tuh_audio_feature_unit_get(uint8_t idx, uint8_t control_selector, uint8_t c
// Each Function will make a USB control transfer request to/from device the function will block until request is
// complete. The function will return the transfer request result
//--------------------------------------------------------------------+
-TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_audio_get_sampling_freq_sync(uint8_t idx, uint8_t as_idx,
- uint32_t *sampling_freq) {
- TU_API_SYNC(tuh_audio_get_sampling_freq, idx, as_idx, sampling_freq);
-}
-
-TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_audio_set_sampling_freq_sync(uint8_t idx, uint8_t as_idx,
- uint32_t sampling_freq) {
- TU_API_SYNC(tuh_audio_set_sampling_freq, idx, as_idx, sampling_freq);
-}
-
TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t
tuh_audio_feature_unit_set_sync(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t value) {
TU_API_SYNC(tuh_audio_feature_unit_set, idx, control_selector, channel, value);
@@ -137,20 +220,6 @@ tuh_audio_feature_unit_get_sync(uint8_t idx, uint8_t control_selector, uint8_t c
}
//--------------------------------------------------------------------+
-// Interrupt/Isochronous Endpoint API
-//--------------------------------------------------------------------+
-
-// Submit an isochronous transfer to receive audio data from a default IN endpoint.
-// In multi-AS scenarios, endpoint selection is implementation-defined default behavior.
-// Use tuh_audio_as_get_info() when application needs explicit per-AS endpoint control.
-bool tuh_audio_receive(uint8_t idx, uint8_t as_idx, uint8_t *buffer, uint16_t len);
-
-// Submit an isochronous transfer to send audio data to a default OUT endpoint.
-// In multi-AS scenarios, endpoint selection is implementation-defined default behavior.
-// Use tuh_audio_as_get_info() when application needs explicit per-AS endpoint control.
-bool tuh_audio_send(uint8_t idx, uint8_t as_idx, uint8_t *buffer, uint16_t len);
-
-//--------------------------------------------------------------------+
// Callbacks (Weak is optional)
//--------------------------------------------------------------------+
@@ -160,11 +229,17 @@ void tuh_audio_mount_cb(uint8_t idx);
// Invoked when device with Audio interface is un-mounted
void tuh_audio_umount_cb(uint8_t idx);
-// 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);
+// Invoked when an isochronous IN transfer completes successfully: the
+// received data is already queued into the stream's capture FIFO.
+void tuh_audio_capture_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes);
+
+// Invoked when an isochronous OUT transfer completes successfully: the
+// next queued packet is submitted from the stream's playback FIFO.
+void tuh_audio_playback_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes);
-// 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);
+// Invoked when an isochronous transfer fails. The stream is stopped
+// (tuh_audio_start() must be called again to resume).
+void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes);
//--------------------------------------------------------------------+
// Internal Class Driver API