diff options
| -rw-r--r-- | examples/host/audio_host/README.md | 15 | ||||
| -rw-r--r-- | examples/host/audio_host/src/audio_app.c | 24 | ||||
| -rw-r--r-- | src/class/audio/audio_host.c | 203 | ||||
| -rw-r--r-- | src/class/audio/audio_host.h | 61 | ||||
| -rw-r--r-- | test/unit-test/test/host/audio/test_audio_host.c | 197 |
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)); |
