summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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));