summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiFiPHile <[email protected]>2026-08-27 06:21:18 +0200
committerHiFiPHile <[email protected]>2026-08-27 10:34:34 +0200
commitb23e33a1ce07297df2f37219861d78d2a4cc9809 (patch)
tree0e4d46110d8e82ef28e9836e3a18e5c622b15430
parentfa6283249362278b887364ae51f9d866fcb96ad3 (diff)
audio: expose generic Audio Control requests
Remove the narrow public Feature Unit request API while retaining managed mute and volume helpers. Expose validated Audio Control descriptors during enumeration and provide raw asynchronous and synchronous entity requests for advanced controls. Signed-off-by: HiFiPHile <[email protected]>
-rw-r--r--examples/host/audio_host/README.md15
-rw-r--r--examples/host/audio_host/src/audio_app.c24
-rw-r--r--src/class/audio/audio_host.c203
-rw-r--r--src/class/audio/audio_host.h61
-rw-r--r--test/unit-test/test/host/audio/test_audio_host.c197
5 files changed, 281 insertions, 219 deletions
diff --git a/examples/host/audio_host/README.md b/examples/host/audio_host/README.md
index 326b469e8..e7140bf67 100644
--- a/examples/host/audio_host/README.md
+++ b/examples/host/audio_host/README.md
@@ -68,7 +68,7 @@ make BOARD=<your_board> flash
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
+ - Print each stream's master mute/volume capabilities, cached volume range, and supported configurations when mounted
- Look for an S16_LE capture configuration at a preferred sample rate (48 kHz first, 44.1 kHz fallback; stereo preferred, mono accepted) and configure it
- Echo captured audio to an S16_LE playback configuration at the same sample rate (same channel count preferred, converted otherwise)
- Read/unmute the microphone and speaker Feature Units and set supported master volumes near -6 dB
@@ -81,25 +81,25 @@ make BOARD=<your_board> flash
TinyUSB Host USB Audio Example
Connect a USB Audio Device (UAC 1.0 or 2.0) to test
Audio device mounted: idx=0 addr=1
- capture stream 1 Feature Unit ID: 5, configurations: 2
+ capture stream 1, configurations: 2
master mute supported
master volume range: min=-23040 max=1536 res=256 (1/256 dB)
[0] format=1 rate=44100 channels=2
[1] format=1 rate=48000 channels=2
- playback stream 0 Feature Unit ID: 2, configurations: 2
+ playback stream 0, configurations: 2
master mute supported
master volume range: min=-23040 max=1536 res=256 (1/256 dB)
[0] format=1 rate=44100 channels=2
[1] format=1 rate=48000 channels=2
Configuring 48 kHz S16_LE capture (2 channels)
Microphone configured
- Microphone Feature Unit 5 master mute: off
- Microphone Feature Unit 5 master volume: 0 (1/256 dB)
+ Microphone master mute: off
+ Microphone master volume: 0 (1/256 dB)
Microphone master volume set: -1536 (1/256 dB)
Configuring 48 kHz S16_LE playback (2 channels)
Speaker configured
- Speaker Feature Unit 2 master mute: off
- Speaker Feature Unit 2 master volume: 0 (1/256 dB)
+ Speaker master mute: off
+ Speaker master volume: 0 (1/256 dB)
Speaker master volume set: -1536 (1/256 dB)
```
@@ -115,6 +115,7 @@ Edit `src/tusb_config.h` to modify:
## Notes
+- `tuh_audio_descriptor_cb()` exposes the validated Audio Control descriptor block during enumeration. Applications that need raw entity controls must copy the required entity IDs or descriptor fields before the callback returns, then use `tuh_audio_control_xfer()` after the device mounts.
- While a stream is running, the driver keeps one isochronous transfer in flight and re-submits on completion, so transfers follow the endpoint's `bInterval`. `tuh_audio_capture_cb()` / `tuh_audio_playback_cb()` report each completed transfer; `tuh_audio_err_cb()` reports failures. The example restarts the failed stream automatically 100 ms after the error callback.
- Capture and playback streams running concurrently in the same Audio Control instance must use the same sample rate.
- `tuh_audio_read()` / `tuh_audio_write()` are non-blocking FIFO operations: they return the number of whole frames actually queued/read (0 when the FIFO is empty/full or the stream is not running), and `tuh_audio_read_available()` / `tuh_audio_write_available()` report the FIFO occupancy in frames. `tuh_audio_write()` only queues data; the playback transfer-completion chain sends it, or sends silence when the FIFO does not contain a complete polling interval without consuming the partial data.
diff --git a/examples/host/audio_host/src/audio_app.c b/examples/host/audio_host/src/audio_app.c
index 4623dff5a..8852ca58c 100644
--- a/examples/host/audio_host/src/audio_app.c
+++ b/examples/host/audio_host/src/audio_app.c
@@ -360,11 +360,9 @@ void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
// Print all supported stream configurations
static void print_stream_configs(uint8_t idx, uint8_t stream_idx) {
- const tuh_audio_direction_t dir = tuh_audio_stream_direction(idx, stream_idx);
- const char *dir_name = (dir == TUH_AUDIO_STREAM_CAPTURE) ? "capture" : "playback";
- const uint8_t feature_unit_id = tuh_audio_get_feature_unit_id(idx, stream_idx);
- printf(" %s stream %u Feature Unit ID: %u, configurations: %u\r\n", dir_name, stream_idx, feature_unit_id,
- tuh_audio_config_count(idx, stream_idx));
+ const tuh_audio_direction_t dir = tuh_audio_stream_direction(idx, stream_idx);
+ const char *dir_name = (dir == TUH_AUDIO_STREAM_CAPTURE) ? "capture" : "playback";
+ printf(" %s stream %u, configurations: %u\r\n", dir_name, stream_idx, tuh_audio_config_count(idx, stream_idx));
tuh_audio_volume_range_t range;
if (tuh_audio_mute_supported(idx, stream_idx)) {
printf(" master mute supported\r\n");
@@ -383,17 +381,14 @@ static void print_stream_configs(uint8_t idx, uint8_t stream_idx) {
}
static void configure_stream_controls(uint8_t idx, uint8_t stream_idx, const char *stream_name) {
- const uint8_t feature_unit_id = tuh_audio_get_feature_unit_id(idx, stream_idx);
- if (feature_unit_id == 0) {
- printf(" %s stream has no master mute/volume Feature Unit\r\n", stream_name);
- return;
- }
+ bool has_control = false;
if (tuh_audio_mute_supported(idx, stream_idx)) {
+ has_control = true;
bool mute;
tusb_xfer_result_t result = tuh_audio_mute_get_sync(idx, stream_idx, &mute);
if (result == XFER_RESULT_SUCCESS) {
- printf(" %s Feature Unit %u master mute: %s\r\n", stream_name, feature_unit_id, mute ? "on" : "off");
+ printf(" %s master mute: %s\r\n", stream_name, mute ? "on" : "off");
result = tuh_audio_mute_set_sync(idx, stream_idx, false);
}
if (result != XFER_RESULT_SUCCESS) {
@@ -403,10 +398,11 @@ static void configure_stream_controls(uint8_t idx, uint8_t stream_idx, const cha
tuh_audio_volume_range_t range;
if (tuh_audio_volume_range_get(idx, stream_idx, &range)) {
+ has_control = true;
int16_t volume;
tusb_xfer_result_t result = tuh_audio_volume_get_sync(idx, stream_idx, &volume);
if (result == XFER_RESULT_SUCCESS) {
- printf(" %s Feature Unit %u master volume: %d (1/256 dB)\r\n", stream_name, feature_unit_id, volume);
+ printf(" %s master volume: %d (1/256 dB)\r\n", stream_name, volume);
int32_t target = FEATURE_UNIT_VOLUME_DB;
target = TU_MAX(target, range.min);
target = TU_MIN(target, range.max);
@@ -421,6 +417,10 @@ static void configure_stream_controls(uint8_t idx, uint8_t stream_idx, const cha
printf(" Accessing %s master volume failed: result=%u\r\n", stream_name, result);
}
}
+
+ if (!has_control) {
+ printf(" %s stream has no master mute/volume control\r\n", stream_name);
+ }
}
// Invoked when device with Audio interface is un-mounted
diff --git a/src/class/audio/audio_host.c b/src/class/audio/audio_host.c
index 370958aaf..432109022 100644
--- a/src/class/audio/audio_host.c
+++ b/src/class/audio/audio_host.c
@@ -229,6 +229,11 @@ CFG_TUH_MEM_SECTION static audioh_epbuf_t _audioh_epbuf[CFG_TUH_AUDIO_MAX];
// WEAK APPLICATION CALLBACKS
//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tuh_audio_descriptor_cb(uint8_t idx, const tuh_audio_descriptor_cb_t *desc_cb_data) {
+ (void)idx;
+ (void)desc_cb_data;
+}
+
TU_ATTR_WEAK void tuh_audio_mount_cb(uint8_t idx) {
(void)idx;
}
@@ -417,25 +422,6 @@ static uint32_t audioh_nominal_frames_q16(uint32_t sample_rate, uint8_t ep_inter
return (uint32_t)((numerator + 500000u) / 1000000u);
}
-// Feature Unit controls with variable or unknown width return zero.
-static uint8_t audioh_fu_control_width(uint8_t control_selector) {
- switch (control_selector) {
- case AUDIO10_FU_CTRL_MUTE:
- case AUDIO10_FU_CTRL_BASS:
- case AUDIO10_FU_CTRL_MID:
- case AUDIO10_FU_CTRL_TREBLE:
- case AUDIO10_FU_CTRL_AGC:
- case AUDIO10_FU_CTRL_BASS_BOOST:
- case AUDIO10_FU_CTRL_LOUDNESS:
- return 1;
- case AUDIO10_FU_CTRL_VOLUME:
- case AUDIO10_FU_CTRL_DELAY:
- return 2;
- default:
- return 0;
- }
-}
-
// Preserve the stream identity and FIFO allocation while clearing device state.
static void audioh_stream_reset(tuh_audio_stream_t *s) {
s->daddr = 0;
@@ -1577,6 +1563,13 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface
}
p_audio->stream_count = stream_idx;
+ const tuh_audio_descriptor_cb_t desc_cb_data = {
+ .desc_audio_control = desc_itf,
+ .desc_cs_audio_control = ac_desc.desc_start,
+ .desc_cs_audio_control_len = (uint16_t)(ac_desc.desc_end - ac_desc.desc_start),
+ };
+ tuh_audio_descriptor_cb(idx, &desc_cb_data);
+
return (uint16_t)((uintptr_t)p_desc - (uintptr_t)desc_start);
open_failed:
@@ -1810,16 +1803,6 @@ uint8_t tuh_audio_get_dev_addr(uint8_t idx) {
return _audioh_itf[idx].daddr;
}
-uint8_t tuh_audio_get_feature_unit_id(uint8_t idx, uint8_t stream_idx) {
- TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, 0);
- audioh_interface_t *p_audio = &_audioh_itf[idx];
- TU_VERIFY(p_audio->daddr != 0, 0);
-
- tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
- TU_VERIFY(s, 0);
- return s->feature_unit_id;
-}
-
bool tuh_audio_mute_supported(uint8_t idx, uint8_t stream_idx) {
TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false);
audioh_interface_t *p_audio = &_audioh_itf[idx];
@@ -2176,7 +2159,7 @@ uint32_t tuh_audio_read_available(uint8_t dev_idx, uint8_t stream_idx) {
}
//--------------------------------------------------------------------+
-// FEATURE UNIT CONTROLS
+// AUDIO CONTROL REQUESTS
//--------------------------------------------------------------------+
// Release driver-owned request state before invoking the application callback,
@@ -2196,12 +2179,11 @@ static void audioh_fu_set_complete(tuh_xfer_t *xfer) {
}
enum {
- AUDIOH_FU_VALUE_U16,
AUDIOH_FU_VALUE_BOOL,
AUDIOH_FU_VALUE_I16
};
-static uint8_t audioh_fu_cur_request(uint8_t protocol, tusb_dir_t direction) {
+static uint8_t audioh_control_cur_request(uint8_t protocol, tusb_dir_t direction) {
#if !(CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1)
(void)direction;
#endif
@@ -2219,30 +2201,60 @@ static uint8_t audioh_fu_cur_request(uint8_t protocol, tusb_dir_t direction) {
}
}
-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;
+static bool audioh_control_submit(uint8_t idx, uint8_t entity_id, tusb_dir_t direction, uint8_t request,
+ uint8_t control_selector, uint8_t channel, void *buffer, uint16_t length,
+ tuh_xfer_t *xfer) {
+ TU_VERIFY(idx < CFG_TUH_AUDIO_MAX && entity_id != 0 && request != 0, false);
+ TU_VERIFY(direction == TUSB_DIR_OUT || direction == TUSB_DIR_IN, false);
+ TU_VERIFY(buffer != NULL || length == 0, false);
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ TU_VERIFY(p_audio->mounted, false);
+
+ const tusb_control_request_t setup = {
+ .bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE, .type = TUSB_REQ_TYPE_CLASS, .direction = direction},
+ .bRequest = request,
+ .wValue = tu_htole16(tu_u16(control_selector, channel)),
+ .wIndex = tu_htole16(tu_u16(entity_id, p_audio->ac_itf_num)),
+ .wLength = tu_htole16(length),
+ };
+ xfer->daddr = p_audio->daddr;
+ xfer->ep_addr = 0;
+ xfer->setup = &setup;
+ xfer->buffer = buffer;
+ return tuh_control_xfer(xfer);
+}
+
+bool tuh_audio_control_xfer(uint8_t idx, uint8_t entity_id, tusb_dir_t direction, uint8_t request,
+ uint8_t control_selector, uint8_t channel, void *buffer, uint16_t length,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
+ tuh_xfer_t xfer = {.complete_cb = complete_cb, .user_data = user_data};
+ return audioh_control_submit(idx, entity_id, direction, request, control_selector, channel, buffer, length, &xfer);
+}
+
+tusb_xfer_result_t tuh_audio_control_xfer_sync(uint8_t idx, uint8_t entity_id, tusb_dir_t direction, uint8_t request,
+ uint8_t control_selector, uint8_t channel, void *buffer, uint16_t length,
+ uint32_t *actual_len) {
+ if (actual_len != NULL) {
+ *actual_len = 0;
}
- #else
- (void)protocol;
- (void)control_selector;
- #endif
- return true;
+
+ tuh_xfer_t xfer = {0};
+ if (!audioh_control_submit(idx, entity_id, direction, request, control_selector, channel, buffer, length, &xfer)) {
+ return XFER_RESULT_TIMEOUT;
+ }
+
+ if (actual_len != NULL) {
+ *actual_len = xfer.actual_len;
+ }
+ return xfer.result;
}
static void audioh_fu_value_store(audioh_ctrl_state_t *ctrl, audioh_epbuf_t *epbuf) {
if (ctrl->fu.control.value_type == AUDIOH_FU_VALUE_BOOL) {
*((bool *)ctrl->value) = audioh_fu_ctrl(epbuf)[0] != 0;
- } else if (ctrl->fu.control.width == 1) {
- *((uint16_t *)ctrl->value) = audioh_fu_ctrl(epbuf)[0];
} else {
- const uint16_t value = tu_le16toh(tu_unaligned_read16(audioh_fu_ctrl(epbuf)));
- if (ctrl->fu.control.value_type == AUDIOH_FU_VALUE_I16) {
- *((int16_t *)ctrl->value) = (int16_t)value;
- } else {
- *((uint16_t *)ctrl->value) = value;
- }
+ const uint16_t value = tu_le16toh(tu_unaligned_read16(audioh_fu_ctrl(epbuf)));
+ *((int16_t *)ctrl->value) = (int16_t)value;
}
}
@@ -2317,11 +2329,12 @@ static bool audioh_mount_feature_unit_submit(uint8_t idx) {
tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, ctrl->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 = {
+ const bool uac2 = p_audio->protocol == AUDIO_INT_PROTOCOL_CODE_V2;
+ const uint8_t selector = uac2 ? AUDIO20_FU_CTRL_VOLUME : AUDIO10_FU_CTRL_VOLUME;
+ const tusb_control_request_t request = {
.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN},
.bRequest = uac2 ? AUDIO20_CS_REQ_RANGE : audioh_fu_volume_range_request(ctrl->fu.mount.range_step),
- .wValue = tu_htole16(tu_u16(AUDIO10_FU_CTRL_VOLUME, 0)),
+ .wValue = tu_htole16(tu_u16(selector, 0)),
.wIndex = tu_htole16(tu_u16(s->feature_unit_id, p_audio->ac_itf_num)),
.wLength = tu_htole16(uac2 ? 8u : 2u),
};
@@ -2411,24 +2424,20 @@ static void audioh_mount_feature_unit_complete(tuh_xfer_t *xfer) {
audioh_mount_feature_unit_next(idx);
}
-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) {
+static bool audioh_fu_set(uint8_t idx, uint8_t stream_idx, uint8_t control_selector, uint16_t value, uint8_t width,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false);
audioh_interface_t *p_audio = &_audioh_itf[idx];
TU_VERIFY(p_audio->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);
+ const uint8_t request_code = audioh_control_cur_request(p_audio->protocol, TUSB_DIR_OUT);
TU_VERIFY(request_code != 0, false);
audioh_ctrl_state_t *ctrl = &p_audio->ctrl;
audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
@@ -2436,29 +2445,17 @@ bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t stream_idx, uint8_t control
// Reserve both bookkeeping and payload storage before populating the request.
ctrl->fu_busy = true;
- const tusb_control_request_t request = {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE,
- .type = TUSB_REQ_TYPE_CLASS,
- .direction = TUSB_DIR_OUT},
- .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};
-
uint8_t *val_buf = audioh_fu_ctrl(epbuf);
val_buf[0] = (uint8_t)(value & 0xFF);
if (width == 2) {
val_buf[1] = (uint8_t)((value >> 8) & 0xFF);
}
- tuh_xfer_t xfer = {.daddr = p_audio->daddr,
- .ep_addr = 0,
- .setup = &request,
- .buffer = val_buf,
- .complete_cb = complete_cb,
- .user_data = user_data};
+ tuh_xfer_t xfer = {.complete_cb = complete_cb, .user_data = user_data};
if (complete_cb == NULL) {
- const bool result = tuh_control_xfer(&xfer);
+ const bool result = audioh_control_submit(idx, s->feature_unit_id, TUSB_DIR_OUT, request_code, control_selector, 0,
+ val_buf, width, &xfer);
ctrl->fu_busy = false;
return result;
}
@@ -2468,7 +2465,8 @@ bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t stream_idx, uint8_t control
xfer.complete_cb = audioh_fu_set_complete;
xfer.user_data = (uintptr_t)idx;
- if (!tuh_control_xfer(&xfer)) {
+ if (!audioh_control_submit(idx, s->feature_unit_id, TUSB_DIR_OUT, request_code, control_selector, 0, val_buf, width,
+ &xfer)) {
ctrl->complete_cb = NULL;
ctrl->fu_busy = false;
return false;
@@ -2476,24 +2474,20 @@ bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t stream_idx, uint8_t control
return true;
}
-static bool audioh_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t control_selector, uint8_t channel,
- void *value, uint8_t value_type, tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
+static bool audioh_fu_get(uint8_t idx, uint8_t stream_idx, uint8_t control_selector, void *value, uint8_t width,
+ uint8_t value_type, tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false);
audioh_interface_t *p_audio = &_audioh_itf[idx];
TU_VERIFY(p_audio->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);
+ const uint8_t request_code = audioh_control_cur_request(p_audio->protocol, TUSB_DIR_IN);
TU_VERIFY(request_code != 0, false);
audioh_ctrl_state_t *ctrl = &p_audio->ctrl;
audioh_epbuf_t *epbuf = &_audioh_epbuf[idx];
@@ -2503,24 +2497,12 @@ static bool audioh_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t con
ctrl->fu.control.width = width;
ctrl->fu.control.value_type = value_type;
- const tusb_control_request_t request = {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE,
- .type = TUSB_REQ_TYPE_CLASS,
- .direction = TUSB_DIR_IN},
- .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};
-
if (complete_cb == NULL) {
// The synchronous transfer completes before its driver-owned response is
// converted to host order.
- tuh_xfer_t xfer = {.daddr = p_audio->daddr,
- .ep_addr = 0,
- .setup = &request,
- .buffer = audioh_fu_ctrl(epbuf),
- .complete_cb = NULL,
- .user_data = user_data};
- if (!tuh_control_xfer(&xfer)) {
+ tuh_xfer_t xfer = {.complete_cb = NULL, .user_data = user_data};
+ if (!audioh_control_submit(idx, s->feature_unit_id, TUSB_DIR_IN, request_code, control_selector, 0,
+ audioh_fu_ctrl(epbuf), width, &xfer)) {
ctrl->fu_busy = false;
return false;
}
@@ -2536,14 +2518,10 @@ static bool audioh_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t con
// The asynchronous wrapper converts the response before calling the application.
ctrl->complete_cb = complete_cb;
ctrl->user_data = user_data;
- tuh_xfer_t xfer = {.daddr = p_audio->daddr,
- .ep_addr = 0,
- .setup = &request,
- .buffer = audioh_fu_ctrl(epbuf),
- .complete_cb = audioh_fu_get_complete,
- .user_data = (uintptr_t)idx};
+ tuh_xfer_t xfer = {.complete_cb = audioh_fu_get_complete, .user_data = (uintptr_t)idx};
- if (!tuh_control_xfer(&xfer)) {
+ if (!audioh_control_submit(idx, s->feature_unit_id, TUSB_DIR_IN, request_code, control_selector, 0,
+ audioh_fu_ctrl(epbuf), width, &xfer)) {
ctrl->complete_cb = NULL;
ctrl->fu_busy = false;
return false;
@@ -2551,21 +2529,14 @@ static bool audioh_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t con
return true;
}
-bool tuh_audio_feature_unit_get(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) {
- return audioh_feature_unit_get(idx, stream_idx, control_selector, channel, value, AUDIOH_FU_VALUE_U16, complete_cb,
- user_data);
-}
-
bool tuh_audio_mute_set(uint8_t idx, uint8_t stream_idx, bool mute, tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
TU_VERIFY(tuh_audio_mute_supported(idx, stream_idx), false);
- return tuh_audio_feature_unit_set(idx, stream_idx, AUDIO10_FU_CTRL_MUTE, 0, mute ? 1 : 0, complete_cb, user_data);
+ return audioh_fu_set(idx, stream_idx, AUDIO10_FU_CTRL_MUTE, mute ? 1 : 0, 1, complete_cb, user_data);
}
bool tuh_audio_mute_get(uint8_t idx, uint8_t stream_idx, bool *mute, tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
TU_VERIFY(mute != NULL && tuh_audio_mute_supported(idx, stream_idx), false);
- return audioh_feature_unit_get(idx, stream_idx, AUDIO10_FU_CTRL_MUTE, 0, mute, AUDIOH_FU_VALUE_BOOL, complete_cb,
- user_data);
+ return audioh_fu_get(idx, stream_idx, AUDIO10_FU_CTRL_MUTE, mute, 1, AUDIOH_FU_VALUE_BOOL, complete_cb, user_data);
}
bool tuh_audio_volume_set(uint8_t idx, uint8_t stream_idx, int16_t volume, tuh_xfer_cb_t complete_cb,
@@ -2582,16 +2553,14 @@ bool tuh_audio_volume_set(uint8_t idx, uint8_t stream_idx, int16_t volume, tuh_x
}
volume = (int16_t)rounded;
}
- return tuh_audio_feature_unit_set(idx, stream_idx, AUDIO10_FU_CTRL_VOLUME, 0, (uint16_t)volume, complete_cb,
- user_data);
+ return audioh_fu_set(idx, stream_idx, AUDIO10_FU_CTRL_VOLUME, (uint16_t)volume, 2, complete_cb, user_data);
}
bool tuh_audio_volume_get(uint8_t idx, uint8_t stream_idx, int16_t *volume, tuh_xfer_cb_t complete_cb,
uintptr_t user_data) {
tuh_audio_volume_range_t range;
TU_VERIFY(volume != NULL && tuh_audio_volume_range_get(idx, stream_idx, &range), false);
- return audioh_feature_unit_get(idx, stream_idx, AUDIO10_FU_CTRL_VOLUME, 0, volume, AUDIOH_FU_VALUE_I16, complete_cb,
- user_data);
+ return audioh_fu_get(idx, stream_idx, AUDIO10_FU_CTRL_VOLUME, volume, 2, AUDIOH_FU_VALUE_I16, complete_cb, user_data);
}
#endif
diff --git a/src/class/audio/audio_host.h b/src/class/audio/audio_host.h
index 4807d376a..2e778c184 100644
--- a/src/class/audio/audio_host.h
+++ b/src/class/audio/audio_host.h
@@ -115,6 +115,14 @@ typedef struct {
uint16_t res;
} tuh_audio_volume_range_t;
+// Audio Control descriptors reported during enumeration. Descriptor pointers
+// are valid only for the duration of tuh_audio_descriptor_cb().
+typedef struct {
+ const tusb_desc_interface_t *desc_audio_control;
+ const uint8_t *desc_cs_audio_control;
+ uint16_t desc_cs_audio_control_len;
+} tuh_audio_descriptor_cb_t;
+
//--------------------------------------------------------------------+
// Stream Enumeration
//--------------------------------------------------------------------+
@@ -212,8 +220,6 @@ static inline uint32_t tuh_audio_config_frame_size(const tuh_audio_stream_config
bool tuh_audio_mounted(uint8_t idx);
// Get device address of Audio device
uint8_t tuh_audio_get_dev_addr(uint8_t idx);
-// Get the Feature Unit ID associated with a stream (0 = none)
-uint8_t tuh_audio_get_feature_unit_id(uint8_t idx, uint8_t stream_idx);
// True when the stream's Feature Unit supports master mute control.
bool tuh_audio_mute_supported(uint8_t idx, uint8_t stream_idx);
// Get the cached master volume range. Returns false when volume is unsupported.
@@ -223,22 +229,13 @@ bool tuh_audio_volume_range_get(uint8_t idx, uint8_t stream_idx, tuh_audio_volum
// Control Request API
//--------------------------------------------------------------------+
-// 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 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.
-bool tuh_audio_feature_unit_get(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);
+// Submit a class-specific request to an entity on the Audio Control interface.
+// request is the protocol-specific UAC request code. buffer contains the raw
+// little-endian control payload. For an asynchronous transfer, buffer must
+// remain valid until complete_cb is invoked.
+bool tuh_audio_control_xfer(uint8_t idx, uint8_t entity_id, tusb_dir_t direction, uint8_t request,
+ uint8_t control_selector, uint8_t channel, void *buffer, uint16_t length,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data);
// Master mute and volume controls. Capability and range information is cached
// before tuh_audio_mount_cb() is invoked. Volume SET accepts
@@ -252,23 +249,12 @@ bool tuh_audio_volume_get(uint8_t idx, uint8_t stream_idx, int16_t *volume, tuh_
uintptr_t user_data);
//--------------------------------------------------------------------+
-// Control Request Sync API
-// Each Function will make a USB control transfer request to/from device the function will block until request is
-// complete. The function will return the transfer request result
+// Synchronous control requests block until the transfer completes and return
+// its result. actual_len may be NULL when the received length is not needed.
//--------------------------------------------------------------------+
-TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_audio_feature_unit_set_sync(uint8_t idx, uint8_t stream_idx,
- uint8_t control_selector,
- uint8_t channel,
- uint16_t value) {
- TU_API_SYNC(tuh_audio_feature_unit_set, idx, stream_idx, control_selector, channel, value);
-}
-
-TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_audio_feature_unit_get_sync(uint8_t idx, uint8_t stream_idx,
- uint8_t control_selector,
- uint8_t channel,
- uint16_t *value) {
- TU_API_SYNC(tuh_audio_feature_unit_get, idx, stream_idx, control_selector, channel, value);
-}
+tusb_xfer_result_t tuh_audio_control_xfer_sync(uint8_t idx, uint8_t entity_id, tusb_dir_t direction, uint8_t request,
+ uint8_t control_selector, uint8_t channel, void *buffer, uint16_t length,
+ uint32_t *actual_len);
TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_audio_mute_set_sync(uint8_t idx, uint8_t stream_idx,
bool mute) {
@@ -294,6 +280,13 @@ TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_audio_volume_get_sync
// Callbacks (Weak is optional)
//--------------------------------------------------------------------+
+// Invoked after the Audio Control and Streaming descriptors have been
+// validated during enumeration, before tuh_audio_mount_cb(). The interface is
+// not mounted yet and control requests must not be submitted from this
+// callback. Applications may inspect or copy descriptors needed for later raw
+// entity control requests.
+void tuh_audio_descriptor_cb(uint8_t idx, const tuh_audio_descriptor_cb_t *desc_cb_data);
+
// Invoked when device with Audio interface is mounted
void tuh_audio_mount_cb(uint8_t idx);
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 9f8957984..2ede6b662 100644
--- a/test/unit-test/test/host/audio/test_audio_host.c
+++ b/test/unit-test/test/host/audio/test_audio_host.c
@@ -24,13 +24,13 @@ enum {
CAPTURE_FU = 12,
CAPTURE_OUTPUT_TERM = 13,
- PLAYBACK_CLOCK = 10,
- UNRELATED_CLOCK_0 = 20,
- UNRELATED_CLOCK_1 = 21,
- UNRELATED_TERM_0 = 22,
- UNRELATED_TERM_1 = 23,
- UNRELATED_FU_0 = 24,
- UNRELATED_FU_1 = 25,
+ PLAYBACK_CLOCK = 10,
+ UNRELATED_CLOCK_0 = 20,
+ UNRELATED_CLOCK_1 = 21,
+ UNRELATED_TERM_0 = 22,
+ UNRELATED_TERM_1 = 23,
+ UNRELATED_FU_0 = 24,
+ UNRELATED_FU_1 = 25,
};
static bool interface_set_result;
@@ -43,6 +43,7 @@ static tuh_xfer_t control_xfer;
static tusb_control_request_t control_request;
static uint8_t control_buffer[8];
static uint8_t control_xfer_count;
+static uint32_t control_sync_actual_len;
static bool edpt_open_result;
static tusb_desc_endpoint_t opened_ep[4];
@@ -67,6 +68,13 @@ static uint8_t err_cb_idx;
static uint8_t err_cb_stream_idx;
static uint16_t err_cb_xferred_bytes;
+static uint8_t descriptor_cb_count;
+static uint8_t descriptor_cb_idx;
+static uint8_t descriptor_cb_protocol;
+static uint8_t descriptor_cb_ac_itf;
+static uint16_t descriptor_cb_cs_len;
+static bool descriptor_cb_has_playback_fu;
+
static uint32_t edpt_mask(uint8_t ep_addr) {
const uint8_t bit = tu_edpt_number(ep_addr) + (tu_edpt_dir(ep_addr) == TUSB_DIR_IN ? 16 : 0);
return 1u << bit;
@@ -83,6 +91,27 @@ void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
err_cb_xferred_bytes = xferred_bytes;
}
+void tuh_audio_descriptor_cb(uint8_t idx, const tuh_audio_descriptor_cb_t *desc_cb) {
+ descriptor_cb_count++;
+ descriptor_cb_idx = idx;
+ descriptor_cb_protocol = desc_cb->desc_audio_control->bInterfaceProtocol;
+ descriptor_cb_ac_itf = desc_cb->desc_audio_control->bInterfaceNumber;
+ descriptor_cb_cs_len = desc_cb->desc_cs_audio_control_len;
+
+ const uint8_t *p_desc = desc_cb->desc_cs_audio_control;
+ const uint8_t *end = p_desc + desc_cb->desc_cs_audio_control_len;
+ while (p_desc < end && tu_desc_len(p_desc) >= 4 && tu_desc_len(p_desc) <= (uint16_t)(end - p_desc)) {
+ const uint8_t fu_subtype = (descriptor_cb_protocol == AUDIO_INT_PROTOCOL_CODE_V2)
+ ? AUDIO20_CS_AC_INTERFACE_FEATURE_UNIT
+ : AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT;
+ if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == fu_subtype &&
+ p_desc[3] == PLAYBACK_FU) {
+ descriptor_cb_has_playback_fu = true;
+ }
+ p_desc = tu_desc_next(p_desc);
+ }
+}
+
tusb_speed_t tuh_speed_get(uint8_t daddr) {
(void)daddr;
return test_speed;
@@ -96,7 +125,8 @@ bool tuh_control_xfer(tuh_xfer_t *xfer) {
if (xfer->buffer != NULL) {
memcpy(control_buffer, xfer->buffer, TU_MIN(sizeof(control_buffer), control_request.wLength));
}
- xfer->result = XFER_RESULT_SUCCESS;
+ xfer->result = XFER_RESULT_SUCCESS;
+ xfer->actual_len = control_sync_actual_len;
return control_xfer_result;
}
@@ -844,7 +874,8 @@ void setUp(void) {
memset(&control_xfer, 0, sizeof(control_xfer));
memset(&control_request, 0, sizeof(control_request));
memset(control_buffer, 0, sizeof(control_buffer));
- control_xfer_count = 0;
+ control_xfer_count = 0;
+ control_sync_actual_len = 0;
edpt_open_result = true;
memset(opened_ep, 0, sizeof(opened_ep));
@@ -867,6 +898,13 @@ void setUp(void) {
err_cb_stream_idx = TUSB_INDEX_INVALID_8;
err_cb_xferred_bytes = 0;
+ descriptor_cb_count = 0;
+ descriptor_cb_idx = TUSB_INDEX_INVALID_8;
+ descriptor_cb_protocol = 0;
+ descriptor_cb_ac_itf = TUSB_INDEX_INVALID_8;
+ descriptor_cb_cs_len = 0;
+ descriptor_cb_has_playback_fu = false;
+
fu_cb_count = 0;
fu_cb_user_data = 0;
@@ -881,9 +919,7 @@ static void open_descriptors(const uint8_t *desc, uint16_t desc_len) {
TEST_ASSERT_EQUAL_UINT16(desc_len, audioh_open(0, AUDIO_DEV_ADDR, (const tusb_desc_interface_t *)desc, desc_len));
}
-static void mount_descriptors(const uint8_t *desc, uint16_t desc_len) {
- open_descriptors(desc, desc_len);
- TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+static void complete_mount(void) {
while (!tuh_audio_mounted(0)) {
switch (control_request.bRequest) {
case AUDIO10_CS_REQ_GET_MIN:
@@ -904,6 +940,12 @@ static void mount_descriptors(const uint8_t *desc, uint16_t desc_len) {
control_xfer_count = 0;
}
+static void mount_descriptors(const uint8_t *desc, uint16_t desc_len) {
+ open_descriptors(desc, desc_len);
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+ complete_mount();
+}
+
void test_audio_host_rejects_midi1_collection_without_consuming_instance(void) {
TEST_ASSERT_EQUAL_UINT16(0, audioh_open(0, AUDIO_DEV_ADDR, (const tusb_desc_interface_t *)midi1_only_collection,
sizeof(midi1_only_collection)));
@@ -916,6 +958,18 @@ void test_audio_host_rejects_midi2_collection_without_consuming_instance(void) {
TEST_ASSERT_EQUAL_UINT8(0, tuh_audio_get_dev_addr(0));
}
+void test_audio_host_exposes_audio_control_descriptors_during_enumeration(void) {
+ open_descriptors(playback_fu_before_terminal, sizeof(playback_fu_before_terminal));
+
+ TEST_ASSERT_EQUAL_UINT8(1, descriptor_cb_count);
+ TEST_ASSERT_EQUAL_UINT8(0, descriptor_cb_idx);
+ TEST_ASSERT_EQUAL_UINT8(AUDIO_INT_PROTOCOL_CODE_V1, descriptor_cb_protocol);
+ TEST_ASSERT_EQUAL_UINT8(AUDIO_AC_ITF, descriptor_cb_ac_itf);
+ TEST_ASSERT_GREATER_THAN_UINT16(0, descriptor_cb_cs_len);
+ TEST_ASSERT_TRUE(descriptor_cb_has_playback_fu);
+ TEST_ASSERT_FALSE(tuh_audio_mounted(0));
+}
+
void test_audio_host_saves_and_opens_explicit_feedback_endpoint(void) {
mount_descriptors(playback_with_explicit_feedback, sizeof(playback_with_explicit_feedback));
@@ -940,6 +994,8 @@ void test_audio_host_rejects_uac2_interface_without_consuming_instance(void) {
void test_audio_host_mounts_uac2_and_sets_clock_before_activating_stream(void) {
open_descriptors(uac2_playback, sizeof(uac2_playback));
+ TEST_ASSERT_EQUAL_UINT8(AUDIO_INT_PROTOCOL_CODE_V2, descriptor_cb_protocol);
+ TEST_ASSERT_TRUE(descriptor_cb_has_playback_fu);
TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
TEST_ASSERT_FALSE(tuh_audio_mounted(0));
@@ -959,7 +1015,6 @@ void test_audio_host_mounts_uac2_and_sets_clock_before_activating_stream(void) {
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));
@@ -999,6 +1054,31 @@ void test_audio_host_mounts_uac2_and_sets_clock_before_activating_stream(void) {
TEST_ASSERT_EQUAL_UINT16(192, edpt_xfer_bytes[0]);
}
+void test_audio_host_generic_control_request_supports_uac2_clock_entity(void) {
+ uint8_t clock_valid = 0;
+ open_descriptors(uac2_playback, sizeof(uac2_playback));
+ 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);
+
+ control_xfer_count = 0;
+ TEST_ASSERT_TRUE(tuh_audio_control_xfer(0, PLAYBACK_CLOCK, TUSB_DIR_IN, AUDIO20_CS_REQ_CUR, AUDIO20_CS_CTRL_CLK_VALID,
+ 0, &clock_valid, sizeof(clock_valid), feature_unit_complete, 77));
+ TEST_ASSERT_EQUAL_UINT8(1, control_xfer_count);
+ TEST_ASSERT_EQUAL(TUSB_DIR_IN, control_request.bmRequestType_bit.direction);
+ TEST_ASSERT_EQUAL_UINT8(AUDIO20_CS_REQ_CUR, control_request.bRequest);
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(AUDIO20_CS_CTRL_CLK_VALID, 0), tu_le16toh(control_request.wValue));
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(PLAYBACK_CLOCK, AUDIO_AC_ITF), tu_le16toh(control_request.wIndex));
+ TEST_ASSERT_EQUAL_UINT16(1, tu_le16toh(control_request.wLength));
+
+ control_xfer.buffer[0] = 1;
+ control_xfer.actual_len = 1;
+ complete_control_xfer(XFER_RESULT_SUCCESS);
+ TEST_ASSERT_EQUAL_UINT8(1, fu_cb_count);
+ TEST_ASSERT_EQUAL_UINT32(77, fu_cb_user_data);
+ TEST_ASSERT_EQUAL_UINT8(1, clock_valid);
+}
+
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));
@@ -1138,23 +1218,28 @@ void test_audio_host_ignores_second_data_endpoint_in_same_as_interface(void) {
void test_audio_host_maps_playback_fu_declared_before_usb_input_terminal(void) {
open_descriptors(playback_fu_before_terminal, sizeof(playback_fu_before_terminal));
- TEST_ASSERT_EQUAL_UINT8(PLAYBACK_FU, tuh_audio_get_feature_unit_id(0, 0));
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(PLAYBACK_FU, AUDIO_AC_ITF), tu_le16toh(control_request.wIndex));
}
void test_audio_host_maps_relevant_terminal_and_fu_after_unrelated_entities(void) {
open_descriptors(playback_after_unrelated_ac_entities, sizeof(playback_after_unrelated_ac_entities));
TEST_ASSERT_EQUAL_UINT8(1, tuh_audio_stream_count(0));
- TEST_ASSERT_EQUAL_UINT8(PLAYBACK_FU, tuh_audio_get_feature_unit_id(0, 0));
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(PLAYBACK_FU, AUDIO_AC_ITF), tu_le16toh(control_request.wIndex));
}
void test_audio_host_maps_capture_fu_declared_before_usb_output_terminal(void) {
uint8_t captured[CFG_TUH_AUDIO_STREAM_BUFSIZE];
const uint16_t fifo_depth = CFG_TUH_AUDIO_STREAM_BUFSIZE - (CFG_TUH_AUDIO_STREAM_BUFSIZE % 3);
- mount_descriptors(capture_fu_before_usb_output, sizeof(capture_fu_before_usb_output));
+ open_descriptors(capture_fu_before_usb_output, sizeof(capture_fu_before_usb_output));
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(CAPTURE_FU, AUDIO_AC_ITF), tu_le16toh(control_request.wIndex));
+ complete_mount();
TEST_ASSERT_EQUAL(TUH_AUDIO_STREAM_CAPTURE, tuh_audio_stream_direction(0, 0));
- TEST_ASSERT_EQUAL_UINT8(CAPTURE_FU, tuh_audio_get_feature_unit_id(0, 0));
+ TEST_ASSERT_TRUE(tuh_audio_mute_supported(0, 0));
TEST_ASSERT_TRUE(tuh_audio_configure(0, 0, 0));
TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
@@ -1183,8 +1268,12 @@ void test_audio_host_maps_duplex_fus_declared_before_usb_terminals(void) {
open_descriptors(duplex_fus_before_usb_terminals, sizeof(duplex_fus_before_usb_terminals));
TEST_ASSERT_EQUAL_UINT8(2, tuh_audio_stream_count(0));
- TEST_ASSERT_EQUAL_UINT8(PLAYBACK_FU, tuh_audio_get_feature_unit_id(0, 0));
- TEST_ASSERT_EQUAL_UINT8(CAPTURE_FU, tuh_audio_get_feature_unit_id(0, 1));
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(PLAYBACK_FU, AUDIO_AC_ITF), tu_le16toh(control_request.wIndex));
+ complete_control_xfer_with_u16((uint16_t)(-90 * 256));
+ complete_control_xfer_with_u16(6 * 256);
+ complete_control_xfer_with_u16(256);
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(CAPTURE_FU, AUDIO_AC_ITF), tu_le16toh(control_request.wIndex));
}
void test_audio_host_reads_volume_ranges_for_both_streams_before_mount(void) {
@@ -1422,40 +1511,51 @@ void test_audio_host_closes_old_endpoint_and_cleans_up_failed_reconfiguration(vo
TEST_ASSERT_EQUAL_UINT8(TUSB_INDEX_INVALID_8, tuh_audio_active_config(0, 0));
}
-void test_audio_host_uses_correct_feature_unit_widths_and_serializes_requests(void) {
- static const uint8_t one_byte_controls[] = {
- AUDIO10_FU_CTRL_MUTE, AUDIO10_FU_CTRL_BASS, AUDIO10_FU_CTRL_MID, AUDIO10_FU_CTRL_TREBLE,
- AUDIO10_FU_CTRL_AGC, AUDIO10_FU_CTRL_BASS_BOOST, AUDIO10_FU_CTRL_LOUDNESS,
- };
- static const uint8_t two_byte_controls[] = {AUDIO10_FU_CTRL_VOLUME, AUDIO10_FU_CTRL_DELAY};
-
+void test_audio_host_submits_generic_entity_control_request(void) {
+ uint8_t payload[] = {0x34, 0x12, 0x56};
mount_descriptors(playback_fu_before_terminal, sizeof(playback_fu_before_terminal));
- for (uint8_t i = 0; i < TU_ARRAY_SIZE(one_byte_controls); i++) {
- TEST_ASSERT_TRUE(tuh_audio_feature_unit_set(0, 0, one_byte_controls[i], 0, 0x1234, NULL, 0));
- TEST_ASSERT_EQUAL_UINT16(1, tu_le16toh(control_request.wLength));
- TEST_ASSERT_EQUAL_HEX8(0x34, control_buffer[0]);
- }
- for (uint8_t i = 0; i < TU_ARRAY_SIZE(two_byte_controls); i++) {
- TEST_ASSERT_TRUE(tuh_audio_feature_unit_set(0, 0, two_byte_controls[i], 0, 0x1234, NULL, 0));
- TEST_ASSERT_EQUAL_UINT16(2, tu_le16toh(control_request.wLength));
- TEST_ASSERT_EQUAL_HEX8(0x34, control_buffer[0]);
- TEST_ASSERT_EQUAL_HEX8(0x12, control_buffer[1]);
- }
- TEST_ASSERT_FALSE(tuh_audio_feature_unit_set(0, 0, AUDIO10_FU_CTRL_GRAPHIC_EQUALIZER, 0, 0, NULL, 0));
-
- const uint8_t previous_xfer_count = control_xfer_count;
- TEST_ASSERT_TRUE(tuh_audio_feature_unit_set(0, 0, AUDIO10_FU_CTRL_MUTE, 0, 1, feature_unit_complete, 0x1234));
- TEST_ASSERT_FALSE(tuh_audio_feature_unit_set(0, 0, AUDIO10_FU_CTRL_VOLUME, 0, 2, feature_unit_complete, 0x5678));
- TEST_ASSERT_EQUAL_UINT8(previous_xfer_count + 1, control_xfer_count);
+ TEST_ASSERT_TRUE(tuh_audio_control_xfer(0, PLAYBACK_FU, TUSB_DIR_OUT, AUDIO10_CS_REQ_SET_CUR,
+ AUDIO10_FU_CTRL_GRAPHIC_EQUALIZER, 2, payload, sizeof(payload),
+ feature_unit_complete, 0x1234));
+ TEST_ASSERT_EQUAL(TUSB_DIR_OUT, control_request.bmRequestType_bit.direction);
+ TEST_ASSERT_EQUAL(TUSB_REQ_TYPE_CLASS, control_request.bmRequestType_bit.type);
+ TEST_ASSERT_EQUAL(TUSB_REQ_RCPT_INTERFACE, control_request.bmRequestType_bit.recipient);
+ TEST_ASSERT_EQUAL_HEX8(AUDIO10_CS_REQ_SET_CUR, control_request.bRequest);
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(AUDIO10_FU_CTRL_GRAPHIC_EQUALIZER, 2), tu_le16toh(control_request.wValue));
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(PLAYBACK_FU, AUDIO_AC_ITF), tu_le16toh(control_request.wIndex));
+ TEST_ASSERT_EQUAL_UINT16(sizeof(payload), tu_le16toh(control_request.wLength));
+ TEST_ASSERT_EQUAL_HEX8_ARRAY(payload, control_buffer, sizeof(payload));
complete_control_xfer(XFER_RESULT_SUCCESS);
TEST_ASSERT_EQUAL_UINT8(1, fu_cb_count);
TEST_ASSERT_EQUAL_HEX32(0x1234, fu_cb_user_data);
- TEST_ASSERT_TRUE(tuh_audio_feature_unit_set(0, 0, AUDIO10_FU_CTRL_VOLUME, 0, 2, feature_unit_complete, 0x5678));
- complete_control_xfer(XFER_RESULT_SUCCESS);
- TEST_ASSERT_EQUAL_UINT8(2, fu_cb_count);
- TEST_ASSERT_EQUAL_HEX32(0x5678, fu_cb_user_data);
+
+ TEST_ASSERT_FALSE(tuh_audio_control_xfer(0, 0, TUSB_DIR_OUT, AUDIO10_CS_REQ_SET_CUR, AUDIO10_FU_CTRL_MUTE, 0, payload,
+ 1, NULL, 0));
+ TEST_ASSERT_FALSE(tuh_audio_control_xfer(0, PLAYBACK_FU, TUSB_DIR_OUT, AUDIO10_CS_REQ_SET_CUR, AUDIO10_FU_CTRL_MUTE,
+ 0, NULL, 1, NULL, 0));
+}
+
+void test_audio_host_sync_entity_control_request_returns_actual_length(void) {
+ uint8_t payload[8] = {0};
+ uint32_t actual_len = UINT32_MAX;
+ mount_descriptors(playback_fu_before_terminal, sizeof(playback_fu_before_terminal));
+
+ control_sync_actual_len = 3;
+ TEST_ASSERT_EQUAL(XFER_RESULT_SUCCESS,
+ tuh_audio_control_xfer_sync(0, PLAYBACK_FU, TUSB_DIR_IN, AUDIO10_CS_REQ_GET_CUR,
+ AUDIO10_FU_CTRL_GRAPHIC_EQUALIZER, 0, payload, sizeof(payload),
+ &actual_len));
+ TEST_ASSERT_EQUAL_UINT32(3, actual_len);
+
+ control_xfer_result = false;
+ actual_len = UINT32_MAX;
+ TEST_ASSERT_EQUAL(XFER_RESULT_TIMEOUT,
+ tuh_audio_control_xfer_sync(0, PLAYBACK_FU, TUSB_DIR_IN, AUDIO10_CS_REQ_GET_CUR,
+ AUDIO10_FU_CTRL_GRAPHIC_EQUALIZER, 0, payload, sizeof(payload),
+ &actual_len));
+ TEST_ASSERT_EQUAL_UINT32(0, actual_len);
}
void test_audio_host_reads_and_caches_feature_unit_controls_before_mount(void) {
@@ -1501,7 +1601,6 @@ void test_audio_host_mounts_with_mute_only_when_volume_range_fails(void) {
TEST_ASSERT_TRUE(tuh_audio_mounted(0));
TEST_ASSERT_TRUE(tuh_audio_mute_supported(0, 0));
TEST_ASSERT_FALSE(tuh_audio_volume_range_get(0, 0, &range));
- TEST_ASSERT_EQUAL_UINT8(PLAYBACK_FU, tuh_audio_get_feature_unit_id(0, 0));
}
void test_audio_host_rejects_invalid_cached_volume_range(void) {
@@ -1522,7 +1621,6 @@ void test_audio_host_ignores_feature_unit_without_master_mute_or_volume(void) {
tuh_audio_volume_range_t range;
mount_descriptors(playback_fu_without_mute_volume, sizeof(playback_fu_without_mute_volume));
- TEST_ASSERT_EQUAL_UINT8(0, tuh_audio_get_feature_unit_id(0, 0));
TEST_ASSERT_FALSE(tuh_audio_mute_supported(0, 0));
TEST_ASSERT_FALSE(tuh_audio_volume_range_get(0, 0, &range));
}
@@ -1541,6 +1639,7 @@ void test_audio_host_typed_mute_and_volume_controls(void) {
TEST_ASSERT_EQUAL_HEX16(tu_u16(AUDIO10_FU_CTRL_MUTE, 0), tu_le16toh(control_request.wValue));
TEST_ASSERT_EQUAL_UINT16(1, tu_le16toh(control_request.wLength));
TEST_ASSERT_EQUAL_HEX8(1, control_buffer[0]);
+ TEST_ASSERT_FALSE(tuh_audio_volume_set(0, 0, -6 * 256, feature_unit_complete, 0));
complete_control_xfer(XFER_RESULT_SUCCESS);
TEST_ASSERT_TRUE(tuh_audio_mute_get(0, 0, &mute, feature_unit_complete, 0x2345));