summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/host/audio_host/README.md18
-rw-r--r--examples/host/audio_host/src/audio_app.c2
-rw-r--r--examples/host/audio_host/src/main.c2
-rw-r--r--examples/host/audio_host/src/tusb_config.h3
-rw-r--r--src/class/audio/audio_host.c1161
-rw-r--r--src/class/audio/audio_host.h36
-rw-r--r--test/unit-test/CMakeLists.txt1
-rw-r--r--test/unit-test/project.yml1
-rw-r--r--test/unit-test/test/host/audio/test_audio_host.c304
9 files changed, 1239 insertions, 289 deletions
diff --git a/examples/host/audio_host/README.md b/examples/host/audio_host/README.md
index 06cc4f197..2b5df7785 100644
--- a/examples/host/audio_host/README.md
+++ b/examples/host/audio_host/README.md
@@ -1,10 +1,10 @@
# USB Audio Host Example
-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.
+This example demonstrates how to use TinyUSB's USB Audio Host driver (TUH_AUDIO) to capture audio from a UAC 1.0 or UAC 2.0 USB microphone and echo it back to the speaker, using a WASAPI/ALSA-like high-level API. The application never touches USB interfaces, alternate settings, or endpoint addresses — it only selects supported `{format, sample_rate, channels}` configurations by stream index.
## Features
-- Enumerates and mounts USB Audio Class 1.0 devices
+- Enumerates and mounts USB Audio Class 1.0 and 2.0 devices
- Discovers the device's logical streams (capture/playback) and their supported configurations (discrete tuples only)
- Reports each stream's master mute/volume capabilities and cached volume range
- Configures and starts an S16_LE capture stream (48 kHz preferred, 44.1 kHz fallback; stereo preferred, mono accepted)
@@ -14,7 +14,7 @@ This example demonstrates how to use TinyUSB's USB Audio Host driver (TUH_AUDIO)
## Supported Devices
-This example supports UAC 1.0 devices whose Type I Format descriptor lists discrete sampling frequencies (`bSamFreqType > 0`), such as:
+This example supports UAC1 devices whose Type I Format descriptor lists discrete sampling frequencies (`bSamFreqType > 0`) and UAC2 devices using a directly connected Clock Source, such as:
- USB microphones
- USB headsets (mono microphone + speaker)
@@ -26,8 +26,10 @@ The echo needs a matching S16_LE playback stream at the capture sample rate; dev
- Explicit feedback endpoints are supported with both 10.14 and 16.16 feedback values. An implicit-feedback IN endpoint is treated as an ordinary audio-data endpoint and is not used to pace playback.
- UAC1 Type I Format descriptors with `bSamFreqType == 0` are unsupported; the driver requires a list of discrete sampling frequencies.
-- Master mute and volume controls are discovered before the mount callback, including the volume MIN/MAX/RES range. Feature Units without master mute or volume are ignored. The typed API controls the master channel; the lower-level Feature Unit API remains available for fixed-width UAC1 controls on the associated unit.
-- The UAC1 `MaxPacketsOnly` endpoint attribute is not supported. OUT transfers are not padded to `wMaxPacketSize`, and padding in IN transfers is not removed from the reported audio data.
+- UAC2 supports direct Clock Sources. Clock Selectors, Clock Multipliers, Sampling Rate Converters, Clock Validity, and Valid Alternate Settings controls are not handled.
+- UAC2 sampling-frequency RANGE responses are expanded into at most `CFG_TUH_AUDIO_MAX_SAM_FREQ` discrete configurations. A read-only Clock Source exposes only its current frequency.
+- Master mute and volume controls are discovered before the mount callback, including the volume range. Feature Units without master mute or volume are ignored. UAC2 volume discovery supports the common RANGE response containing one subrange.
+- The `MaxPacketsOnly` endpoint attribute is not supported. OUT transfers are not padded to `wMaxPacketSize`, and padding in IN transfers is not removed from the reported audio data.
## Building
@@ -63,7 +65,7 @@ make BOARD=<your_board> flash
## Usage
1. Build and flash the example to your board
-2. Connect a USB Audio device (UAC 1.0) to the USB host port
+2. Connect a USB Audio device (UAC 1.0 or 2.0) to the USB host port
3. Open a serial terminal to view output
4. The example will:
- Print each stream's Feature Unit ID, master mute/volume capabilities, cached volume range, and supported configurations when mounted
@@ -77,7 +79,7 @@ make BOARD=<your_board> flash
```
TinyUSB Host USB Audio Example
-Connect a USB Audio Device (UAC 1.0) to test
+Connect a USB Audio Device (UAC 1.0 or 2.0) to test
Audio device mounted: idx=0 addr=1
capture stream 1 Feature Unit ID: 5, configurations: 2
master mute supported
@@ -105,6 +107,8 @@ Audio device mounted: idx=0 addr=1
Edit `src/tusb_config.h` to modify:
- `CFG_TUH_AUDIO_MAX`: Maximum number of audio devices supported
+- `CFG_TUH_AUDIO_PROTOCOLS`: Bitmask selecting UAC1 and/or UAC2 support; the example enables both
+- `CFG_TUH_AUDIO_MAX_SAM_FREQ`: Maximum number of discrete frequencies retained per alternate setting or UAC2 Clock Source
- `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); capture overwrites the oldest frames when full
diff --git a/examples/host/audio_host/src/audio_app.c b/examples/host/audio_host/src/audio_app.c
index 9a1434284..4623dff5a 100644
--- a/examples/host/audio_host/src/audio_app.c
+++ b/examples/host/audio_host/src/audio_app.c
@@ -30,7 +30,7 @@
// - 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}
+#define SAMPLE_RATES {44100, 48000}
#define FEATURE_UNIT_VOLUME_DB (-6 * 256)
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
diff --git a/examples/host/audio_host/src/main.c b/examples/host/audio_host/src/main.c
index 3a3e18ab7..45c468f23 100644
--- a/examples/host/audio_host/src/main.c
+++ b/examples/host/audio_host/src/main.c
@@ -31,7 +31,7 @@ int main(void) {
board_init();
printf("TinyUSB Host USB Audio Example\r\n");
- printf("Connect a USB Audio Device (UAC 1.0) to test\r\n");
+ printf("Connect a USB Audio Device (UAC 1.0 or 2.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};
diff --git a/examples/host/audio_host/src/tusb_config.h b/examples/host/audio_host/src/tusb_config.h
index 9b7f3c94b..5c2d54993 100644
--- a/examples/host/audio_host/src/tusb_config.h
+++ b/examples/host/audio_host/src/tusb_config.h
@@ -87,6 +87,9 @@ extern "C" {
#define CFG_TUH_MSC 0
#define CFG_TUH_VENDOR 0
#define CFG_TUH_AUDIO 1
+#ifndef CFG_TUH_AUDIO_PROTOCOLS
+ #define CFG_TUH_AUDIO_PROTOCOLS (TUH_AUDIO_PROTOCOL_UAC1 | TUH_AUDIO_PROTOCOL_UAC2)
+#endif
// max device support (excluding hub device): 1 hub typically has 4 ports
#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1)
diff --git a/src/class/audio/audio_host.c b/src/class/audio/audio_host.c
index a28cad156..e676a3354 100644
--- a/src/class/audio/audio_host.c
+++ b/src/class/audio/audio_host.c
@@ -1,15 +1,16 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 Zhenjiang Zhang
+ * SPDX-FileCopyrightText: Copyright (c) 2026 HiFiPhile (Zixun LI)
* SPDX-License-Identifier: MIT
*
* This file is part of the TinyUSB stack.
*/
/*
- * 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.
+ * This driver implements a USB Audio Host (UAC1/UAC2) 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.
*
* Each instance (Audio Control interface) provides at most one logical stream
* per direction:
@@ -34,13 +35,13 @@
* grouped by alternate setting so their format and endpoint properties are
* stored only once. The selected mapping is applied by tuh_audio_configure().
*
- * Non-PCM formats and sampling-frequency ranges are not registered as
- * supported configurations during enumeration.
- *
+ * Non-PCM formats are not registered as supported configurations during
+ * enumeration. UAC1 continuous
+ * sampling-frequency ranges are unsupported.
* The driver owns:
* 1. Endpoint selection and opening; only the alternate setting selected by
* tuh_audio_configure() is activated by tuh_audio_start().
- * 2. Endpoint sampling-frequency control (SET_CUR, 3 bytes little-endian).
+ * 2. Protocol-specific sampling-frequency control.
*/
#include "tusb_option.h"
@@ -99,10 +100,30 @@ enum {
STREAM_STATE_READY // configured, ready to start/stop
};
+enum {
+ AUDIOH_CTRL_NONE = 0,
+ AUDIOH_CTRL_READ = 1,
+ AUDIOH_CTRL_READ_WRITE = 3
+};
+
+#if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ #define AUDIOH_MAX_RATE_SOURCES (2 * CFG_TUH_AUDIO_MAX_AS)
+#else
+ #define AUDIOH_MAX_RATE_SOURCES TUH_AUDIO_STREAM_DIRECTION_COUNT
+#endif
+
+// A UAC1 alternate setting owns one descriptor-provided rate source. UAC2
+// alternate settings attached to the same Clock Source share one rate source.
+typedef struct {
+ uint32_t sample_rate[CFG_TUH_AUDIO_MAX_SAM_FREQ];
+ uint8_t control_id; // UAC1 endpoint address or UAC2 Clock Source ID
+ uint8_t sample_rate_count;
+ uint8_t frequency_access;
+} audioh_rate_source_t;
+
// One Audio Streaming alternate setting. Format, channels, and endpoint
// properties are shared by all of its discrete sampling frequencies.
typedef struct {
- uint32_t sample_rate[CFG_TUH_AUDIO_MAX_SAM_FREQ];
uint16_t ep_size; // endpoint max packet size
uint8_t itf_num;
uint8_t alt_setting;
@@ -111,8 +132,9 @@ typedef struct {
uint8_t ep_attr; // bmAttributes synchronization and usage bits
uint8_t format;
uint8_t channels;
- uint8_t sample_rate_count;
- bool sam_freq_ctrl;
+ uint8_t terminal_id;
+ uint8_t rate_source_idx;
+ uint8_t rate_count;
} audioh_as_config_t;
// Explicit feedback exists only for playback alternate settings.
@@ -163,8 +185,8 @@ typedef struct {
// One Feature Unit associated with this logical stream (0 = none)
uint8_t feature_unit_id;
- bool mute_supported;
- bool volume_supported;
+ uint8_t mute_access;
+ uint8_t volume_access;
tuh_audio_volume_range_t volume_range;
// Size in bytes of one frame (all channels) of the active configuration
@@ -183,19 +205,30 @@ typedef struct {
typedef struct {
uint8_t daddr; // device address (0 = free slot)
uint8_t ac_itf_num; // Audio Control interface number
+ uint8_t protocol; // AUDIO_INT_PROTOCOL_CODE_V1/V2
uint8_t stream_count;
+ uint8_t rate_source_count;
bool mounted;
+ audioh_rate_source_t rate_source[AUDIOH_MAX_RATE_SOURCES];
+
// Logical streams: playback first, then capture (stream index order)
tuh_audio_stream_t out_stream;
tuh_audio_stream_t in_stream;
audioh_playback_t playback;
} audioh_interface_t;
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ #define AUDIOH_CLOCK_RANGE_BUFSIZE (2 + 12 * CFG_TUH_AUDIO_MAX_SAM_FREQ)
+ #endif
+
typedef struct {
// Sampling-frequency SET completes before feedback starts, so both
// transfers can use one stable DMA buffer.
TUH_EPBUF_DEF(rate_feedback, 4);
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ TUH_EPBUF_DEF(clock_range, AUDIOH_CLOCK_RANGE_BUFSIZE);
+ #endif
TUH_EPBUF_DEF(fu_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
@@ -213,6 +246,12 @@ typedef struct {
uint8_t range_step;
} mount;
} fu;
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ struct {
+ uint8_t rate_source_idx;
+ bool read_cur;
+ } clock;
+ #endif
bool fu_busy;
} audioh_epbuf_t;
@@ -242,6 +281,17 @@ static bool audioh_desc_valid(const uint8_t *p_desc, const uint8_t *desc_end, ui
TUH_VALIDATE_BASIC(tu_desc_len(p_desc) <= remaining);
}
+static bool audioh_protocol_enabled(uint8_t protocol) {
+ switch (protocol) {
+ case AUDIO_INT_PROTOCOL_CODE_V1:
+ return (CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1) != 0;
+ case AUDIO_INT_PROTOCOL_CODE_V2:
+ return (CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2) != 0;
+ default:
+ return false;
+ }
+}
+
static tuh_audio_stream_t *audioh_get_stream(audioh_interface_t *p_audio, tusb_dir_t direction) {
switch (direction) {
case TUSB_DIR_IN:
@@ -257,7 +307,7 @@ static tuh_audio_stream_t *audioh_get_stream(audioh_interface_t *p_audio, tusb_d
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) {
+ if (s->as_count > 0 && s->stream_idx == stream_idx) {
return s;
}
}
@@ -272,28 +322,47 @@ TU_ATTR_ALWAYS_INLINE static inline audioh_as_config_t *audioh_stream_active_as(
return &s->as[s->active_as];
}
+TU_ATTR_ALWAYS_INLINE static inline audioh_rate_source_t *audioh_as_rate_source(const tuh_audio_stream_t *s,
+ const audioh_as_config_t *as) {
+ return &_audioh_itf[s->idx].rate_source[as->rate_source_idx];
+}
+
+static bool audioh_as_rate_fits(const audioh_interface_t *p_audio, const tuh_audio_stream_t *stream,
+ const audioh_as_config_t *as, uint32_t sample_rate);
+
static bool audioh_stream_resolve_config(const tuh_audio_stream_t *s, uint8_t config_idx, uint8_t *as_idx,
uint8_t *rate_idx) {
TU_VERIFY(config_idx < s->config_count, false);
for (uint8_t i = 0; i < s->as_count; i++) {
- if (config_idx < s->as[i].sample_rate_count) {
- *as_idx = i;
- *rate_idx = config_idx;
- return true;
+ if (config_idx < s->as[i].rate_count) {
+ const audioh_as_config_t *as = &s->as[i];
+ const audioh_rate_source_t *rate_source = audioh_as_rate_source(s, as);
+ for (uint8_t source_rate_idx = 0; source_rate_idx < rate_source->sample_rate_count; source_rate_idx++) {
+ if (audioh_as_rate_fits(&_audioh_itf[s->idx], s, as, rate_source->sample_rate[source_rate_idx])) {
+ if (config_idx == 0) {
+ *as_idx = i;
+ *rate_idx = source_rate_idx;
+ return true;
+ }
+ config_idx--;
+ }
+ }
+ return false;
}
- config_idx -= s->as[i].sample_rate_count;
+ config_idx -= s->as[i].rate_count;
}
return false;
}
static void audioh_stream_config_fill(const tuh_audio_stream_t *s, uint8_t as_idx, uint8_t rate_idx,
tuh_audio_stream_config_t *config) {
- const audioh_as_config_t *as = &s->as[as_idx];
- config->dir = (s->dir == TUSB_DIR_IN) ? TUH_AUDIO_STREAM_CAPTURE : TUH_AUDIO_STREAM_PLAYBACK;
- config->format = (tuh_audio_format_t)as->format;
- config->sample_rate = as->sample_rate[rate_idx];
- config->channels = as->channels;
+ const audioh_as_config_t *as = &s->as[as_idx];
+ const audioh_rate_source_t *rate_source = audioh_as_rate_source(s, as);
+ config->dir = (s->dir == TUSB_DIR_IN) ? TUH_AUDIO_STREAM_CAPTURE : TUH_AUDIO_STREAM_PLAYBACK;
+ config->format = (tuh_audio_format_t)as->format;
+ config->sample_rate = rate_source->sample_rate[rate_idx];
+ config->channels = as->channels;
}
static bool audioh_stream_config_get(const tuh_audio_stream_t *s, uint8_t config_idx,
@@ -306,23 +375,24 @@ static bool audioh_stream_config_get(const tuh_audio_stream_t *s, uint8_t config
return true;
}
-static void audioh_stream_set_feature_unit(tuh_audio_stream_t *s, uint8_t unit_id, uint8_t controls) {
- s->feature_unit_id = unit_id;
- s->mute_supported = (controls & AUDIO10_FU_CONTROL_BM_MUTE) != 0;
- s->volume_supported = (controls & AUDIO10_FU_CONTROL_BM_VOLUME) != 0;
+static void audioh_stream_set_feature_unit(tuh_audio_stream_t *s, uint8_t unit_id, uint8_t mute_access,
+ uint8_t volume_access) {
+ s->feature_unit_id = unit_id;
+ s->mute_access = mute_access;
+ s->volume_access = volume_access;
}
-// 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) {
+// Map a Type-I PCM (subslot size, bit resolution) pair to a supported format.
+static bool audioh_format_from_pcm(uint8_t subslot_size, uint8_t bit_resolution, tuh_audio_format_t *format) {
+ if (subslot_size == 1 && bit_resolution == 8) {
*format = TUH_AUDIO_FORMAT_S8;
- } else if (subframe_size == 2 && bit_resolution == 16) {
+ } else if (subslot_size == 2 && bit_resolution == 16) {
*format = TUH_AUDIO_FORMAT_S16_LE;
- } else if (subframe_size == 3 && bit_resolution == 24) {
+ } else if (subslot_size == 3 && bit_resolution == 24) {
*format = TUH_AUDIO_FORMAT_S24_3LE;
- } else if (subframe_size == 4 && bit_resolution == 24) {
+ } else if (subslot_size == 4 && bit_resolution == 24) {
*format = TUH_AUDIO_FORMAT_S24_LE;
- } else if (subframe_size == 4 && bit_resolution == 32) {
+ } else if (subslot_size == 4 && bit_resolution == 32) {
*format = TUH_AUDIO_FORMAT_S32_LE;
} else {
return false;
@@ -345,7 +415,7 @@ static uint32_t audioh_nominal_frames_q16(uint32_t sample_rate, uint8_t ep_inter
return (uint32_t)((numerator + 500000u) / 1000000u);
}
-// UAC 1.0 feature-unit control value width (0 = unsupported variable/unknown width)
+// Supported Feature Unit control widths (0 = unsupported variable/unknown width).
static uint8_t audioh_fu_control_width(uint8_t control_selector) {
switch (control_selector) {
case AUDIO10_FU_CTRL_MUTE:
@@ -366,20 +436,20 @@ static uint8_t audioh_fu_control_width(uint8_t control_selector) {
// 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->as_count = 0;
- s->config_count = 0;
- s->active_config = TUSB_INDEX_INVALID_8;
- s->active_as = TUSB_INDEX_INVALID_8;
- s->active_rate = TUSB_INDEX_INVALID_8;
- s->state = STREAM_STATE_IDLE;
- s->running = false;
- s->feature_unit_id = 0;
- s->mute_supported = false;
- s->volume_supported = false;
- s->volume_range = (tuh_audio_volume_range_t){0};
- s->frame_bytes = 0;
+ s->daddr = 0;
+ s->stream_idx = TUSB_INDEX_INVALID_8;
+ s->as_count = 0;
+ s->config_count = 0;
+ s->active_config = TUSB_INDEX_INVALID_8;
+ s->active_as = TUSB_INDEX_INVALID_8;
+ s->active_rate = TUSB_INDEX_INVALID_8;
+ s->state = STREAM_STATE_IDLE;
+ s->running = false;
+ s->feature_unit_id = 0;
+ s->mute_access = AUDIOH_CTRL_NONE;
+ s->volume_access = AUDIOH_CTRL_NONE;
+ s->volume_range = (tuh_audio_volume_range_t){0};
+ s->frame_bytes = 0;
tu_edpt_stream_close(&s->edpt);
tu_edpt_stream_clear(&s->edpt);
}
@@ -535,22 +605,44 @@ static void audioh_stream_error(tuh_audio_stream_t *s, uint16_t xferred_bytes) {
tuh_audio_err_cb(s->idx, s->stream_idx, xferred_bytes);
}
-// Set the endpoint sampling frequency (3 bytes little-endian)
+// Submit the protocol-specific sampling-frequency control request.
static bool audioh_stream_set_freq(tuh_audio_stream_t *s, tuh_xfer_cb_t complete_cb) {
- const audioh_as_config_t *as = audioh_stream_active_as(s);
- const uint32_t sample_rate = as->sample_rate[s->active_rate];
- uint8_t *ctrl = _audioh_epbuf[s->idx].rate_feedback;
+ const audioh_as_config_t *as = audioh_stream_active_as(s);
+ const audioh_rate_source_t *rate_source = audioh_as_rate_source(s, as);
+ const uint32_t sample_rate = rate_source->sample_rate[s->active_rate];
+ uint8_t *ctrl = _audioh_epbuf[s->idx].rate_feedback;
+ tusb_control_request_t request = {0};
ctrl[0] = (uint8_t)(sample_rate & 0xFF);
ctrl[1] = (uint8_t)((sample_rate >> 8) & 0xFF);
ctrl[2] = (uint8_t)((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(as->ep_addr),
- .wLength = 3};
+ switch (_audioh_itf[s->idx].protocol) {
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ case AUDIO_INT_PROTOCOL_CODE_V1:
+ request.bmRequestType_bit.recipient = TUSB_REQ_RCPT_ENDPOINT;
+ request.bmRequestType_bit.type = TUSB_REQ_TYPE_CLASS;
+ request.bmRequestType_bit.direction = TUSB_DIR_OUT;
+ request.bRequest = AUDIO10_CS_REQ_SET_CUR;
+ request.wValue = tu_htole16(tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0));
+ request.wIndex = tu_htole16(rate_source->control_id);
+ request.wLength = 3;
+ break;
+ #endif
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ case AUDIO_INT_PROTOCOL_CODE_V2:
+ request.bmRequestType_bit.recipient = TUSB_REQ_RCPT_INTERFACE;
+ request.bmRequestType_bit.type = TUSB_REQ_TYPE_CLASS;
+ request.bmRequestType_bit.direction = TUSB_DIR_OUT;
+ request.bRequest = AUDIO20_CS_REQ_CUR;
+ request.wValue = tu_htole16(tu_u16(AUDIO20_CS_CTRL_SAM_FREQ, 0));
+ request.wIndex = tu_htole16(tu_u16(rate_source->control_id, _audioh_itf[s->idx].ac_itf_num));
+ request.wLength = 4;
+ ctrl[3] = (uint8_t)(sample_rate >> 24);
+ break;
+ #endif
+ default:
+ return false;
+ }
tuh_xfer_t xfer = {.daddr = s->daddr,
.ep_addr = 0,
@@ -667,9 +759,11 @@ void audioh_close(uint8_t daddr) {
_audioh_epbuf[idx].complete_cb = NULL; // drop a pending feature-unit request
_audioh_epbuf[idx].fu_busy = false;
- p_audio->stream_count = 0;
- p_audio->daddr = 0;
- p_audio->mounted = false;
+ p_audio->stream_count = 0;
+ p_audio->daddr = 0;
+ p_audio->protocol = 0;
+ p_audio->rate_source_count = 0;
+ p_audio->mounted = false;
}
}
@@ -762,11 +856,355 @@ bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uin
// Enumeration
//--------------------------------------------------------------------+
+ #define AUDIOH_MAX_AC_ENTITIES TUH_AUDIO_STREAM_DIRECTION_COUNT
+
+typedef struct {
+ uint8_t id;
+ uint8_t source_id;
+ uint8_t clock_id;
+ uint8_t stream_dir;
+} audioh_terminal_info_t;
+
+typedef struct {
+ uint8_t id;
+ uint8_t source_id;
+ uint8_t mute_access;
+ uint8_t volume_access;
+} audioh_fu_info_t;
+
+typedef struct {
+ uint8_t id;
+ uint8_t frequency_access;
+} audioh_clock_info_t;
+
+typedef struct {
+ audioh_terminal_info_t terminal[AUDIOH_MAX_AC_ENTITIES];
+ audioh_fu_info_t feature_unit[AUDIOH_MAX_AC_ENTITIES];
+ audioh_clock_info_t clock[AUDIOH_MAX_AC_ENTITIES];
+ uint8_t terminal_count;
+ uint8_t feature_unit_count;
+ uint8_t clock_count;
+} audioh_ac_map_t;
+
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+static uint8_t audioh_uac2_control_access(uint32_t controls, uint8_t position) {
+ const uint8_t access = (uint8_t)((controls >> position) & 0x03u);
+ return (access == AUDIOH_CTRL_READ || access == AUDIOH_CTRL_READ_WRITE) ? access : AUDIOH_CTRL_NONE;
+}
+ #endif
+
+static bool audioh_as_rate_fits(const audioh_interface_t *p_audio, const tuh_audio_stream_t *stream,
+ const audioh_as_config_t *as, uint32_t sample_rate) {
+ const uint32_t frame_bytes = (uint32_t)as->channels * tuh_audio_format_bytes((tuh_audio_format_t)as->format);
+ const uint16_t epbuf_size = (stream->dir == TUSB_DIR_IN) ? CFG_TUH_AUDIO_EPIN_BUFSIZE : CFG_TUH_AUDIO_EPOUT_BUFSIZE;
+ const uint64_t frames_numerator = (uint64_t)sample_rate * audioh_interval_us(as->ep_interval, p_audio->daddr);
+ const uint64_t max_frames = (frames_numerator + 999999u) / 1000000u;
+ const uint64_t packet_bytes = max_frames * frame_bytes;
+
+ return packet_bytes > 0 && packet_bytes <= as->ep_size &&
+ (stream->dir != TUSB_DIR_OUT || (packet_bytes <= epbuf_size && packet_bytes <= CFG_TUH_AUDIO_STREAM_BUFSIZE));
+}
+
+static void audioh_ac_terminal_add(audioh_ac_map_t *map, uint8_t id, uint8_t source_id, uint8_t clock_id,
+ tusb_dir_t stream_dir) {
+ if (map->terminal_count < TU_ARRAY_SIZE(map->terminal)) {
+ map->terminal[map->terminal_count++] =
+ (audioh_terminal_info_t){.id = id, .source_id = source_id, .clock_id = clock_id, .stream_dir = stream_dir};
+ }
+}
+
+static void audioh_ac_feature_unit_add(audioh_ac_map_t *map, uint8_t id, uint8_t source_id, uint8_t mute_access,
+ uint8_t volume_access) {
+ if ((mute_access != AUDIOH_CTRL_NONE || volume_access != AUDIOH_CTRL_NONE) &&
+ map->feature_unit_count < TU_ARRAY_SIZE(map->feature_unit)) {
+ map->feature_unit[map->feature_unit_count++] =
+ (audioh_fu_info_t){.id = id, .source_id = source_id, .mute_access = mute_access, .volume_access = volume_access};
+ }
+}
+
+static const audioh_terminal_info_t *audioh_ac_terminal_find(const audioh_ac_map_t *map, uint8_t id) {
+ for (uint8_t i = 0; i < map->terminal_count; i++) {
+ if (map->terminal[i].id == id) {
+ return &map->terminal[i];
+ }
+ }
+ return NULL;
+}
+
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+static const audioh_clock_info_t *audioh_ac_clock_find(const audioh_ac_map_t *map, uint8_t id) {
+ for (uint8_t i = 0; i < map->clock_count; i++) {
+ if (map->clock[i].id == id) {
+ return &map->clock[i];
+ }
+ }
+ return NULL;
+}
+ #endif
+
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+static bool audioh_uac1_parse_ac_entity(audioh_ac_map_t *map, const uint8_t *p_desc) {
+ switch (tu_desc_subtype(p_desc)) {
+ case AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_input_terminal_t)), false);
+ const audio10_desc_input_terminal_t *terminal = (const audio10_desc_input_terminal_t *)p_desc;
+ if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
+ audioh_ac_terminal_add(map, terminal->bTerminalID, 0, 0, TUSB_DIR_OUT);
+ }
+ break;
+ }
+ case AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_output_terminal_t)), false);
+ const audio10_desc_output_terminal_t *terminal = (const audio10_desc_output_terminal_t *)p_desc;
+ if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
+ audioh_ac_terminal_add(map, terminal->bTerminalID, terminal->bSourceID, 0, TUSB_DIR_IN);
+ }
+ break;
+ }
+ case AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 7), false);
+ const uint8_t control_size = p_desc[5];
+ TU_VERIFY(TUH_VALIDATE_BASIC(control_size > 0), false);
+ TU_VERIFY(TUH_VALIDATE_BASIC(control_size <= (uint8_t)(tu_desc_len(p_desc) - 7)), false);
+ const uint8_t controls = p_desc[6];
+ audioh_ac_feature_unit_add(map, p_desc[3], p_desc[4],
+ (controls & AUDIO10_FU_CONTROL_BM_MUTE) ? AUDIOH_CTRL_READ_WRITE : AUDIOH_CTRL_NONE,
+ (controls & AUDIO10_FU_CONTROL_BM_VOLUME) ? AUDIOH_CTRL_READ_WRITE : AUDIOH_CTRL_NONE);
+ break;
+ }
+ default:
+ break;
+ }
+ return true;
+}
+ #endif
+
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+static bool audioh_uac2_parse_ac_entity(audioh_ac_map_t *map, const uint8_t *p_desc) {
+ switch (tu_desc_subtype(p_desc)) {
+ case AUDIO20_CS_AC_INTERFACE_INPUT_TERMINAL: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_input_terminal_t)), false);
+ const audio20_desc_input_terminal_t *terminal = (const audio20_desc_input_terminal_t *)p_desc;
+ if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
+ audioh_ac_terminal_add(map, terminal->bTerminalID, 0, terminal->bCSourceID, TUSB_DIR_OUT);
+ }
+ break;
+ }
+ case AUDIO20_CS_AC_INTERFACE_OUTPUT_TERMINAL: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_output_terminal_t)), false);
+ const audio20_desc_output_terminal_t *terminal = (const audio20_desc_output_terminal_t *)p_desc;
+ if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
+ audioh_ac_terminal_add(map, terminal->bTerminalID, terminal->bSourceID, terminal->bCSourceID, TUSB_DIR_IN);
+ }
+ break;
+ }
+ case AUDIO20_CS_AC_INTERFACE_FEATURE_UNIT: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 10), false);
+ const uint32_t controls = tu_le32toh(tu_unaligned_read32(&p_desc[5]));
+ audioh_ac_feature_unit_add(map, p_desc[3], p_desc[4],
+ audioh_uac2_control_access(controls, AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS),
+ audioh_uac2_control_access(controls, AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS));
+ break;
+ }
+ case AUDIO20_CS_AC_INTERFACE_CLOCK_SOURCE: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_clock_source_t)), false);
+ const audio20_desc_clock_source_t *clock = (const audio20_desc_clock_source_t *)p_desc;
+ if (map->clock_count < TU_ARRAY_SIZE(map->clock)) {
+ map->clock[map->clock_count++] =
+ (audioh_clock_info_t){.id = clock->bClockID,
+ .frequency_access =
+ audioh_uac2_control_access(clock->bmControls, AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS)};
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return true;
+}
+ #endif
+
+static bool audioh_parse_ac_entity(audioh_interface_t *p_audio, audioh_ac_map_t *map, const uint8_t *p_desc) {
+ switch (p_audio->protocol) {
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ case AUDIO_INT_PROTOCOL_CODE_V1:
+ return audioh_uac1_parse_ac_entity(map, p_desc);
+ #endif
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ case AUDIO_INT_PROTOCOL_CODE_V2:
+ return audioh_uac2_parse_ac_entity(map, p_desc);
+ #endif
+ default:
+ return false;
+ }
+}
+
+static void audioh_link_feature_units(audioh_interface_t *p_audio, const audioh_ac_map_t *map) {
+ for (uint8_t direction = TUSB_DIR_OUT; direction <= TUSB_DIR_IN; direction++) {
+ tuh_audio_stream_t *stream = audioh_get_stream(p_audio, (tusb_dir_t)direction);
+ if (stream == NULL || stream->as_count == 0) {
+ continue;
+ }
+ const audioh_terminal_info_t *terminal = audioh_ac_terminal_find(map, stream->as[0].terminal_id);
+ if (terminal == NULL || terminal->stream_dir != direction) {
+ continue;
+ }
+ for (uint8_t i = 0; i < map->feature_unit_count; i++) {
+ const audioh_fu_info_t *fu = &map->feature_unit[i];
+ const bool linked =
+ (direction == TUSB_DIR_OUT) ? (fu->source_id == terminal->id) : (fu->id == terminal->source_id);
+ if (linked) {
+ audioh_stream_set_feature_unit(stream, fu->id, fu->mute_access, fu->volume_access);
+ break;
+ }
+ }
+ }
+}
+
+typedef struct {
+ uint32_t format_bitmap;
+ uint16_t format_tag;
+ const uint8_t *sample_rate_data;
+ uint8_t terminal_id;
+ uint8_t format_type;
+ uint8_t channels;
+ uint8_t subslot_size;
+ uint8_t bit_resolution;
+ uint8_t sample_rate_count;
+} audioh_as_class_info_t;
+
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+static bool audioh_uac1_parse_as_interface(const uint8_t *p_desc, audioh_as_class_info_t *info) {
+ switch (tu_desc_subtype(p_desc)) {
+ case AUDIO10_CS_AS_INTERFACE_AS_GENERAL: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_cs_as_interface_t)), false);
+ const audio10_desc_cs_as_interface_t *general = (const audio10_desc_cs_as_interface_t *)p_desc;
+ info->terminal_id = general->bTerminalLink;
+ info->format_tag = tu_le16toh(general->wFormatTag);
+ break;
+ }
+ case AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE:
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 8), false);
+ info->format_type = p_desc[3];
+ if (info->format_type != AUDIO10_FORMAT_TYPE_I) {
+ break;
+ }
+ info->channels = p_desc[4];
+ info->subslot_size = p_desc[5];
+ info->bit_resolution = p_desc[6];
+ info->sample_rate_count = 0;
+ info->sample_rate_data = NULL;
+ if (p_desc[7] > 0) {
+ TU_VERIFY(TUH_VALIDATE_BASIC(p_desc[7] <= (tu_desc_len(p_desc) - 8u) / 3u), false);
+ info->sample_rate_count = TU_MIN(p_desc[7], CFG_TUH_AUDIO_MAX_SAM_FREQ);
+ info->sample_rate_data = &p_desc[8];
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+ #endif
+
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+static bool audioh_uac2_parse_as_interface(const uint8_t *p_desc, audioh_as_class_info_t *info) {
+ switch (tu_desc_subtype(p_desc)) {
+ case AUDIO20_CS_AS_INTERFACE_AS_GENERAL: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_cs_as_interface_t)), false);
+ const audio20_desc_cs_as_interface_t *general = (const audio20_desc_cs_as_interface_t *)p_desc;
+ info->terminal_id = general->bTerminalLink;
+ info->format_type = general->bFormatType;
+ info->format_bitmap = tu_le32toh(general->bmFormats);
+ info->channels = general->bNrChannels;
+ break;
+ }
+ case AUDIO20_CS_AS_INTERFACE_FORMAT_TYPE: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_type_I_format_t)), false);
+ const audio20_desc_type_I_format_t *format = (const audio20_desc_type_I_format_t *)p_desc;
+ if (format->bFormatType == AUDIO20_FORMAT_TYPE_I) {
+ info->subslot_size = format->bSubslotSize;
+ info->bit_resolution = format->bBitResolution;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return true;
+}
+ #endif
+
+static bool audioh_parse_as_interface(audioh_interface_t *p_audio, const uint8_t *p_desc,
+ audioh_as_class_info_t *info) {
+ switch (p_audio->protocol) {
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ case AUDIO_INT_PROTOCOL_CODE_V1:
+ return audioh_uac1_parse_as_interface(p_desc, info);
+ #endif
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ case AUDIO_INT_PROTOCOL_CODE_V2:
+ return audioh_uac2_parse_as_interface(p_desc, info);
+ #endif
+ default:
+ return false;
+ }
+}
+
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+static int8_t audioh_uac2_rate_source_get(audioh_interface_t *p_audio, const audioh_ac_map_t *map,
+ const audioh_terminal_info_t *terminal) {
+ if (terminal->clock_id == 0) {
+ return -1;
+ }
+ const audioh_clock_info_t *clock = audioh_ac_clock_find(map, terminal->clock_id);
+ if (clock == NULL || clock->frequency_access == AUDIOH_CTRL_NONE) {
+ return -1;
+ }
+ for (uint8_t i = 0; i < p_audio->rate_source_count; i++) {
+ if (p_audio->rate_source[i].control_id == clock->id) {
+ return (int8_t)i;
+ }
+ }
+ if (p_audio->rate_source_count >= AUDIOH_MAX_RATE_SOURCES) {
+ return -1;
+ }
+ const uint8_t idx = p_audio->rate_source_count++;
+ p_audio->rate_source[idx] =
+ (audioh_rate_source_t){.control_id = clock->id, .frequency_access = clock->frequency_access};
+ return (int8_t)idx;
+}
+ #endif
+
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+static bool audioh_uac1_rates_store(const audioh_interface_t *p_audio, const tuh_audio_stream_t *stream,
+ audioh_as_config_t *as, const audioh_as_class_info_t *info,
+ audioh_rate_source_t *rate_source) {
+ for (uint8_t i = 0; i < info->sample_rate_count; i++) {
+ const uint8_t *rate_data = &info->sample_rate_data[i * 3u];
+ const uint32_t sample_rate =
+ (uint32_t)rate_data[0] | ((uint32_t)rate_data[1] << 8) | ((uint32_t)rate_data[2] << 16);
+ if (sample_rate == 0 || !audioh_as_rate_fits(p_audio, stream, as, sample_rate)) {
+ continue;
+ }
+
+ const uint8_t rate_idx = rate_source->sample_rate_count++;
+ rate_source->sample_rate[rate_idx] = sample_rate;
+ as->rate_count++;
+ }
+ return as->rate_count > 0;
+}
+ #endif
+
// 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) {
+static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_ac_map_t *ac_map,
+ const tusb_desc_interface_t *desc_itf, const uint8_t *p_desc,
+ const uint8_t *desc_end) {
+ #if !(CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2)
+ (void)ac_map;
+ #endif
TU_VERIFY(audioh_desc_valid(p_desc, desc_end, sizeof(tusb_desc_interface_t)), NULL);
const uint8_t itf_num = desc_itf->bInterfaceNumber;
@@ -787,12 +1225,7 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de
}
// 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_count = 0;
- const uint8_t *sam_freq_data = NULL;
+ audioh_as_class_info_t class_info = {0};
// An AS alternate setting has one audio data endpoint and may have one
// explicit feedback endpoint. Implicit-feedback endpoints are data endpoints
@@ -818,38 +1251,13 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de
switch (tu_desc_type(p_desc)) {
case TUSB_DESC_CS_INTERFACE: {
TU_VERIFY(audioh_desc_valid(p_desc, desc_end, 3), NULL);
- switch (tu_desc_subtype(p_desc)) {
- case AUDIO10_CS_AS_INTERFACE_AS_GENERAL: {
- TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_cs_as_interface_t)), NULL);
- const audio10_desc_cs_as_interface_t *desc_as_general = (const audio10_desc_cs_as_interface_t *)p_desc;
- format_tag = tu_le16toh(desc_as_general->wFormatTag);
- break;
- }
- case AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE: {
- TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 8), NULL);
- 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_count = 0;
- sam_freq_data = NULL;
- if (p_desc[7] > 0) {
- TU_VERIFY(TUH_VALIDATE_BASIC(p_desc[7] <= (tu_desc_len(p_desc) - 8u) / 3u), NULL);
- sam_freq_count = TU_MIN(p_desc[7], CFG_TUH_AUDIO_MAX_SAM_FREQ);
- sam_freq_data = &p_desc[8];
- }
- break;
- }
- default:
- break;
- }
+ TU_VERIFY(audioh_parse_as_interface(p_audio, p_desc, &class_info), NULL);
break;
}
case TUSB_DESC_CS_ENDPOINT: {
TU_VERIFY(audioh_desc_valid(p_desc, desc_end, 3), NULL);
- if (tu_desc_subtype(p_desc) == AUDIO10_CS_EP_SUBTYPE_GENERAL) {
+ if (p_audio->protocol == AUDIO_INT_PROTOCOL_CODE_V1 &&
+ tu_desc_subtype(p_desc) == AUDIO10_CS_EP_SUBTYPE_GENERAL) {
TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 4), NULL);
const audio10_desc_cs_as_iso_data_ep_t *desc_ep = (const audio10_desc_cs_as_iso_data_ep_t *)p_desc;
ep_info.sam_freq_ctrl = (desc_ep->bmAttributes & AUDIO10_CS_AS_ISO_DATA_EP_ATT_SAMPLING_FRQ) != 0;
@@ -921,35 +1329,57 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de
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);
+ // Reject unsupported formats explicitly.
+ bool pcm_supported = false;
+ switch (p_audio->protocol) {
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ case AUDIO_INT_PROTOCOL_CODE_V1:
+ pcm_supported =
+ class_info.format_type == AUDIO10_FORMAT_TYPE_I && class_info.format_tag == AUDIO10_DATA_FORMAT_TYPE_I_PCM;
+ break;
+ #endif
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ case AUDIO_INT_PROTOCOL_CODE_V2:
+ pcm_supported = class_info.format_type == AUDIO20_FORMAT_TYPE_I &&
+ (class_info.format_bitmap & AUDIO20_DATA_FORMAT_TYPE_I_PCM) != 0;
+ break;
+ #endif
+ default:
+ break;
+ }
+ if (!pcm_supported) {
+ TU_LOG_DRV(" AUDIO AS itf %u: Type-I PCM format not supported\r\n", itf_num);
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);
+ if (!audioh_format_from_pcm(class_info.subslot_size, class_info.bit_resolution, &format)) {
+ TU_LOG_DRV(" AUDIO AS itf %u: subslot %u bits %u not supported\r\n", itf_num, class_info.subslot_size,
+ class_info.bit_resolution);
return p_desc;
}
- if (num_channels == 0) {
+ if (class_info.channels == 0) {
TU_LOG_DRV(" AUDIO AS itf %u: zero channels not supported\r\n", itf_num);
return p_desc;
}
const uint16_t iso_xfer_size =
(tuh_speed_get(p_audio->daddr) == TUSB_SPEED_HIGH) ? TUSB_EPSIZE_ISO_HS_MAX : TUSB_EPSIZE_ISO_FS_MAX;
- const uint32_t frame_bytes_32 = (uint32_t)num_channels * tuh_audio_format_bytes(format);
+ const uint32_t frame_bytes_32 = (uint32_t)class_info.channels * tuh_audio_format_bytes(format);
if (frame_bytes_32 == 0 || frame_bytes_32 > iso_xfer_size) {
TU_LOG_DRV(" AUDIO AS itf %u: frame size %lu not supported\r\n", itf_num, (unsigned long)frame_bytes_32);
return p_desc;
}
- const uint16_t frame_bytes = (uint16_t)frame_bytes_32;
-
// Register one AS alternate setting containing its discrete sampling
// frequencies. The public API flattens these entries when requested.
const audioh_ep_info_t *ep = &ep_info;
tuh_audio_stream_t *stream = audioh_get_stream(p_audio, tu_edpt_dir(ep->ep_addr));
TU_ASSERT(stream != NULL, p_desc);
+ const audioh_terminal_info_t *terminal = audioh_ac_terminal_find(ac_map, class_info.terminal_id);
+ if (terminal == NULL || terminal->stream_dir != stream->dir) {
+ TU_LOG_DRV(" AUDIO AS itf %u alt %u: terminal %u does not match endpoint direction\r\n", itf_num, alt,
+ class_info.terminal_id);
+ return p_desc;
+ }
const uint16_t epbuf_size = (stream->dir == TUSB_DIR_IN) ? CFG_TUH_AUDIO_EPIN_BUFSIZE : CFG_TUH_AUDIO_EPOUT_BUFSIZE;
@@ -965,48 +1395,63 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de
return p_desc;
}
- audioh_as_config_t as_config = {.ep_size = ep->ep_size,
- .itf_num = itf_num,
- .alt_setting = alt,
- .ep_addr = ep->ep_addr,
- .ep_interval = ep->ep_interval,
- .ep_attr = ep->ep_attr,
- .format = (uint8_t)format,
- .channels = num_channels,
- .sam_freq_ctrl = ep->sam_freq_ctrl};
-
- for (uint8_t i = 0; i < sam_freq_count; i++) {
- const uint8_t *rate_data = &sam_freq_data[i * 3u];
- const uint32_t sample_rate =
- (uint32_t)rate_data[0] | ((uint32_t)rate_data[1] << 8) | ((uint32_t)rate_data[2] << 16);
- if (sample_rate == 0) {
- continue;
- }
+ audioh_as_config_t as_config = {.ep_size = ep->ep_size,
+ .itf_num = itf_num,
+ .alt_setting = alt,
+ .ep_addr = ep->ep_addr,
+ .ep_interval = ep->ep_interval,
+ .ep_attr = ep->ep_attr,
+ .format = (uint8_t)format,
+ .channels = class_info.channels,
+ .terminal_id = class_info.terminal_id};
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ audioh_rate_source_t rate_source = {0};
+ #endif
- // The largest whole-frame packet for one poll interval must fit the
- // endpoint. Playback must also stage it in the transfer buffer and FIFO.
- const uint64_t frames_numerator = (uint64_t)sample_rate * audioh_interval_us(ep->ep_interval, p_audio->daddr);
- const uint64_t max_frames = (frames_numerator + 999999u) / 1000000u;
- const uint64_t packet_bytes = max_frames * frame_bytes;
- if (packet_bytes == 0 || packet_bytes > ep->ep_size ||
- (stream->dir == TUSB_DIR_OUT && (packet_bytes > epbuf_size || packet_bytes > CFG_TUH_AUDIO_STREAM_BUFSIZE))) {
- TU_LOG_DRV(" AUDIO AS itf %u alt %u: packet per interval does not fit endpoint/buffers (ep size %u)\r\n",
- itf_num, alt, ep->ep_size);
- continue;
+ switch (p_audio->protocol) {
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ case AUDIO_INT_PROTOCOL_CODE_V1:
+ rate_source.control_id = ep->ep_addr;
+ rate_source.frequency_access = ep->sam_freq_ctrl ? AUDIOH_CTRL_READ_WRITE : AUDIOH_CTRL_NONE;
+ if (!audioh_uac1_rates_store(p_audio, stream, &as_config, &class_info, &rate_source)) {
+ TU_LOG_DRV(" AUDIO AS itf %u alt %u: no supported sampling frequency\r\n", itf_num, alt);
+ return p_desc;
+ }
+ break;
+ #endif
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ case AUDIO_INT_PROTOCOL_CODE_V2: {
+ const int8_t rate_source_idx = audioh_uac2_rate_source_get(p_audio, ac_map, terminal);
+ if (rate_source_idx < 0) {
+ TU_LOG_DRV(" AUDIO AS itf %u alt %u: direct Clock Source not found\r\n", itf_num, alt);
+ return p_desc;
+ }
+ as_config.rate_source_idx = (uint8_t)rate_source_idx;
+ break;
}
- as_config.sample_rate[as_config.sample_rate_count++] = sample_rate;
- }
-
- if (as_config.sample_rate_count == 0) {
- return p_desc;
+ #endif
+ default:
+ return p_desc;
}
if (stream->as_count >= CFG_TUH_AUDIO_MAX_AS) {
TU_LOG_DRV(" AUDIO AS itf %u alt %u: reach max alternate settings %u\r\n", itf_num, alt, CFG_TUH_AUDIO_MAX_AS);
return p_desc;
}
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ if (p_audio->protocol == AUDIO_INT_PROTOCOL_CODE_V1 && p_audio->rate_source_count >= AUDIOH_MAX_RATE_SOURCES) {
+ TU_LOG_DRV(" AUDIO AS itf %u alt %u: reach max rate sources %u\r\n", itf_num, alt, AUDIOH_MAX_RATE_SOURCES);
+ return p_desc;
+ }
+ #endif
const uint8_t as_idx = stream->as_count;
- stream->as[as_idx] = as_config;
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ if (p_audio->protocol == AUDIO_INT_PROTOCOL_CODE_V1) {
+ as_config.rate_source_idx = p_audio->rate_source_count;
+ p_audio->rate_source[p_audio->rate_source_count++] = rate_source;
+ }
+ #endif
+ stream->as[as_idx] = as_config;
if (stream->dir == TUSB_DIR_OUT && has_feedback_ep) {
audioh_feedback_ep_t *feedback = &p_audio->playback.feedback[as_idx];
feedback->ep_addr = fb_info.ep_addr;
@@ -1015,7 +1460,7 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de
feedback->ep_attr = fb_info.ep_attr;
}
stream->as_count++;
- stream->config_count += as_config.sample_rate_count;
+ stream->config_count += as_config.rate_count;
return p_desc;
}
@@ -1032,13 +1477,16 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface
TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_type(desc_itf) == TUSB_DESC_INTERFACE), 0);
TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0);
TU_VERIFY(AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass, 0);
- TU_VERIFY(AUDIO_INT_PROTOCOL_CODE_V1 == desc_itf->bInterfaceProtocol, 0);
+ TU_VERIFY(audioh_protocol_enabled(desc_itf->bInterfaceProtocol), 0);
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->daddr = dev_addr;
p_audio->ac_itf_num = desc_itf->bInterfaceNumber;
+ p_audio->protocol = desc_itf->bInterfaceProtocol;
+ p_audio->rate_source_count = 0;
+ tu_memclr(p_audio->rate_source, sizeof(p_audio->rate_source));
audioh_stream_reset(&p_audio->in_stream);
audioh_stream_reset(&p_audio->out_stream);
audioh_playback_reset(&p_audio->playback);
@@ -1047,15 +1495,7 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface
TU_LOG_DRV("AUDIO opening AC Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr);
- uint8_t usb_input_terminal_id = 0;
- uint8_t usb_output_source_id = 0;
- // A Feature Unit may precede the USB terminal that identifies its stream.
- typedef struct {
- uint8_t id;
- uint8_t source_id;
- uint8_t controls;
- } audioh_fu_info_t;
- audioh_fu_info_t pending_fu[TUH_AUDIO_STREAM_DIRECTION_COUNT] = {0};
+ audioh_ac_map_t ac_map = {0};
p_desc = tu_desc_next(p_desc);
while (p_desc < desc_end) {
@@ -1070,80 +1510,8 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface
if (!audioh_desc_valid(p_desc, desc_end, 3)) {
goto open_failed;
}
- switch (tu_desc_subtype(p_desc)) {
- case AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL: {
- if (!TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_input_terminal_t))) {
- goto open_failed;
- }
- const audio10_desc_input_terminal_t *terminal = (const audio10_desc_input_terminal_t *)p_desc;
- if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING && usb_input_terminal_id == 0) {
- usb_input_terminal_id = terminal->bTerminalID;
- for (uint8_t i = 0; i < TU_ARRAY_SIZE(pending_fu); i++) {
- if (pending_fu[i].source_id == usb_input_terminal_id) {
- audioh_stream_set_feature_unit(&p_audio->out_stream, pending_fu[i].id, pending_fu[i].controls);
- pending_fu[i] = (audioh_fu_info_t){0};
- break;
- }
- }
- }
- break;
- }
- case AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL: {
- if (!TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_output_terminal_t))) {
- goto open_failed;
- }
- const audio10_desc_output_terminal_t *terminal = (const audio10_desc_output_terminal_t *)p_desc;
- if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING && usb_output_source_id == 0) {
- usb_output_source_id = terminal->bSourceID;
- for (uint8_t i = 0; i < TU_ARRAY_SIZE(pending_fu); i++) {
- if (pending_fu[i].id == usb_output_source_id) {
- audioh_stream_set_feature_unit(&p_audio->in_stream, pending_fu[i].id, pending_fu[i].controls);
- pending_fu[i] = (audioh_fu_info_t){0};
- break;
- }
- }
- }
- break;
- }
- case AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT: {
- if (!TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 7)) {
- goto open_failed;
- }
- const uint8_t control_size = p_desc[5];
- if (!TUH_VALIDATE_BASIC(control_size > 0) ||
- !TUH_VALIDATE_BASIC(control_size <= (uint8_t)(tu_desc_len(p_desc) - 7))) {
- goto open_failed;
- }
- const uint8_t controls = p_desc[6] & (AUDIO10_FU_CONTROL_BM_MUTE | AUDIO10_FU_CONTROL_BM_VOLUME);
- if (controls == 0) {
- break;
- }
-
- bool mapped = false;
- if (usb_input_terminal_id != 0 && p_audio->out_stream.feature_unit_id == 0 &&
- p_desc[4] == usb_input_terminal_id) {
- audioh_stream_set_feature_unit(&p_audio->out_stream, p_desc[3], controls);
- mapped = true;
- }
- if (usb_output_source_id != 0 && p_audio->in_stream.feature_unit_id == 0 &&
- p_desc[3] == usb_output_source_id) {
- audioh_stream_set_feature_unit(&p_audio->in_stream, p_desc[3], controls);
- mapped = true;
- }
- if (!mapped) {
- for (uint8_t i = 0; i < TU_ARRAY_SIZE(pending_fu); i++) {
- if (pending_fu[i].id == 0) {
- pending_fu[i].id = p_desc[3];
- pending_fu[i].source_id = p_desc[4];
- pending_fu[i].controls = controls;
- break;
- }
- }
- }
- break;
- }
- default:
- break;
+ if (!audioh_parse_ac_entity(p_audio, &ac_map, p_desc)) {
+ goto open_failed;
}
}
p_desc = tu_desc_next(p_desc);
@@ -1164,31 +1532,34 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface
}
const tusb_desc_interface_t *desc_interface = (const tusb_desc_interface_t *)p_desc;
if (desc_interface->bInterfaceClass != TUSB_CLASS_AUDIO ||
- desc_interface->bInterfaceSubClass != AUDIO_SUBCLASS_STREAMING) {
+ desc_interface->bInterfaceSubClass != AUDIO_SUBCLASS_STREAMING ||
+ desc_interface->bInterfaceProtocol != p_audio->protocol) {
break;
}
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);
+ p_desc = audioh_parse_as(p_audio, &ac_map, desc_interface, p_desc, desc_end);
if (p_desc == NULL) {
goto open_failed;
}
}
- // Release the tentative instance when no supported stream configuration was
- // collected, including MIDI-only and unsupported Audio functions.
- if (p_audio->in_stream.config_count == 0 && p_audio->out_stream.config_count == 0) {
+ audioh_link_feature_units(p_audio, &ac_map);
+
+ // UAC2 configurations receive their rates asynchronously during mount.
+ // Release the tentative instance when no supported AS alternate was found.
+ if (p_audio->in_stream.as_count == 0 && p_audio->out_stream.as_count == 0) {
goto open_failed;
}
// 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) {
+ if (p_audio->out_stream.as_count > 0) {
p_audio->out_stream.stream_idx = stream_idx++;
}
- if (p_audio->in_stream.config_count > 0) {
+ if (p_audio->in_stream.as_count > 0) {
p_audio->in_stream.stream_idx = stream_idx++;
}
p_audio->stream_count = stream_idx;
@@ -1199,10 +1570,12 @@ open_failed:
audioh_stream_reset(&p_audio->in_stream);
audioh_stream_reset(&p_audio->out_stream);
audioh_playback_reset(&p_audio->playback);
- p_audio->daddr = 0;
- p_audio->ac_itf_num = 0;
- p_audio->stream_count = 0;
- p_audio->mounted = false;
+ p_audio->daddr = 0;
+ p_audio->ac_itf_num = 0;
+ p_audio->protocol = 0;
+ p_audio->stream_count = 0;
+ p_audio->rate_source_count = 0;
+ p_audio->mounted = false;
return 0;
}
@@ -1211,6 +1584,174 @@ open_failed:
//--------------------------------------------------------------------+
static void audioh_mount_feature_unit_next(uint8_t idx);
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+static void audioh_mount_clock_complete(tuh_xfer_t *xfer);
+
+static void audioh_uac2_configs_rebuild(audioh_interface_t *p_audio) {
+ p_audio->in_stream.config_count = 0;
+ p_audio->out_stream.config_count = 0;
+
+ for (uint8_t direction = TUSB_DIR_OUT; direction <= TUSB_DIR_IN; direction++) {
+ tuh_audio_stream_t *stream = audioh_get_stream(p_audio, (tusb_dir_t)direction);
+ TU_ASSERT(stream != NULL, );
+ for (uint8_t as_idx = 0; as_idx < stream->as_count; as_idx++) {
+ audioh_as_config_t *as = &stream->as[as_idx];
+ audioh_rate_source_t *rate_source = audioh_as_rate_source(stream, as);
+ as->rate_count = 0;
+ for (uint8_t rate_idx = 0; rate_idx < rate_source->sample_rate_count; rate_idx++) {
+ if (audioh_as_rate_fits(p_audio, stream, as, rate_source->sample_rate[rate_idx])) {
+ as->rate_count++;
+ }
+ }
+ stream->config_count += as->rate_count;
+ }
+ }
+
+ uint8_t stream_idx = 0;
+ p_audio->out_stream.stream_idx = TUSB_INDEX_INVALID_8;
+ p_audio->in_stream.stream_idx = TUSB_INDEX_INVALID_8;
+ 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;
+}
+
+static bool audioh_uac2_clock_range_store(audioh_rate_source_t *rate_source, const uint8_t *buffer, uint16_t length) {
+ TU_VERIFY(length >= 2, false);
+ const uint16_t subrange_count = tu_le16toh(tu_unaligned_read16(buffer));
+ const uint16_t available = (uint16_t)((length - 2u) / 12u);
+ TU_VERIFY(subrange_count > 0 && available > 0, false);
+
+ rate_source->sample_rate_count = 0;
+ const uint16_t parsed_count = TU_MIN(subrange_count, available);
+ for (uint16_t i = 0; i < parsed_count && rate_source->sample_rate_count < CFG_TUH_AUDIO_MAX_SAM_FREQ; i++) {
+ const uint8_t *subrange = &buffer[2u + 12u * i];
+ const uint32_t min = tu_le32toh(tu_unaligned_read32(&subrange[0]));
+ const uint32_t max = tu_le32toh(tu_unaligned_read32(&subrange[4]));
+ const uint32_t res = tu_le32toh(tu_unaligned_read32(&subrange[8]));
+ if (min == 0 || min > max || (min != max && res == 0)) {
+ continue;
+ }
+ if (min == max) {
+ rate_source->sample_rate[rate_source->sample_rate_count++] = min;
+ continue;
+ }
+ for (uint32_t rate = min; rate <= max && rate_source->sample_rate_count < CFG_TUH_AUDIO_MAX_SAM_FREQ;) {
+ rate_source->sample_rate[rate_source->sample_rate_count++] = rate;
+ if (max - rate < res) {
+ break;
+ }
+ rate += res;
+ }
+ }
+ return rate_source->sample_rate_count > 0;
+}
+
+static bool audioh_mount_clock_submit(uint8_t idx) {
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
+ audioh_rate_source_t *rate_source = &p_audio->rate_source[epbuf->clock.rate_source_idx];
+ const uint16_t length = epbuf->clock.read_cur ? 4u : (uint16_t)sizeof(epbuf->clock_range);
+
+ const tusb_control_request_t request = {
+ .bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN},
+ .bRequest = epbuf->clock.read_cur ? AUDIO20_CS_REQ_CUR : AUDIO20_CS_REQ_RANGE,
+ .wValue = tu_htole16(tu_u16(AUDIO20_CS_CTRL_SAM_FREQ, 0)),
+ .wIndex = tu_htole16(tu_u16(rate_source->control_id, p_audio->ac_itf_num)),
+ .wLength = tu_htole16(length),
+ };
+ tuh_xfer_t xfer = {.daddr = p_audio->daddr,
+ .ep_addr = 0,
+ .setup = &request,
+ .buffer = epbuf->clock_range,
+ .complete_cb = audioh_mount_clock_complete,
+ .user_data = (uintptr_t)idx};
+ return tuh_control_xfer(&xfer);
+}
+
+static void audioh_mount_clock_finish(uint8_t idx) {
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
+ epbuf->fu_busy = false;
+ audioh_uac2_configs_rebuild(p_audio);
+
+ if (p_audio->stream_count == 0) {
+ const uint8_t daddr = p_audio->daddr;
+ const uint8_t itf_num = p_audio->ac_itf_num;
+ audioh_stream_reset(&p_audio->in_stream);
+ audioh_stream_reset(&p_audio->out_stream);
+ audioh_playback_reset(&p_audio->playback);
+ p_audio->daddr = 0;
+ p_audio->ac_itf_num = 0;
+ p_audio->protocol = 0;
+ p_audio->rate_source_count = 0;
+ usbh_driver_set_config_complete(daddr, itf_num);
+ return;
+ }
+
+ epbuf->fu.mount.stream_idx = 0;
+ audioh_mount_feature_unit_next(idx);
+}
+
+static void audioh_mount_clock_next(uint8_t idx) {
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
+
+ while (epbuf->clock.rate_source_idx < p_audio->rate_source_count) {
+ epbuf->clock.read_cur = false;
+ epbuf->fu_busy = true;
+ if (audioh_mount_clock_submit(idx)) {
+ return;
+ }
+ p_audio->rate_source[epbuf->clock.rate_source_idx].sample_rate_count = 0;
+ epbuf->clock.rate_source_idx++;
+ }
+ audioh_mount_clock_finish(idx);
+}
+
+static void audioh_mount_clock_complete(tuh_xfer_t *xfer) {
+ const uint8_t idx = (uint8_t)xfer->user_data;
+ audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
+ if (!epbuf->fu_busy) {
+ return;
+ }
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ audioh_rate_source_t *rate_source = &p_audio->rate_source[epbuf->clock.rate_source_idx];
+
+ bool success = xfer->result == XFER_RESULT_SUCCESS;
+ if (success && epbuf->clock.read_cur) {
+ success = xfer->actual_len == 4;
+ if (success) {
+ const uint32_t current = tu_le32toh(tu_unaligned_read32(epbuf->clock_range));
+ success = current > 0;
+ if (success) {
+ rate_source->sample_rate[0] = current;
+ rate_source->sample_rate_count = 1;
+ }
+ }
+ } else if (success) {
+ success = audioh_uac2_clock_range_store(rate_source, epbuf->clock_range, (uint16_t)xfer->actual_len);
+ if (success && rate_source->frequency_access == AUDIOH_CTRL_READ) {
+ epbuf->clock.read_cur = true;
+ if (audioh_mount_clock_submit(idx)) {
+ return;
+ }
+ success = false;
+ }
+ }
+
+ if (!success) {
+ rate_source->sample_rate_count = 0;
+ }
+ epbuf->fu_busy = false;
+ epbuf->clock.rate_source_idx++;
+ audioh_mount_clock_next(idx);
+}
+ #endif
+
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++) {
@@ -1227,9 +1768,17 @@ bool audioh_set_config(uint8_t dev_addr, uint8_t itf_num) {
return true;
}
- audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
- epbuf->fu.mount.stream_idx = 0;
- audioh_mount_feature_unit_next(idx);
+ audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ if (_audioh_itf[idx].protocol == AUDIO_INT_PROTOCOL_CODE_V2) {
+ epbuf->clock.rate_source_idx = 0;
+ audioh_mount_clock_next(idx);
+ } else
+ #endif
+ {
+ epbuf->fu.mount.stream_idx = 0;
+ audioh_mount_feature_unit_next(idx);
+ }
return true;
}
@@ -1261,7 +1810,7 @@ bool tuh_audio_mute_supported(uint8_t idx, uint8_t stream_idx) {
audioh_interface_t *p_audio = &_audioh_itf[idx];
TU_VERIFY(p_audio->mounted, false);
tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
- return s != NULL && s->mute_supported;
+ return s != NULL && s->mute_access != AUDIOH_CTRL_NONE;
}
bool tuh_audio_volume_range_get(uint8_t idx, uint8_t stream_idx, tuh_audio_volume_range_t *range) {
@@ -1269,7 +1818,7 @@ bool tuh_audio_volume_range_get(uint8_t idx, uint8_t stream_idx, tuh_audio_volum
audioh_interface_t *p_audio = &_audioh_itf[idx];
TU_VERIFY(p_audio->mounted, false);
tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
- TU_VERIFY(s != NULL && s->volume_supported, false);
+ TU_VERIFY(s != NULL && s->volume_access != AUDIOH_CTRL_NONE, false);
*range = s->volume_range;
return true;
}
@@ -1417,6 +1966,13 @@ static void audioh_stream_start_xfer(tuh_audio_stream_t *s) {
}
}
+static void audioh_stream_start_complete(tuh_xfer_t *xfer);
+
+static bool audioh_stream_activate(tuh_audio_stream_t *s) {
+ const audioh_as_config_t *as = audioh_stream_active_as(s);
+ return tuh_interface_set(s->daddr, as->itf_num, as->alt_setting, audioh_stream_start_complete, (uintptr_t)s);
+}
+
static void audioh_stream_start_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_READY || !s->running) {
@@ -1427,17 +1983,31 @@ static void audioh_stream_start_set_freq_complete(tuh_xfer_t *xfer) {
audioh_stream_error(s, 0);
return;
}
- audioh_stream_start_xfer(s);
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ if (_audioh_itf[s->idx].protocol == AUDIO_INT_PROTOCOL_CODE_V2) {
+ if (!audioh_stream_activate(s)) {
+ audioh_stream_error(s, 0);
+ }
+ } else
+ #endif
+ {
+ audioh_stream_start_xfer(s);
+ }
}
static bool audioh_stream_start_active(tuh_audio_stream_t *s) {
- const audioh_as_config_t *as = audioh_stream_active_as(s);
- if (as->sam_freq_ctrl) {
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ const audioh_as_config_t *as = audioh_stream_active_as(s);
+ const audioh_rate_source_t *rate_source = audioh_as_rate_source(s, as);
+ if (_audioh_itf[s->idx].protocol == AUDIO_INT_PROTOCOL_CODE_V1 &&
+ rate_source->frequency_access == AUDIOH_CTRL_READ_WRITE) {
if (!audioh_stream_set_freq(s, audioh_stream_start_set_freq_complete)) {
audioh_stream_error(s, 0);
return false;
}
- } else {
+ } else
+ #endif
+ {
audioh_stream_start_xfer(s);
}
return true;
@@ -1477,9 +2047,19 @@ bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx) {
p_audio->playback.rem_acc = 0;
}
s->running = true;
- // Activate the interface's alternate setting asynchronously: transfers
- // begin once SET_INTERFACE and sampling-frequency control complete.
- if (!tuh_interface_set(s->daddr, as->itf_num, as->alt_setting, audioh_stream_start_complete, (uintptr_t)s)) {
+ // UAC2 changes the Clock Source before selecting the alternate setting;
+ // UAC1 selects the alternate first because its control targets the endpoint.
+ bool submitted = false;
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ const audioh_rate_source_t *rate_source = audioh_as_rate_source(s, as);
+ if (p_audio->protocol == AUDIO_INT_PROTOCOL_CODE_V2 && rate_source->frequency_access == AUDIOH_CTRL_READ_WRITE) {
+ submitted = audioh_stream_set_freq(s, audioh_stream_start_set_freq_complete);
+ } else
+ #endif
+ {
+ submitted = audioh_stream_activate(s);
+ }
+ if (!submitted) {
s->running = false;
return false;
}
@@ -1609,6 +2189,36 @@ enum {
AUDIOH_FU_VALUE_I16
};
+static uint8_t audioh_fu_cur_request(uint8_t protocol, tusb_dir_t direction) {
+ #if !(CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1)
+ (void)direction;
+ #endif
+ switch (protocol) {
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ case AUDIO_INT_PROTOCOL_CODE_V1:
+ return (direction == TUSB_DIR_IN) ? AUDIO10_CS_REQ_GET_CUR : AUDIO10_CS_REQ_SET_CUR;
+ #endif
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ case AUDIO_INT_PROTOCOL_CODE_V2:
+ return AUDIO20_CS_REQ_CUR;
+ #endif
+ default:
+ return 0;
+ }
+}
+
+static bool audioh_fu_selector_supported(uint8_t protocol, uint8_t control_selector) {
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ if (protocol == AUDIO_INT_PROTOCOL_CODE_V2) {
+ return control_selector == AUDIO20_FU_CTRL_MUTE || control_selector == AUDIO20_FU_CTRL_VOLUME;
+ }
+ #else
+ (void)protocol;
+ (void)control_selector;
+ #endif
+ return true;
+}
+
static void audioh_fu_value_store(audioh_epbuf_t *epbuf) {
if (epbuf->fu.control.value_type == AUDIOH_FU_VALUE_BOOL) {
*((bool *)epbuf->value) = epbuf->fu_ctrl[0] != 0;
@@ -1692,12 +2302,13 @@ static bool audioh_mount_feature_unit_submit(uint8_t idx) {
tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, epbuf->fu.mount.stream_idx);
TU_ASSERT(s != NULL);
+ const bool uac2 = p_audio->protocol == AUDIO_INT_PROTOCOL_CODE_V2;
const tusb_control_request_t request = {
.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN},
- .bRequest = audioh_fu_volume_range_request(epbuf->fu.mount.range_step),
+ .bRequest = uac2 ? AUDIO20_CS_REQ_RANGE : audioh_fu_volume_range_request(epbuf->fu.mount.range_step),
.wValue = tu_htole16(tu_u16(AUDIO10_FU_CTRL_VOLUME, 0)),
.wIndex = tu_htole16(tu_u16(s->feature_unit_id, p_audio->ac_itf_num)),
- .wLength = tu_htole16(2),
+ .wLength = tu_htole16(uac2 ? 8u : 2u),
};
tuh_xfer_t xfer = {.daddr = p_audio->daddr,
.ep_addr = 0,
@@ -1715,15 +2326,15 @@ static void audioh_mount_feature_unit_next(uint8_t idx) {
while (epbuf->fu.mount.stream_idx < p_audio->stream_count) {
tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, epbuf->fu.mount.stream_idx);
TU_ASSERT(s != NULL, );
- if (s->volume_supported) {
+ if (s->volume_access != AUDIOH_CTRL_NONE) {
s->volume_range = (tuh_audio_volume_range_t){0};
epbuf->fu.mount.range_step = AUDIOH_VOLUME_RANGE_MIN;
epbuf->fu_busy = true;
if (audioh_mount_feature_unit_submit(idx)) {
return;
}
- s->volume_supported = false;
- if (!s->mute_supported) {
+ s->volume_access = AUDIOH_CTRL_NONE;
+ if (s->mute_access == AUDIOH_CTRL_NONE) {
s->feature_unit_id = 0;
}
epbuf->fu_busy = false;
@@ -1747,7 +2358,16 @@ static void audioh_mount_feature_unit_complete(tuh_xfer_t *xfer) {
tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, epbuf->fu.mount.stream_idx);
TU_ASSERT(s != NULL, );
- if (xfer->result == XFER_RESULT_SUCCESS && xfer->actual_len == 2) {
+ const bool uac2 = p_audio->protocol == AUDIO_INT_PROTOCOL_CODE_V2;
+ if (uac2 && xfer->result == XFER_RESULT_SUCCESS && xfer->actual_len == 8 &&
+ tu_le16toh(tu_unaligned_read16(epbuf->fu_ctrl)) == 1) {
+ s->volume_range.min = (int16_t)tu_le16toh(tu_unaligned_read16(&epbuf->fu_ctrl[2]));
+ s->volume_range.max = (int16_t)tu_le16toh(tu_unaligned_read16(&epbuf->fu_ctrl[4]));
+ s->volume_range.res = tu_le16toh(tu_unaligned_read16(&epbuf->fu_ctrl[6]));
+ if (s->volume_range.min > s->volume_range.max || s->volume_range.res == 0) {
+ xfer->result = XFER_RESULT_FAILED;
+ }
+ } else if (!uac2 && xfer->result == XFER_RESULT_SUCCESS && xfer->actual_len == 2) {
audioh_fu_volume_range_store(s, epbuf);
epbuf->fu.mount.range_step++;
@@ -1761,10 +2381,11 @@ static void audioh_mount_feature_unit_complete(tuh_xfer_t *xfer) {
}
}
- if (xfer->result != XFER_RESULT_SUCCESS || xfer->actual_len != 2) {
- s->volume_supported = false;
- s->volume_range = (tuh_audio_volume_range_t){0};
- if (!s->mute_supported) {
+ const uint32_t expected_len = uac2 ? 8u : 2u;
+ if (xfer->result != XFER_RESULT_SUCCESS || xfer->actual_len != expected_len) {
+ s->volume_access = AUDIOH_CTRL_NONE;
+ s->volume_range = (tuh_audio_volume_range_t){0};
+ if (s->mute_access == AUDIOH_CTRL_NONE) {
s->feature_unit_id = 0;
}
}
@@ -1780,10 +2401,18 @@ bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t stream_idx, uint8_t control
TU_VERIFY(p_audio->mounted, false);
tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
TU_VERIFY(s && s->feature_unit_id != 0, false);
+ TU_VERIFY(audioh_fu_selector_supported(p_audio->protocol, control_selector), false);
const uint8_t width = audioh_fu_control_width(control_selector);
TU_VERIFY(width != 0, false);
+ if (control_selector == AUDIO10_FU_CTRL_MUTE) {
+ TU_VERIFY(s->mute_access == AUDIOH_CTRL_READ_WRITE, false);
+ } else if (control_selector == AUDIO10_FU_CTRL_VOLUME) {
+ TU_VERIFY(s->volume_access == AUDIOH_CTRL_READ_WRITE, false);
+ }
+ const uint8_t request_code = audioh_fu_cur_request(p_audio->protocol, TUSB_DIR_OUT);
+ TU_VERIFY(request_code != 0, false);
audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
TU_VERIFY(!epbuf->fu_busy, false);
epbuf->fu_busy = true; // reserve the request state and fu_ctrl before writing
@@ -1791,7 +2420,7 @@ bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t stream_idx, uint8_t control
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,
+ .bRequest = request_code,
.wValue = tu_htole16(tu_u16(control_selector, channel)),
.wIndex = tu_htole16(tu_u16(s->feature_unit_id, p_audio->ac_itf_num)),
.wLength = width};
@@ -1835,10 +2464,18 @@ static bool audioh_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t con
TU_VERIFY(p_audio->mounted && value, false);
tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
TU_VERIFY(s && s->feature_unit_id != 0, false);
+ TU_VERIFY(audioh_fu_selector_supported(p_audio->protocol, control_selector), false);
const uint8_t width = audioh_fu_control_width(control_selector);
TU_VERIFY(width != 0, false);
+ if (control_selector == AUDIO10_FU_CTRL_MUTE) {
+ TU_VERIFY(s->mute_access != AUDIOH_CTRL_NONE, false);
+ } else if (control_selector == AUDIO10_FU_CTRL_VOLUME) {
+ TU_VERIFY(s->volume_access != AUDIOH_CTRL_NONE, false);
+ }
+ const uint8_t request_code = audioh_fu_cur_request(p_audio->protocol, TUSB_DIR_IN);
+ TU_VERIFY(request_code != 0, false);
audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
TU_VERIFY(!epbuf->fu_busy, false);
epbuf->fu_busy = true;
@@ -1849,7 +2486,7 @@ static bool audioh_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t con
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,
+ .bRequest = request_code,
.wValue = tu_htole16(tu_u16(control_selector, channel)),
.wIndex = tu_htole16(tu_u16(s->feature_unit_id, p_audio->ac_itf_num)),
.wLength = width};
diff --git a/src/class/audio/audio_host.h b/src/class/audio/audio_host.h
index f8b6a80c8..42cf4b4e5 100644
--- a/src/class/audio/audio_host.h
+++ b/src/class/audio/audio_host.h
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 Zhenjiang Zhang
+ * SPDX-FileCopyrightText: Copyright (c) 2026 HiFiPhile (Zixun LI)
* SPDX-License-Identifier: MIT
*
* This file is part of the TinyUSB stack.
@@ -17,6 +18,24 @@ extern "C" {
//--------------------------------------------------------------------+
// Class Driver Configuration
//--------------------------------------------------------------------+
+
+// Audio Class protocol versions compiled into the host driver. Multiple
+// versions can be enabled so UAC1 and UAC2 devices can be mounted together.
+#define TUH_AUDIO_PROTOCOL_UAC1 TU_BIT(0)
+#define TUH_AUDIO_PROTOCOL_UAC2 TU_BIT(1)
+
+#ifndef CFG_TUH_AUDIO_PROTOCOLS
+ #define CFG_TUH_AUDIO_PROTOCOLS TUH_AUDIO_PROTOCOL_UAC1
+#endif
+
+#if !(CFG_TUH_AUDIO_PROTOCOLS & (TUH_AUDIO_PROTOCOL_UAC1 | TUH_AUDIO_PROTOCOL_UAC2))
+ #error CFG_TUH_AUDIO_PROTOCOLS must enable UAC1 and/or UAC2
+#endif
+
+#if CFG_TUH_AUDIO_PROTOCOLS & ~(TUH_AUDIO_PROTOCOL_UAC1 | TUH_AUDIO_PROTOCOL_UAC2)
+ #error CFG_TUH_AUDIO_PROTOCOLS contains an unsupported protocol bit
+#endif
+
// Maximum number of Audio devices
#ifndef CFG_TUH_AUDIO_MAX
#define CFG_TUH_AUDIO_MAX 1
@@ -62,8 +81,8 @@ typedef enum {
TUH_AUDIO_STREAM_DIRECTION_COUNT
} tuh_audio_direction_t;
-// Discrete sample format. Only UAC1 configurations with bSamFreqType > 0 are
-// supported; sampling-frequency ranges are ignored by the driver.
+// Discrete Type-I PCM sample format. UAC1 requires bSamFreqType > 0; UAC2
+// configurations are built from the directly connected Clock Source RANGE.
typedef enum {
TUH_AUDIO_FORMAT_S8 = 0, // signed 8-bit
TUH_AUDIO_FORMAT_S16_LE, // signed 16-bit little-endian
@@ -139,8 +158,9 @@ bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx
// Stream Control / Frame-based Data
//--------------------------------------------------------------------+
-// Start transferring data by activating the alternate setting selected by
-// configure, then set the endpoint sampling frequency when supported.
+// Start transferring data with the configuration selected by configure().
+// UAC1 activates the alternate setting before setting an endpoint frequency;
+// UAC2 sets a writable Clock Source before activating the alternate setting.
bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx);
// Stop transferring and deactivate the Audio Streaming interface (alt 0).
bool tuh_audio_stop(uint8_t dev_idx, uint8_t stream_idx);
@@ -203,13 +223,17 @@ bool tuh_audio_volume_range_get(uint8_t idx, uint8_t stream_idx, tuh_audio_volum
// Control Request API
//--------------------------------------------------------------------+
-// Set a Feature Unit control (mute, volume, ...) associated with an Audio stream (UAC 1.0)
+// Set a Feature Unit control associated with an Audio stream. UAC2 supports
+// mute and volume through this low-level API; the other fixed-width selectors
+// below are UAC1-only.
// Mute/bass/mid/treble/AGC/bass boost/loudness use one byte; volume/delay use two.
// Graphic EQ and unknown selectors are unsupported.
bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t stream_idx, uint8_t control_selector, uint8_t channel,
uint16_t value, tuh_xfer_cb_t complete_cb, uintptr_t user_data);
-// Get a Feature Unit control (mute, volume, ...) associated with an Audio stream (UAC 1.0)
+// Get a Feature Unit control associated with an Audio stream. UAC2 supports
+// mute and volume through this low-level API; the other fixed-width selectors
+// below are UAC1-only.
// The value is converted to host byte order before complete_cb is invoked.
// Graphic EQ and unknown selectors are unsupported.
// Only one Feature Unit operation may be in flight per device.
diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt
index cea9fe33d..06cff5b09 100644
--- a/test/unit-test/CMakeLists.txt
+++ b/test/unit-test/CMakeLists.txt
@@ -137,6 +137,7 @@ add_ceedling_test(
target_compile_definitions(test_audio_host PRIVATE
CFG_TUH_ENABLED=1
CFG_TUH_AUDIO=1
+ CFG_TUH_AUDIO_PROTOCOLS=3
CFG_TUSB_DEBUG=0
CFG_TUH_AUDIO_EPIN_BUFSIZE=1024
CFG_TUH_AUDIO_EPOUT_BUFSIZE=1024
diff --git a/test/unit-test/project.yml b/test/unit-test/project.yml
index 9dba70d20..599dabc21 100644
--- a/test/unit-test/project.yml
+++ b/test/unit-test/project.yml
@@ -135,6 +135,7 @@
:test_audio_host:
- CFG_TUH_ENABLED=1
- CFG_TUH_AUDIO=1
+ - CFG_TUH_AUDIO_PROTOCOLS=3
- CFG_TUSB_DEBUG=0
- CFG_TUH_AUDIO_EPIN_BUFSIZE=1024
- CFG_TUH_AUDIO_EPOUT_BUFSIZE=1024
diff --git a/test/unit-test/test/host/audio/test_audio_host.c b/test/unit-test/test/host/audio/test_audio_host.c
index 0d7569090..5e312e457 100644
--- a/test/unit-test/test/host/audio/test_audio_host.c
+++ b/test/unit-test/test/host/audio/test_audio_host.c
@@ -22,6 +22,8 @@ enum {
CAPTURE_INPUT_TERM = 11,
CAPTURE_FU = 12,
CAPTURE_OUTPUT_TERM = 13,
+
+ PLAYBACK_CLOCK = 10,
};
static bool interface_set_result;
@@ -32,7 +34,7 @@ static uint8_t interface_set_count;
static bool control_xfer_result;
static tuh_xfer_t control_xfer;
static tusb_control_request_t control_request;
-static uint8_t control_buffer[3];
+static uint8_t control_buffer[8];
static uint8_t control_xfer_count;
static bool edpt_open_result;
@@ -247,6 +249,47 @@ uint32_t tu_edpt_stream_read_xfer(tu_edpt_stream_t *s) {
#define TEST_UAC1_CS_DATA_EP TEST_UAC1_CS_DATA_EP_ATTR(AUDIO10_CS_AS_ISO_DATA_EP_ATT_SAMPLING_FRQ)
+#define TEST_UAC2_AC_HEADER(_total_len) \
+ 9, TUSB_DESC_INTERFACE, AUDIO_AC_ITF, 0, 0, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_INT_PROTOCOL_CODE_V2, 0, \
+ 9, TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AC_INTERFACE_HEADER, U16_TO_U8S_LE(0x0200), 0, U16_TO_U8S_LE(_total_len), 0
+
+#define TEST_UAC2_CLOCK_SOURCE(_id, _access) \
+ 8, TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AC_INTERFACE_CLOCK_SOURCE, _id, AUDIO20_CLOCK_SOURCE_ATT_INT_PRO_CLK, _access, \
+ 0, 0
+
+#define TEST_UAC2_INPUT_TERM(_id, _type, _clock_id, _channels) \
+ 17, TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AC_INTERFACE_INPUT_TERMINAL, _id, U16_TO_U8S_LE(_type), 0, _clock_id, \
+ _channels, U32_TO_U8S_LE(AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED), 0, U16_TO_U8S_LE(0), 0
+
+#define TEST_UAC2_OUTPUT_TERM(_id, _type, _source_id, _clock_id) \
+ 12, TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AC_INTERFACE_OUTPUT_TERMINAL, _id, U16_TO_U8S_LE(_type), 0, _source_id, \
+ _clock_id, U16_TO_U8S_LE(0), 0
+
+
+#define TEST_UAC2_FEATURE_UNIT_STEREO(_id, _source_id, _master_controls) \
+ 18, TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AC_INTERFACE_FEATURE_UNIT, _id, _source_id, U32_TO_U8S_LE(_master_controls), \
+ U32_TO_U8S_LE(0), U32_TO_U8S_LE(0), 0
+
+
+#define TEST_UAC2_AS_INTERFACE_NUM(_itf, _alt, _ep_count) \
+ 9, TUSB_DESC_INTERFACE, _itf, _alt, _ep_count, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_STREAMING, \
+ AUDIO_INT_PROTOCOL_CODE_V2, 0
+
+#define TEST_UAC2_AS_INTERFACE(_alt, _ep_count) TEST_UAC2_AS_INTERFACE_NUM(AUDIO_AS_ITF, _alt, _ep_count)
+
+#define TEST_UAC2_AS_ALT0 TEST_UAC2_AS_INTERFACE(0, 0)
+
+#define TEST_UAC2_AS_GENERAL(_term_id, _channels) \
+ 16, TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AS_INTERFACE_AS_GENERAL, _term_id, 0, AUDIO20_FORMAT_TYPE_I, \
+ U32_TO_U8S_LE(AUDIO20_DATA_FORMAT_TYPE_I_PCM), _channels, U32_TO_U8S_LE(AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED), 0
+
+#define TEST_UAC2_FORMAT(_bytes, _bits) \
+ 6, TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AS_INTERFACE_FORMAT_TYPE, AUDIO20_FORMAT_TYPE_I, _bytes, _bits
+
+#define TEST_UAC2_CS_DATA_EP \
+ 8, TUSB_DESC_CS_ENDPOINT, AUDIO20_CS_EP_SUBTYPE_GENERAL, AUDIO20_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, 0, \
+ AUDIO20_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, U16_TO_U8S_LE(0)
+
static const uint8_t playback_with_explicit_feedback[] = {
TEST_UAC1_AC_HEADER,
TEST_UAC1_INPUT_TERM(PLAYBACK_INPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, 2),
@@ -278,6 +321,73 @@ static const uint8_t uac2_control_interface[] = {
9, TUSB_DESC_INTERFACE, AUDIO_AC_ITF, 0, 0, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_INT_PROTOCOL_CODE_V2, 0,
};
+static const uint8_t uac2_playback[] = {
+ TEST_UAC2_AC_HEADER(64),
+ // Deliberately reorder AC entities to verify association is ID-based.
+ TEST_UAC2_FEATURE_UNIT_STEREO(PLAYBACK_FU, PLAYBACK_INPUT_TERM,
+ (AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS) |
+ (AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS)),
+ TEST_UAC2_OUTPUT_TERM(PLAYBACK_OUTPUT_TERM, AUDIO_TERM_TYPE_OUT_HEADPHONES, PLAYBACK_FU, PLAYBACK_CLOCK),
+ TEST_UAC2_INPUT_TERM(PLAYBACK_INPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, PLAYBACK_CLOCK, 2),
+ TEST_UAC2_CLOCK_SOURCE(PLAYBACK_CLOCK, AUDIO20_CTRL_RW << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS),
+ TEST_UAC2_AS_ALT0,
+ TEST_UAC2_AS_INTERFACE(1, 1),
+ TEST_UAC2_AS_GENERAL(PLAYBACK_INPUT_TERM, 2),
+ TEST_UAC2_FORMAT(2, 16),
+ TEST_UAC1_DATA_EP(0x01, TUSB_ISO_EP_ATT_ASYNCHRONOUS, 192, 1),
+ TEST_UAC2_CS_DATA_EP,
+};
+
+static const uint8_t uac2_playback_read_only_feature_unit[] = {
+ TEST_UAC2_AC_HEADER(64),
+ TEST_UAC2_CLOCK_SOURCE(PLAYBACK_CLOCK, AUDIO20_CTRL_RW << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS),
+ TEST_UAC2_INPUT_TERM(PLAYBACK_INPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, PLAYBACK_CLOCK, 2),
+ TEST_UAC2_FEATURE_UNIT_STEREO(PLAYBACK_FU, PLAYBACK_INPUT_TERM,
+ (AUDIO20_CTRL_R << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS) |
+ (AUDIO20_CTRL_R << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS)),
+ TEST_UAC2_OUTPUT_TERM(PLAYBACK_OUTPUT_TERM, AUDIO_TERM_TYPE_OUT_HEADPHONES, PLAYBACK_FU, PLAYBACK_CLOCK),
+ TEST_UAC2_AS_ALT0,
+ TEST_UAC2_AS_INTERFACE(1, 1),
+ TEST_UAC2_AS_GENERAL(PLAYBACK_INPUT_TERM, 2),
+ TEST_UAC2_FORMAT(2, 16),
+ TEST_UAC1_DATA_EP(0x01, TUSB_ISO_EP_ATT_ASYNCHRONOUS, 192, 1),
+ TEST_UAC2_CS_DATA_EP,
+};
+
+static const uint8_t uac2_playback_read_only_clock[] = {
+ TEST_UAC2_AC_HEADER(46),
+ TEST_UAC2_CLOCK_SOURCE(PLAYBACK_CLOCK, AUDIO20_CTRL_R << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS),
+ TEST_UAC2_INPUT_TERM(PLAYBACK_INPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, PLAYBACK_CLOCK, 2),
+ TEST_UAC2_OUTPUT_TERM(PLAYBACK_OUTPUT_TERM, AUDIO_TERM_TYPE_OUT_HEADPHONES, PLAYBACK_INPUT_TERM, PLAYBACK_CLOCK),
+ TEST_UAC2_AS_ALT0,
+ TEST_UAC2_AS_INTERFACE(1, 1),
+ TEST_UAC2_AS_GENERAL(PLAYBACK_INPUT_TERM, 2),
+ TEST_UAC2_FORMAT(2, 16),
+ TEST_UAC1_DATA_EP(0x01, TUSB_ISO_EP_ATT_ASYNCHRONOUS, 192, 1),
+ TEST_UAC2_CS_DATA_EP,
+};
+
+static const uint8_t uac2_duplex_shared_clock[] = {
+ TEST_UAC2_AC_HEADER(75),
+ TEST_UAC2_OUTPUT_TERM(CAPTURE_OUTPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, CAPTURE_INPUT_TERM, PLAYBACK_CLOCK),
+ TEST_UAC2_INPUT_TERM(PLAYBACK_INPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, PLAYBACK_CLOCK, 2),
+ TEST_UAC2_CLOCK_SOURCE(PLAYBACK_CLOCK, AUDIO20_CTRL_RW << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS),
+ TEST_UAC2_INPUT_TERM(CAPTURE_INPUT_TERM, AUDIO_TERM_TYPE_IN_GENERIC_MIC, PLAYBACK_CLOCK, 1),
+ TEST_UAC2_OUTPUT_TERM(PLAYBACK_OUTPUT_TERM, AUDIO_TERM_TYPE_OUT_HEADPHONES, PLAYBACK_INPUT_TERM, PLAYBACK_CLOCK),
+ TEST_UAC2_AS_ALT0,
+ TEST_UAC2_AS_INTERFACE(1, 1),
+ TEST_UAC2_AS_GENERAL(PLAYBACK_INPUT_TERM, 2),
+ TEST_UAC2_FORMAT(2, 16),
+ TEST_UAC2_CS_DATA_EP,
+ TEST_UAC1_DATA_EP(0x01, TUSB_ISO_EP_ATT_ASYNCHRONOUS, 192, 1),
+ TEST_UAC2_AS_INTERFACE_NUM(AUDIO_AS_IN_ITF, 0, 0),
+ TEST_UAC2_AS_INTERFACE_NUM(AUDIO_AS_IN_ITF, 1, 1),
+ TEST_UAC2_AS_GENERAL(CAPTURE_OUTPUT_TERM, 1),
+ TEST_UAC2_FORMAT(2, 16),
+ TEST_UAC1_DATA_EP(0x81, TUSB_ISO_EP_ATT_ASYNCHRONOUS, 96, 1),
+ TEST_UAC2_CS_DATA_EP,
+};
+
static const uint8_t playback_with_implicit_feedback[] = {
TEST_UAC1_AC_HEADER_2,
TEST_UAC1_INPUT_TERM(PLAYBACK_INPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, 2),
@@ -445,7 +555,8 @@ static const uint8_t playback_with_cs_ep_before_data_ep[] = {
static const uint8_t malformed_zero_length_ac_descriptor[] = {
TEST_UAC1_AC_HEADER,
- 0, TUSB_DESC_CS_INTERFACE,
+ 0,
+ TUSB_DESC_CS_INTERFACE,
};
static const uint8_t malformed_sampling_frequency_list[] = {
@@ -454,7 +565,14 @@ static const uint8_t malformed_sampling_frequency_list[] = {
TEST_UAC1_AS_ALT0,
TEST_UAC1_AS_INTERFACE(1, 1),
TEST_UAC1_AS_GENERAL(PLAYBACK_INPUT_TERM),
- 11, TUSB_DESC_CS_INTERFACE, AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE, AUDIO10_FORMAT_TYPE_I, 2, 2, 16, 2,
+ 11,
+ TUSB_DESC_CS_INTERFACE,
+ AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE,
+ AUDIO10_FORMAT_TYPE_I,
+ 2,
+ 2,
+ 16,
+ 2,
U24_TO_U8S_LE(48000),
};
@@ -465,7 +583,11 @@ static const uint8_t malformed_short_endpoint[] = {
TEST_UAC1_AS_INTERFACE(1, 1),
TEST_UAC1_AS_GENERAL(PLAYBACK_INPUT_TERM),
TEST_UAC1_FORMAT(2, 2, 16, 48000),
- 6, TUSB_DESC_ENDPOINT, 0x01, (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ADAPTIVE), U16_TO_U8S_LE(192),
+ 6,
+ TUSB_DESC_ENDPOINT,
+ 0x01,
+ (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ADAPTIVE),
+ U16_TO_U8S_LE(192),
};
static const uint8_t playback_with_two_alternates[] = {
@@ -561,6 +683,36 @@ static void complete_control_xfer_with_u16(uint16_t value) {
complete_control_xfer(XFER_RESULT_SUCCESS);
}
+static void complete_control_xfer_with_u32(uint32_t value) {
+ TEST_ASSERT_NOT_NULL(control_xfer.buffer);
+ tu_unaligned_write32(control_xfer.buffer, tu_htole32(value));
+ control_xfer.actual_len = 4;
+ complete_control_xfer(XFER_RESULT_SUCCESS);
+}
+
+static void complete_uac2_clock_range(uint32_t rate0, uint32_t rate1) {
+ TEST_ASSERT_NOT_NULL(control_xfer.buffer);
+ tu_unaligned_write16(control_xfer.buffer, tu_htole16(2));
+ tu_unaligned_write32(&control_xfer.buffer[2], tu_htole32(rate0));
+ tu_unaligned_write32(&control_xfer.buffer[6], tu_htole32(rate0));
+ tu_unaligned_write32(&control_xfer.buffer[10], 0);
+ tu_unaligned_write32(&control_xfer.buffer[14], tu_htole32(rate1));
+ tu_unaligned_write32(&control_xfer.buffer[18], tu_htole32(rate1));
+ tu_unaligned_write32(&control_xfer.buffer[22], 0);
+ control_xfer.actual_len = 26;
+ complete_control_xfer(XFER_RESULT_SUCCESS);
+}
+
+static void complete_uac2_volume_range(int16_t min, int16_t max, uint16_t res) {
+ TEST_ASSERT_NOT_NULL(control_xfer.buffer);
+ tu_unaligned_write16(control_xfer.buffer, tu_htole16(1));
+ tu_unaligned_write16(&control_xfer.buffer[2], tu_htole16((uint16_t)min));
+ tu_unaligned_write16(&control_xfer.buffer[4], tu_htole16((uint16_t)max));
+ tu_unaligned_write16(&control_xfer.buffer[6], tu_htole16(res));
+ control_xfer.actual_len = 8;
+ complete_control_xfer(XFER_RESULT_SUCCESS);
+}
+
static uint16_t edpt_xfer_bytes_at(uint8_t ep_addr, uint8_t ep_xfer_idx) {
uint8_t found = 0;
for (uint8_t i = 0; i < edpt_xfer_count; i++) {
@@ -702,6 +854,135 @@ void test_audio_host_rejects_uac2_interface_without_consuming_instance(void) {
TEST_ASSERT_EQUAL_UINT8(1, tuh_audio_stream_count(0));
}
+void test_audio_host_mounts_uac2_and_sets_clock_before_activating_stream(void) {
+ open_descriptors(uac2_playback, sizeof(uac2_playback));
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+
+ TEST_ASSERT_FALSE(tuh_audio_mounted(0));
+ TEST_ASSERT_EQUAL_UINT8(1, control_xfer_count);
+ TEST_ASSERT_EQUAL_UINT8(AUDIO20_CS_REQ_RANGE, control_request.bRequest);
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(AUDIO20_CS_CTRL_SAM_FREQ, 0), tu_le16toh(control_request.wValue));
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(PLAYBACK_CLOCK, AUDIO_AC_ITF), tu_le16toh(control_request.wIndex));
+ complete_uac2_clock_range(44100, 48000);
+
+ TEST_ASSERT_FALSE(tuh_audio_mounted(0));
+ TEST_ASSERT_EQUAL_UINT8(2, control_xfer_count);
+ TEST_ASSERT_EQUAL_UINT8(AUDIO20_CS_REQ_RANGE, control_request.bRequest);
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(AUDIO20_FU_CTRL_VOLUME, 0), tu_le16toh(control_request.wValue));
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(PLAYBACK_FU, AUDIO_AC_ITF), tu_le16toh(control_request.wIndex));
+ complete_uac2_volume_range((int16_t)(-90 * 256), (int16_t)(6 * 256), 256);
+
+ TEST_ASSERT_TRUE(tuh_audio_mounted(0));
+ TEST_ASSERT_EQUAL_UINT8(1, tuh_audio_stream_count(0));
+ TEST_ASSERT_EQUAL_UINT8(2, tuh_audio_config_count(0, 0));
+ TEST_ASSERT_EQUAL_UINT8(PLAYBACK_FU, tuh_audio_get_feature_unit_id(0, 0));
+ TEST_ASSERT_TRUE(tuh_audio_mute_supported(0, 0));
+ tuh_audio_volume_range_t range;
+ TEST_ASSERT_TRUE(tuh_audio_volume_range_get(0, 0, &range));
+ TEST_ASSERT_EQUAL_INT16(-90 * 256, range.min);
+ TEST_ASSERT_EQUAL_INT16(6 * 256, range.max);
+ TEST_ASSERT_EQUAL_UINT16(256, range.res);
+
+ control_xfer_count = 0;
+ TEST_ASSERT_TRUE(tuh_audio_volume_set(0, 0, (int16_t)(-6 * 256), feature_unit_complete, 42));
+ TEST_ASSERT_EQUAL_UINT8(1, control_xfer_count);
+ TEST_ASSERT_EQUAL_UINT8(AUDIO20_CS_REQ_CUR, control_request.bRequest);
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(AUDIO20_FU_CTRL_VOLUME, 0), tu_le16toh(control_request.wValue));
+ TEST_ASSERT_EQUAL_UINT16((uint16_t)(-6 * 256), tu_le16toh(tu_unaligned_read16(control_buffer)));
+ complete_control_xfer(XFER_RESULT_SUCCESS);
+ TEST_ASSERT_EQUAL_UINT8(1, fu_cb_count);
+ TEST_ASSERT_EQUAL_UINT32(42, fu_cb_user_data);
+
+ tuh_audio_stream_config_t config;
+ TEST_ASSERT_TRUE(tuh_audio_config_get(0, 0, 1, &config));
+ TEST_ASSERT_EQUAL_UINT32(48000, config.sample_rate);
+ TEST_ASSERT_TRUE(tuh_audio_configure(0, 0, 1));
+
+ control_xfer_count = 0;
+ TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
+ TEST_ASSERT_EQUAL_UINT8(1, control_xfer_count);
+ TEST_ASSERT_EQUAL_UINT8(0, interface_set_count);
+ TEST_ASSERT_EQUAL_UINT8(AUDIO20_CS_REQ_CUR, control_request.bRequest);
+ TEST_ASSERT_EQUAL_UINT16(4, tu_le16toh(control_request.wLength));
+ TEST_ASSERT_EQUAL_UINT32(48000, tu_le32toh(tu_unaligned_read32(control_buffer)));
+
+ complete_control_xfer(XFER_RESULT_SUCCESS);
+ TEST_ASSERT_EQUAL_UINT8(1, interface_set_count);
+ TEST_ASSERT_EQUAL_UINT8(1, interface_alt);
+ TEST_ASSERT_EQUAL_UINT8(0, edpt_xfer_count);
+ complete_interface_set(XFER_RESULT_SUCCESS);
+ TEST_ASSERT_EQUAL_UINT8(1, edpt_xfer_count);
+ TEST_ASSERT_EQUAL_UINT16(192, edpt_xfer_bytes[0]);
+}
+
+void test_audio_host_uac2_read_only_clock_exposes_cur_rate_without_setting_it(void) {
+ open_descriptors(uac2_playback_read_only_clock, sizeof(uac2_playback_read_only_clock));
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+ complete_uac2_clock_range(44100, 48000);
+
+ TEST_ASSERT_EQUAL_UINT8(AUDIO20_CS_REQ_CUR, control_request.bRequest);
+ TEST_ASSERT_EQUAL_UINT16(4, tu_le16toh(control_request.wLength));
+ complete_control_xfer_with_u32(48000);
+ TEST_ASSERT_TRUE(tuh_audio_mounted(0));
+ TEST_ASSERT_EQUAL_UINT8(1, tuh_audio_config_count(0, 0));
+
+ tuh_audio_stream_config_t config;
+ TEST_ASSERT_TRUE(tuh_audio_config_get(0, 0, 0, &config));
+ TEST_ASSERT_EQUAL_UINT32(48000, config.sample_rate);
+ TEST_ASSERT_TRUE(tuh_audio_configure(0, 0, 0));
+
+ control_xfer_count = 0;
+ TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
+ TEST_ASSERT_EQUAL_UINT8(0, control_xfer_count);
+ TEST_ASSERT_EQUAL_UINT8(1, interface_set_count);
+ TEST_ASSERT_EQUAL_UINT8(1, interface_alt);
+}
+
+void test_audio_host_uac2_read_only_feature_controls_reject_set_and_allow_get(void) {
+ open_descriptors(uac2_playback_read_only_feature_unit, sizeof(uac2_playback_read_only_feature_unit));
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+ complete_uac2_clock_range(44100, 48000);
+ complete_uac2_volume_range((int16_t)(-90 * 256), (int16_t)(6 * 256), 256);
+ TEST_ASSERT_TRUE(tuh_audio_mounted(0));
+
+ TEST_ASSERT_FALSE(tuh_audio_mute_set(0, 0, false, feature_unit_complete, 0));
+ TEST_ASSERT_FALSE(tuh_audio_volume_set(0, 0, 0, feature_unit_complete, 0));
+
+ int16_t volume = 0;
+ TEST_ASSERT_TRUE(tuh_audio_volume_get(0, 0, &volume, feature_unit_complete, 7));
+ TEST_ASSERT_EQUAL_UINT8(AUDIO20_CS_REQ_CUR, control_request.bRequest);
+ TEST_ASSERT_EQUAL(TUSB_DIR_IN, control_request.bmRequestType_bit.direction);
+ complete_control_xfer_with_u16((uint16_t)(-12 * 256));
+ TEST_ASSERT_EQUAL_INT16(-12 * 256, volume);
+ TEST_ASSERT_EQUAL_UINT32(7, fu_cb_user_data);
+}
+
+void test_audio_host_uac2_shared_clock_is_discovered_once_for_both_streams(void) {
+ open_descriptors(uac2_duplex_shared_clock, sizeof(uac2_duplex_shared_clock));
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+ TEST_ASSERT_EQUAL_UINT8(1, control_xfer_count);
+ complete_uac2_clock_range(44100, 48000);
+
+ TEST_ASSERT_TRUE(tuh_audio_mounted(0));
+ TEST_ASSERT_EQUAL_UINT8(1, control_xfer_count);
+ TEST_ASSERT_EQUAL_UINT8(2, tuh_audio_stream_count(0));
+ TEST_ASSERT_EQUAL(TUH_AUDIO_STREAM_PLAYBACK, tuh_audio_stream_direction(0, 0));
+ TEST_ASSERT_EQUAL(TUH_AUDIO_STREAM_CAPTURE, tuh_audio_stream_direction(0, 1));
+ TEST_ASSERT_EQUAL_UINT8(2, tuh_audio_config_count(0, 0));
+ TEST_ASSERT_EQUAL_UINT8(2, tuh_audio_config_count(0, 1));
+}
+
+void test_audio_host_rejects_malformed_uac2_clock_range_and_releases_instance(void) {
+ open_descriptors(uac2_playback, sizeof(uac2_playback));
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+ tu_unaligned_write16(control_xfer.buffer, tu_htole16(1));
+ control_xfer.actual_len = 2;
+ complete_control_xfer(XFER_RESULT_SUCCESS);
+
+ TEST_ASSERT_FALSE(tuh_audio_mounted(0));
+ TEST_ASSERT_EQUAL_UINT8(0, tuh_audio_get_dev_addr(0));
+}
+
void test_audio_host_treats_implicit_feedback_as_audio_in_endpoint(void) {
open_descriptors(playback_with_implicit_feedback, sizeof(playback_with_implicit_feedback));
@@ -925,8 +1206,9 @@ void test_audio_host_keeps_supported_stream_when_other_as_format_is_unsupported(
}
void test_audio_host_rejects_sampling_frequency_range_and_releases_instance(void) {
- TEST_ASSERT_EQUAL_UINT16(0, audioh_open(0, AUDIO_DEV_ADDR, (const tusb_desc_interface_t *)playback_with_frequency_range,
- sizeof(playback_with_frequency_range)));
+ TEST_ASSERT_EQUAL_UINT16(0,
+ audioh_open(0, AUDIO_DEV_ADDR, (const tusb_desc_interface_t *)playback_with_frequency_range,
+ sizeof(playback_with_frequency_range)));
TEST_ASSERT_EQUAL_UINT8(0, tuh_audio_get_dev_addr(0));
open_descriptors(playback_with_explicit_feedback, sizeof(playback_with_explicit_feedback));
@@ -951,8 +1233,8 @@ void test_audio_host_rejects_malformed_descriptors_and_releases_instance(void) {
};
for (uint8_t i = 0; i < TU_ARRAY_SIZE(malformed); i++) {
- TEST_ASSERT_EQUAL_UINT16(
- 0, audioh_open(0, AUDIO_DEV_ADDR, (const tusb_desc_interface_t *)malformed[i].desc, malformed[i].len));
+ TEST_ASSERT_EQUAL_UINT16(0, audioh_open(0, AUDIO_DEV_ADDR, (const tusb_desc_interface_t *)malformed[i].desc,
+ malformed[i].len));
TEST_ASSERT_EQUAL_UINT8(0, tuh_audio_get_dev_addr(0));
}
}
@@ -1154,14 +1436,12 @@ void test_audio_host_schedules_44100_hz_fractional_packets_with_max_packets_only
while (edpt_xfer_count < 5) {
complete_edpt(0x01);
- TEST_ASSERT_TRUE(audioh_xfer_cb(AUDIO_DEV_ADDR, 0x01, XFER_RESULT_SUCCESS,
- edpt_xfer_bytes[edpt_xfer_count - 1]));
+ TEST_ASSERT_TRUE(audioh_xfer_cb(AUDIO_DEV_ADDR, 0x01, XFER_RESULT_SUCCESS, edpt_xfer_bytes[edpt_xfer_count - 1]));
}
TEST_ASSERT_EQUAL_UINT32(185, tuh_audio_write(0, 0, samples, 185));
while (edpt_xfer_count < 10) {
complete_edpt(0x01);
- TEST_ASSERT_TRUE(audioh_xfer_cb(AUDIO_DEV_ADDR, 0x01, XFER_RESULT_SUCCESS,
- edpt_xfer_bytes[edpt_xfer_count - 1]));
+ TEST_ASSERT_TRUE(audioh_xfer_cb(AUDIO_DEV_ADDR, 0x01, XFER_RESULT_SUCCESS, edpt_xfer_bytes[edpt_xfer_count - 1]));
}
for (uint8_t i = 0; i < 9; i++) {