feat(audio): add USB Audio Host (UAC 1.0) support #3774
Conversation
Add TinyUSB Host Audio class driver supporting UAC 1.0 devices. Features: - Support multiple Audio Streaming (AS) interfaces with independent format storage - Support both IN (Microphone) and OUT (Speaker) endpoints - Per-AS interface format info: channels, sample rate, bit resolution - Support Feature Unit volume control - Support sampling frequency get/set - Add host/audio_host example for STM32F407 discovery board - Support mono-to-stereo conversion for loopback Changes: - Add src/class/audio/audio_host.c and audio_host.h - Register AUDIO driver in usbh.c - Add CFG_TUH_AUDIO macro in tusb_option.h - Add host/audio_host example with CMake and Makefile build support Tested with Jabra USB headset (stereo speaker + mono microphone) on STM32F407 disco.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces initial USB Audio Class (UAC 1.0) Host support to TinyUSB by adding a new host class driver (audio_host.c/.h), wiring it into the host driver registry, and providing a new host-side example application demonstrating enumeration and basic streaming/control requests.
Changes:
- Add a new TUH Audio (UAC 1.0) host class driver and public host API header.
- Integrate the new class driver into the host stack and build systems (CMake + Make).
- Add a new
examples/host/audio_hostexample + README.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tusb.h | Expose TUH audio host API when CFG_TUH_AUDIO is enabled. |
| src/tusb_option.h | Add default CFG_TUH_AUDIO option. |
| src/tinyusb.mk | Compile new audio host class driver. |
| src/host/usbh.c | Register audio host class driver in the host class driver table. |
| src/CMakeLists.txt | Add class/audio/audio_host.c to TinyUSB core sources. |
| src/class/audio/audio_host.h | New TUH audio host public API + callback types. |
| src/class/audio/audio_host.c | New UAC1 host driver implementation (enumeration, set_config, transfers, control requests). |
| examples/host/audio_host/src/tusb_config.h | New example configuration enabling TUH audio. |
| examples/host/audio_host/src/main.c | New host example main loop. |
| examples/host/audio_host/src/audio_app.c | New example “loopback” style audio logic and callbacks. |
| examples/host/audio_host/src/app.h | Example app header. |
| examples/host/audio_host/README.md | Example documentation. |
| examples/host/audio_host/Makefile | Make-based build for the new example. |
| examples/host/audio_host/CMakeLists.txt | CMake-based build for the new example. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| while (tu_desc_in_bounds(p_desc, desc_end)) { | ||
| if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) { | ||
| const tusb_desc_interface_t *itf = (const tusb_desc_interface_t *)p_desc; | ||
| if (itf->bInterfaceClass == TUSB_CLASS_AUDIO && itf->bInterfaceSubClass == AUDIO_SUBCLASS_STREAMING) { | ||
| // Found Audio Streaming Interface | ||
| TU_LOG_DRV(" Found AS Interface %u (alt = %u)\r\n", itf->bInterfaceNumber, itf->bAlternateSetting); |
| // If not found, check if this is an AS interface that belongs to a known AC interface | ||
| if (idx >= CFG_TUH_AUDIO_MAX) { | ||
| for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX; i++) { | ||
| if (_audioh_itf[i].daddr == dev_addr && _audioh_itf[i].as_interface_num == itf_num) { | ||
| // AS interface, already handled by AC interface's set_config | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } |
| // Send SET_INTERFACE for next AS interface if any | ||
| p_audio->as_set_idx++; | ||
| if (p_audio->as_set_idx < p_audio->as_count) { | ||
| uint8_t as_idx = p_audio->as_set_idx; | ||
| uint8_t itf = p_audio->as_interfaces[as_idx]; | ||
| uint8_t alt = p_audio->as_alt_settings[as_idx]; | ||
| if (alt > 0) { | ||
| TU_LOG_DRV("AUDIO Set Interface %u Alt %u (addr = %u)\r\n", itf, alt, xfer->daddr); | ||
| tuh_interface_set(xfer->daddr, itf, alt, audioh_set_interface_complete, idx); | ||
| return; | ||
| } | ||
| } |
| // Send SET_INTERFACE for all AS interfaces with alt_setting > 0 | ||
| if (p_audio->as_count > 0) { | ||
| p_audio->as_set_idx = 0; | ||
| uint8_t itf = p_audio->as_interfaces[0]; | ||
| uint8_t alt = p_audio->as_alt_settings[0]; | ||
| if (alt > 0) { | ||
| TU_LOG_DRV("AUDIO Set Interface %u Alt %u (addr = %u)\r\n", itf, alt, dev_addr); | ||
| tuh_interface_set(dev_addr, itf, alt, audioh_set_interface_complete, idx); | ||
| return true; | ||
| } | ||
| } |
| typedef struct { | ||
| TUH_EPBUF_DEF(epin, CFG_TUH_AUDIO_EPIN_BUFSIZE); | ||
| TUH_EPBUF_DEF(epout, CFG_TUH_AUDIO_EPOUT_BUFSIZE); | ||
| } audioh_epbuf_t; | ||
|
|
||
| static audioh_interface_t _audioh_itf[CFG_TUH_AUDIO_MAX]; | ||
|
|
| uint8_t val_buf[2] = { (uint8_t)(value & 0xFF), (uint8_t)((value >> 8) & 0xFF) }; | ||
|
|
||
| tuh_xfer_t xfer = { | ||
| .daddr = daddr, | ||
| .ep_addr = 0, | ||
| .setup = &request, | ||
| .buffer = val_buf, | ||
| .complete_cb = complete_cb, |
| bool tuh_audio_get_sampling_freq(uint8_t daddr, uint8_t ep_addr, uint32_t *sampling_freq, | ||
| tuh_xfer_cb_t complete_cb, uintptr_t user_data) { | ||
| tusb_control_request_t const request = { |
| uint16_t samples = xferred_bytes / 2; | ||
| mono_to_stereo(audio_rx_buffer, audio_tx_buffer, samples); | ||
| ok = tuh_audio_send(audio_dev_addr, audio_idx, audio_tx_buffer, xferred_bytes * 2); |
| printf(" Sampling frequency set OK, ready for isochronous transfer\r\n"); | ||
| // Set Feature Unit volume to un-mute | ||
| set_feature_unit_volume(); | ||
| // Set OUT sampling frequency then send empty packet to kick-start device | ||
| tuh_audio_set_sampling_freq(audio_dev_addr, audio_ep_out, sampling_freq, NULL, 0); | ||
| audio_ready = true; |
| 4. The example will: | ||
| - Print device information when mounted | ||
| - Set sampling frequency to 48kHz | ||
| - Receive audio samples from the device (IN endpoint) | ||
| - Send test sine wave audio to the device (OUT endpoint) |
|
…sync control transfer buffer - Stop parsing at first non-Audio interface in audioh_open to avoid claiming unrelated interfaces - Call usbh_driver_set_config_complete for AS and unknown interfaces to allow enumeration to continue - Add global ctrl endpoint buffer to audioh_epbuf_t to fix use-after-return in feature_unit_set - Add sampling_freq NULL check and initialize to 0 in tuh_audio_get_sampling_freq - Change BOARD_TUH_RHPORT from 1 to 0 in audio_host example - Add only.txt with supported MCU/family list for audio_host example
| .bRequest = AUDIO10_CS_REQ_SET_CUR, | ||
| .wValue = tu_u16(control_selector, channel), | ||
| .wIndex = tu_u16(itf_num, unit_id), | ||
| .wLength = 2 |
| .bRequest = AUDIO10_CS_REQ_GET_CUR, | ||
| .wValue = tu_u16(control_selector, channel), | ||
| .wIndex = tu_u16(itf_num, unit_id), | ||
| .wLength = len |
| .bRequest = AUDIO10_CS_REQ_SET_CUR, | ||
| .wValue = tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0), // Control Selector = Sampling Freq, Channel = 0 | ||
| .wIndex = tu_u16_low(ep_addr), | ||
| .wLength = 3 | ||
| }; |
| .bRequest = AUDIO10_CS_REQ_GET_CUR, | ||
| .wValue = tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0), // Control Selector = Sampling Freq, Channel = 0 | ||
| .wIndex = tu_u16_low(ep_addr), | ||
| .wLength = 3 | ||
| }; |
| static uint8_t freq_buf[3] = {0}; | ||
| tusb_control_request_t const request = { |
| typedef struct { | ||
| TUH_EPBUF_DEF(epin, CFG_TUH_AUDIO_EPIN_BUFSIZE); | ||
| TUH_EPBUF_DEF(epout, CFG_TUH_AUDIO_EPOUT_BUFSIZE); | ||
| TUH_EPBUF_DEF(ctrl, 8); | ||
| } audioh_epbuf_t; |
| bool tuh_audio_set_interface(uint8_t daddr, uint8_t itf_num, uint8_t alt_setting, | ||
| tuh_xfer_cb_t complete_cb, uintptr_t user_data) { | ||
| tusb_control_request_t const request = { | ||
| .bmRequestType_bit = { | ||
| .recipient = TUSB_REQ_RCPT_INTERFACE, | ||
| .type = TUSB_REQ_TYPE_STANDARD, | ||
| .direction = TUSB_DIR_OUT | ||
| }, | ||
| .bRequest = TUSB_REQ_SET_INTERFACE, | ||
| .wValue = alt_setting, | ||
| .wIndex = itf_num, | ||
| .wLength = 0 | ||
| }; | ||
|
|
||
| tuh_xfer_t xfer = { | ||
| .daddr = daddr, | ||
| .ep_addr = 0, | ||
| .setup = &request, | ||
| .buffer = NULL, | ||
| .complete_cb = complete_cb, | ||
| .user_data = user_data | ||
| }; | ||
|
|
||
| return tuh_control_xfer(&xfer); | ||
| } |
| - Print device information when mounted | ||
| - Set sampling frequency to 48kHz | ||
| - Receive audio samples from the device (IN endpoint) | ||
| - Send test sine wave audio to the device (OUT endpoint) |
| // Set OUT sampling frequency then send empty packet to kick-start device | ||
| tuh_audio_set_sampling_freq(audio_dev_addr, audio_ep_out, sampling_freq, NULL, 0); | ||
| audio_ready = true; |
| // Mono microphone, convert to stereo and send to OUT endpoint | ||
| uint16_t samples = xferred_bytes / 2; | ||
| mono_to_stereo(audio_rx_buffer, audio_tx_buffer, samples); | ||
| ok = tuh_audio_send(audio_dev_addr, audio_idx, audio_tx_buffer, xferred_bytes * 2); | ||
| } else { |
Hardware-in-the-loop (HIL) Test Reporthfp.json✅ 39 passed · ❌ 13 failed · ⚪ 0 skipped · blank not run
tinyusb.json✅ 316 passed · ❌ 23 failed · ⚪ 11 skipped · blank not run
|
…audio host - Fix missing tu_htole16() conversions for wValue and wIndex in tuh_audio_set_sampling_freq, tuh_audio_get_sampling_freq, tuh_audio_feature_unit_set, and tuh_audio_feature_unit_get - Fix incorrect wIndex parameter order in feature unit requests (unit_id and itf_num were swapped) - Replace static freq_buf with per-endpoint ctrl buffer in tuh_audio_set_sampling_freq to avoid concurrency issues - Update audio_host README to match actual example behavior
| bool tuh_audio_set_interface(uint8_t daddr, uint8_t itf_num, uint8_t alt_setting, | ||
| tuh_xfer_cb_t complete_cb, uintptr_t user_data) { | ||
| tusb_control_request_t const request = { | ||
| .bmRequestType_bit = { | ||
| .recipient = TUSB_REQ_RCPT_INTERFACE, | ||
| .type = TUSB_REQ_TYPE_STANDARD, | ||
| .direction = TUSB_DIR_OUT | ||
| }, | ||
| .bRequest = TUSB_REQ_SET_INTERFACE, | ||
| .wValue = alt_setting, | ||
| .wIndex = itf_num, | ||
| .wLength = 0 | ||
| }; | ||
|
|
||
| tuh_xfer_t xfer = { | ||
| .daddr = daddr, | ||
| .ep_addr = 0, | ||
| .setup = &request, | ||
| .buffer = NULL, | ||
| .complete_cb = complete_cb, | ||
| .user_data = user_data | ||
| }; | ||
|
|
||
| return tuh_control_xfer(&xfer); | ||
| } |
| // If not found, check if this is an AS interface that belongs to a known AC interface | ||
| if (idx >= CFG_TUH_AUDIO_MAX) { | ||
| for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX; i++) { | ||
| if (_audioh_itf[i].daddr == dev_addr && _audioh_itf[i].as_interface_num == itf_num) { | ||
| // AS interface: configuration is driven by the AC interface, so just pass through | ||
| usbh_driver_set_config_complete(dev_addr, itf_num); | ||
| return true; | ||
| } | ||
| } | ||
| // Not an Audio interface we own; pass through so enumeration can continue | ||
| usbh_driver_set_config_complete(dev_addr, itf_num); | ||
| return true; | ||
| } |
| typedef struct { | ||
| TUH_EPBUF_DEF(epin, CFG_TUH_AUDIO_EPIN_BUFSIZE); | ||
| TUH_EPBUF_DEF(epout, CFG_TUH_AUDIO_EPOUT_BUFSIZE); | ||
| TUH_EPBUF_DEF(ctrl, 8); | ||
| } audioh_epbuf_t; | ||
|
|
||
| static audioh_interface_t _audioh_itf[CFG_TUH_AUDIO_MAX]; | ||
| static audioh_epbuf_t _audioh_epbuf[CFG_TUH_AUDIO_MAX]; |
| bool tuh_audio_itf_get_info(uint8_t idx, tuh_itf_info_t *info) { | ||
| audioh_interface_t *p_audio = &_audioh_itf[idx]; | ||
| TU_VERIFY(p_audio && info); | ||
|
|
| printf(" Sampling frequency set OK, ready for isochronous transfer\r\n"); | ||
| // Set Feature Unit volume to un-mute | ||
| set_feature_unit_volume(); | ||
| // Set OUT sampling frequency then send empty packet to kick-start device | ||
| tuh_audio_set_sampling_freq(audio_dev_addr, audio_ep_out, sampling_freq, NULL, 0); | ||
| audio_ready = true; |
| if (audio_mic_channels == 1) { | ||
| // Mono microphone, convert to stereo and send to OUT endpoint | ||
| uint16_t samples = xferred_bytes / 2; | ||
| mono_to_stereo(audio_rx_buffer, audio_tx_buffer, samples); | ||
| ok = tuh_audio_send(audio_dev_addr, audio_idx, audio_tx_buffer, xferred_bytes * 2); | ||
| } else { |
| - This example uses isochronous transfers which require precise timing | ||
| - For production applications, synchronize audio transfers with the device's audio clock | ||
| - The example sends a simple sine wave for testing; replace with actual audio data in real applications |
| bool tuh_audio_get_sampling_freq(uint8_t daddr, uint8_t ep_addr, uint32_t *sampling_freq, | ||
| tuh_xfer_cb_t complete_cb, uintptr_t user_data) { | ||
| TU_VERIFY(sampling_freq, false); | ||
| *sampling_freq = 0; | ||
|
|
…nify const style Rename descriptor pointer variables to use desc_ prefix for consistency with audio_device.c: - it -> desc_input_terminal - ot -> desc_output_terminal - fu -> desc_feature_unit - itf -> desc_interface - p_ep -> desc_endpoint Unify const qualifier placement to type_t const * style. Add file header comment describing UAC 1.0 host driver capabilities. Switch license header to SPDX identifier.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 9 comments.
Comments suppressed due to low confidence (1)
examples/host/audio_host/README.md:99
- The Notes section says the example sends a "simple sine wave", but the implementation actually loops back received microphone data to the speaker (with optional mono-to-stereo conversion). This is misleading for users trying to understand the example's behavior.
## Notes
- This example uses isochronous transfers which require precise timing
- For production applications, synchronize audio transfers with the device's audio clock
- The example sends a simple sine wave for testing; replace with actual audio data in real applications
| bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { | ||
| (void) result; | ||
| const uint8_t idx = get_idx_by_ep_addr(dev_addr, ep_addr); | ||
| TU_VERIFY(idx < CFG_TUH_AUDIO_MAX); | ||
| audioh_interface_t *p_audio = &_audioh_itf[idx]; | ||
|
|
||
| if (ep_addr == p_audio->ep_in) { | ||
| tuh_audio_rx_cb(idx, ep_addr, (uint16_t) xferred_bytes); | ||
| } else if (ep_addr == p_audio->ep_out) { | ||
| tuh_audio_tx_cb(idx, ep_addr, (uint16_t) xferred_bytes); | ||
| } | ||
|
|
||
| return true; | ||
| } |
| case TUSB_DESC_ENDPOINT: { | ||
| const tusb_desc_endpoint_t *desc_endpoint = (const tusb_desc_endpoint_t *)p_desc; | ||
| if (desc_endpoint->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { | ||
| TU_LOG_DRV(" Isochronous EP %02x\r\n", desc_endpoint->bEndpointAddress); | ||
| if (tu_edpt_dir(desc_endpoint->bEndpointAddress) == TUSB_DIR_IN) { | ||
| p_audio->ep_in = desc_endpoint->bEndpointAddress; | ||
| p_audio->ep_in_size = tu_edpt_packet_size(desc_endpoint); | ||
| p_audio->ep_in_interval = desc_endpoint->bInterval; | ||
| desc_cb.desc_ep_in = desc_endpoint; | ||
| // Save to per-AS storage | ||
| if (as_entry_idx < CFG_TUH_AUDIO_MAX_AS) { | ||
| audioh_as_t *as = &p_audio->as[as_entry_idx]; | ||
| as->ep_addr = desc_endpoint->bEndpointAddress; | ||
| as->ep_size = tu_edpt_packet_size(desc_endpoint); | ||
| as->ep_dir = TUSB_DIR_IN; | ||
| as->format_type = tmp_format_type; | ||
| as->num_channels = tmp_num_channels; | ||
| as->sub_frame_size = tmp_sub_frame_size; | ||
| as->bit_resolution = tmp_bit_resolution; | ||
| as->sam_freq_type = tmp_sam_freq_type; | ||
| as->sam_freq_lower = tmp_sam_freq_lower; | ||
| as->sam_freq_upper = tmp_sam_freq_upper; | ||
| for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX_SAM_FREQ; i++) { | ||
| as->sam_freq[i] = tmp_sam_freq[i]; | ||
| } | ||
| } | ||
| } else { | ||
| p_audio->ep_out = desc_endpoint->bEndpointAddress; | ||
| p_audio->ep_out_size = tu_edpt_packet_size(desc_endpoint); | ||
| p_audio->ep_out_interval = desc_endpoint->bInterval; | ||
| desc_cb.desc_ep_out = desc_endpoint; | ||
| // Save to per-AS storage | ||
| if (as_entry_idx < CFG_TUH_AUDIO_MAX_AS) { | ||
| audioh_as_t *as = &p_audio->as[as_entry_idx]; | ||
| as->ep_addr = desc_endpoint->bEndpointAddress; | ||
| as->ep_size = tu_edpt_packet_size(desc_endpoint); | ||
| as->ep_dir = TUSB_DIR_OUT; | ||
| as->format_type = tmp_format_type; | ||
| as->num_channels = tmp_num_channels; | ||
| as->sub_frame_size = tmp_sub_frame_size; | ||
| as->bit_resolution = tmp_bit_resolution; | ||
| as->sam_freq_type = tmp_sam_freq_type; | ||
| as->sam_freq_lower = tmp_sam_freq_lower; | ||
| as->sam_freq_upper = tmp_sam_freq_upper; | ||
| for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX_SAM_FREQ; i++) { | ||
| as->sam_freq[i] = tmp_sam_freq[i]; | ||
| } | ||
| } | ||
| } | ||
| TU_ASSERT(tuh_edpt_open(dev_addr, desc_endpoint), 0); | ||
| } |
| bool tuh_audio_set_sampling_freq(uint8_t daddr, uint8_t ep_addr, uint32_t sampling_freq, | ||
| tuh_xfer_cb_t complete_cb, uintptr_t user_data) { | ||
| uint8_t const idx = get_idx_by_ep_addr(daddr, ep_addr); | ||
| TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false); | ||
| uint8_t* freq_buf = _audioh_epbuf[idx].ctrl; | ||
| tusb_control_request_t const request = { | ||
| .bmRequestType_bit = { | ||
| .recipient = TUSB_REQ_RCPT_ENDPOINT, | ||
| .type = TUSB_REQ_TYPE_CLASS, | ||
| .direction = TUSB_DIR_OUT | ||
| }, | ||
| .bRequest = AUDIO10_CS_REQ_SET_CUR, | ||
| .wValue = tu_htole16(tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0)), // Control Selector = Sampling Freq, Channel = 0 | ||
| .wIndex = tu_htole16((uint16_t) ep_addr), | ||
| .wLength = 3 | ||
| }; |
| bool tuh_audio_get_sampling_freq(uint8_t daddr, uint8_t ep_addr, uint32_t *sampling_freq, | ||
| tuh_xfer_cb_t complete_cb, uintptr_t user_data) { | ||
| TU_VERIFY(sampling_freq, false); | ||
| *sampling_freq = 0; | ||
|
|
||
| tusb_control_request_t const request = { | ||
| .bmRequestType_bit = { | ||
| .recipient = TUSB_REQ_RCPT_ENDPOINT, | ||
| .type = TUSB_REQ_TYPE_CLASS, | ||
| .direction = TUSB_DIR_IN | ||
| }, | ||
| .bRequest = AUDIO10_CS_REQ_GET_CUR, | ||
| .wValue = tu_htole16(tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0)), // Control Selector = Sampling Freq, Channel = 0 | ||
| .wIndex = tu_htole16((uint16_t) ep_addr), | ||
| .wLength = 3 | ||
| }; |
| bool tuh_audio_feature_unit_set(uint8_t daddr, uint8_t itf_num, uint8_t unit_id, | ||
| uint8_t control_selector, uint8_t channel, | ||
| uint16_t value, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { | ||
| tusb_control_request_t const request = { | ||
| .bmRequestType_bit = { | ||
| .recipient = TUSB_REQ_RCPT_INTERFACE, | ||
| .type = TUSB_REQ_TYPE_CLASS, | ||
| .direction = TUSB_DIR_OUT | ||
| }, | ||
| .bRequest = AUDIO10_CS_REQ_SET_CUR, | ||
| .wValue = tu_htole16(tu_u16(control_selector, channel)), | ||
| .wIndex = tu_htole16(tu_u16(unit_id, itf_num)), | ||
| .wLength = 2 | ||
| }; |
| bool tuh_audio_feature_unit_get(uint8_t daddr, uint8_t itf_num, uint8_t unit_id, | ||
| uint8_t control_selector, uint8_t channel, | ||
| void *buffer, uint8_t len, | ||
| tuh_xfer_cb_t complete_cb, uintptr_t user_data) { | ||
| tusb_control_request_t const request = { | ||
| .bmRequestType_bit = { | ||
| .recipient = TUSB_REQ_RCPT_INTERFACE, | ||
| .type = TUSB_REQ_TYPE_CLASS, | ||
| .direction = TUSB_DIR_IN | ||
| }, | ||
| .bRequest = AUDIO10_CS_REQ_GET_CUR, | ||
| .wValue = tu_htole16(tu_u16(control_selector, channel)), | ||
| .wIndex = tu_htole16(tu_u16(unit_id, itf_num)), | ||
| .wLength = len | ||
| }; |
| bool tuh_audio_set_interface(uint8_t daddr, uint8_t itf_num, uint8_t alt_setting, | ||
| tuh_xfer_cb_t complete_cb, uintptr_t user_data) { | ||
| tusb_control_request_t const request = { | ||
| .bmRequestType_bit = { | ||
| .recipient = TUSB_REQ_RCPT_INTERFACE, | ||
| .type = TUSB_REQ_TYPE_STANDARD, | ||
| .direction = TUSB_DIR_OUT | ||
| }, | ||
| .bRequest = TUSB_REQ_SET_INTERFACE, | ||
| .wValue = alt_setting, | ||
| .wIndex = itf_num, | ||
| .wLength = 0 | ||
| }; | ||
|
|
||
| tuh_xfer_t xfer = { | ||
| .daddr = daddr, | ||
| .ep_addr = 0, | ||
| .setup = &request, | ||
| .buffer = NULL, | ||
| .complete_cb = complete_cb, | ||
| .user_data = user_data | ||
| }; | ||
|
|
||
| return tuh_control_xfer(&xfer); | ||
| } |
| // Callback after IN sampling frequency is set | ||
| static void in_sampling_freq_set_cb(tuh_xfer_t *xfer) { | ||
| if (xfer->result != XFER_RESULT_SUCCESS) { | ||
| printf(" Sampling frequency set FAILED: result=%u\r\n", xfer->result); | ||
| return; | ||
| } | ||
| printf(" Sampling frequency set OK, ready for isochronous transfer\r\n"); | ||
| // Set Feature Unit volume to un-mute | ||
| set_feature_unit_volume(); | ||
| // Set OUT sampling frequency then send empty packet to kick-start device | ||
| tuh_audio_set_sampling_freq(audio_dev_addr, audio_ep_out, sampling_freq, NULL, 0); | ||
| audio_ready = true; | ||
| } |
| if (xferred_bytes > 0 && audio_ep_out != 0 && !audio_tx_busy) { | ||
| bool ok; | ||
| if (audio_mic_channels == 1) { | ||
| // Mono microphone, convert to stereo and send to OUT endpoint | ||
| uint16_t samples = xferred_bytes / 2; | ||
| mono_to_stereo(audio_rx_buffer, audio_tx_buffer, samples); | ||
| ok = tuh_audio_send(audio_dev_addr, audio_idx, audio_tx_buffer, xferred_bytes * 2); | ||
| } else { | ||
| // Stereo microphone, send directly to OUT endpoint | ||
| ok = tuh_audio_send(audio_dev_addr, audio_idx, audio_rx_buffer, xferred_bytes); | ||
| } | ||
|
|
||
| if (ok) { | ||
| audio_tx_busy = true; | ||
| } | ||
| } |
Add TinyUSB Host Audio class driver supporting UAC 1.0 devices.
Features:
Changes:
Tested with Jabra USB headset (stereo speaker + mono microphone) on STM32F407 disco.