diff --git a/.build-test-rules.yml b/.build-test-rules.yml index fe7c53c5..fd72f65b 100644 --- a/.build-test-rules.yml +++ b/.build-test-rules.yml @@ -6,4 +6,4 @@ device/esp_tinyusb: host/class: enable: - - if: IDF_TARGET in ["esp32s2", "esp32s3"] \ No newline at end of file + - if: IDF_TARGET in ["esp32s2", "esp32s3"] diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index 1a2c73d1..90a74d98 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -41,6 +41,7 @@ jobs: host/class/cdc/usb_host_vcp; host/class/hid/usb_host_hid; host/class/msc/usb_host_msc; + host/class/uac/usb_host_uac; host/class/uvc/usb_host_uvc; namespace: "espressif" # API token will only be available in the master branch in the main repository. diff --git a/.idf_build_apps.toml b/.idf_build_apps.toml index 2ad117c2..739fb9ec 100644 --- a/.idf_build_apps.toml +++ b/.idf_build_apps.toml @@ -4,6 +4,7 @@ paths = [ "host/class/cdc/usb_host_cdc_acm/test_app", "host/class/hid/usb_host_hid/test_app", "host/class/msc/usb_host_msc/test_app", + "host/class/uac/usb_host_uac/test_app", "host/class/uvc/usb_host_uvc/test_app", ] recursive = true diff --git a/host/class/uac/usb_host_uac/CHANGELOG.md b/host/class/uac/usb_host_uac/CHANGELOG.md new file mode 100644 index 00000000..532bcd2c --- /dev/null +++ b/host/class/uac/usb_host_uac/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version diff --git a/host/class/uac/usb_host_uac/CMakeLists.txt b/host/class/uac/usb_host_uac/CMakeLists.txt new file mode 100644 index 00000000..6f198ee3 --- /dev/null +++ b/host/class/uac/usb_host_uac/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register( SRCS "uac_descriptors.c" "uac_host.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES usb esp_ringbuf) diff --git a/host/class/uac/usb_host_uac/Kconfig b/host/class/uac/usb_host_uac/Kconfig new file mode 100644 index 00000000..011de6aa --- /dev/null +++ b/host/class/uac/usb_host_uac/Kconfig @@ -0,0 +1,24 @@ +menu "USB Host UAC" + config PRINTF_UAC_CONFIGURATION_DESCRIPTOR + bool "Print UAC Configuration Descriptor" + default n + help + Print UAC Configuration Descriptor to console. + config UAC_FREQ_NUM_MAX + int "Max Number of Frequencies each Alt-interface supports" + default 4 + help + Max Number of Frequencies each Alt-interface supports. If the device supports more frequencies, + the driver will only use the first UAC_FREQ_NUM_MAX frequencies. + config UAC_NUM_ISOC_URBS + int "Number of UAC ISOC URBs" + default 3 + help + Number of UAC ISOC URBs to use. Fewer URBs could cause audio dropouts. + More URBs will increase the RAM usage. + config UAC_NUM_PACKETS_PER_URB + int "Number of Packets per UAC ISOC URB" + default 3 + help + Number of Packets per UAC ISOC URB. It limits the minimum packets each transfer will send. +endmenu # "USB Host UAC" diff --git a/host/class/uac/usb_host_uac/LICENSE b/host/class/uac/usb_host_uac/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/host/class/uac/usb_host_uac/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/host/class/uac/usb_host_uac/README.md b/host/class/uac/usb_host_uac/README.md new file mode 100644 index 00000000..e610b7d1 --- /dev/null +++ b/host/class/uac/usb_host_uac/README.md @@ -0,0 +1,56 @@ +# USB Host UAC Driver + +[![Component Registry](https://components.espressif.com/components/espressif/usb_host_uac/badge.svg)](https://components.espressif.com/components/espressif/usb_host_uac) + +This directory contains an implementation of a USB UAC Driver implemented on top of the [USB Host Library](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html). + +UAC driver allows access to UAC 1.0 devices. + +## Usage + +The following steps outline the typical API call pattern of the UAC Class Driver: + +1. Install the USB Host Library via `usb_host_install()` +2. Install the UAC driver via `uac_host_install()` +3. When the new (logic) UAC device is connected, the driver event callback will be called with USB device address and event: + - `UAC_HOST_DRIVER_EVENT_TX_CONNECTED` + - `UAC_HOST_DRIVER_EVENT_RX_CONNECTED` +4. To open/close the UAC device with USB device address and interface number: + - `uac_host_device_open()` + - `uac_host_device_close()` +5. To get the device-supported audio format use: + - `uac_host_get_device_info()` + - `uac_host_get_device_alt_param()` +6. To enable/disable data streaming with specific audio format use: + - `uac_host_device_start()` + - `uac_host_device_stop()` +7. To suspend/resume data streaming use: + - `uac_host_device_suspend()` + - `uac_host_device_resume()` +8. To control the volume/mute use: + - `uac_host_device_set_mute()` +9. To control the volume use: + - `uac_host_device_set_volume()` or `uac_host_device_set_volume_db()` +10. After the uac device is opened, the device event callback will be called with the following events: + - UAC_HOST_DEVICE_EVENT_RX_DONE + - UAC_HOST_DEVICE_EVENT_TX_DONE + - UAC_HOST_DEVICE_EVENT_TRANSFER_ERROR + - UAC_HOST_DRIVER_EVENT_DISCONNECTED +11. When the `UAC_HOST_DRIVER_EVENT_DISCONNECTED` event is called, the device should be closed via `uac_host_device_close()` +12. The UAC driver can be uninstalled via `uac_host_uninstall()` + +> Note: For physical device with both microphone and speaker, the driver will treat it as two separate logic devices. + +> The `UAC_HOST_DRIVER_EVENT_TX_CONNECTED` and `UAC_HOST_DRIVER_EVENT_RX_CONNECTED` event will be called for the device. + +## Known issues + +- Empty + +## Examples + +- For an example, refer to [usb_audio_player](https://github.com/espressif/esp-iot-solution/tree/master/examples/usb/host/usb_audio_player) + +## Supported Devices + +- UAC Driver supports any UAC 1.0 compatible device. \ No newline at end of file diff --git a/host/class/uac/usb_host_uac/idf_component.yml b/host/class/uac/usb_host_uac/idf_component.yml new file mode 100644 index 00000000..f732b7fa --- /dev/null +++ b/host/class/uac/usb_host_uac/idf_component.yml @@ -0,0 +1,10 @@ +## IDF Component Manager Manifest File +version: "1.0.0" +description: USB Host UAC driver +url: https://github.com/espressif/esp-usb/tree/master/host/class/uac/usb_host_uac +dependencies: + idf: ">=4.4" +targets: + - esp32s2 + - esp32s3 + - esp32p4 diff --git a/host/class/uac/usb_host_uac/include/usb/uac.h b/host/class/uac/usb_host_uac/include/usb/uac.h new file mode 100644 index 00000000..a9c1b85b --- /dev/null +++ b/host/class/uac/usb_host_uac/include/usb/uac.h @@ -0,0 +1,516 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "sdkconfig.h" +#include "usb/usb_types_ch9.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/********************************* Refer audio10.pdf ***************************************************/ + +/** + * @brief Audio Interface Subclass Codes + * + * @see Table A-2 of audio10.pdf + */ +typedef enum { + UAC_SUBCLASS_UNDEFINED = 0x00, + UAC_SUBCLASS_AUDIOCONTROL = 0x01, + UAC_SUBCLASS_AUDIOSTREAMING = 0x02, + UAC_SUBCLASS_MIDISTREAMING = 0x03, +} uac_subclass_t; + +/** + * @brief Audio Interface Protocol Codes + * + * @see Table A-3 of audio10.pdf + */ +typedef enum { + UAC_PROTOCOL_UNDEFINED = 0x00, + UAC_PROTOCOL_v20 = 0x20, // refer UAC v2.0 +} uac_protocol_t; + +/** + * @brief Audio Class-Specific Descriptor Types + * + * @see Table A-4 of audio10.pdf + */ +typedef enum { + UAC_CS_UNDEFINED = 0x20, + UAC_CS_DEVICE = 0x21, + UAC_CS_CONFIGURATION = 0x22, + UAC_CS_STRING = 0x23, + UAC_CS_INTERFACE = 0x24, + UAC_CS_ENDPOINT = 0x25 +} uac_cs_descriptor_type_t; + +/** + * @brief Audio Class-Specific AC Interface Descriptor Subtypes + * + * @see Table A-5 of audio10.pdf + */ +typedef enum { + UAC_AC_DESCRIPTOR_UNDEFINED = 0x00, + UAC_AC_HEADER = 0x01, + UAC_AC_INPUT_TERMINAL = 0x02, + UAC_AC_OUTPUT_TERMINAL = 0x03, + UAC_AC_MIXER_UNIT = 0x04, + UAC_AC_SELECTOR_UNIT = 0x05, + UAC_AC_FEATURE_UNIT = 0x06, + UAC_AC_PROCESSING_UNIT = 0x07, + UAC_AC_EXTENSION_UNIT = 0x08 +} uac_ac_descriptor_subtype_t; + +/** + * @brief Audio Class-Specific AS Interface Descriptor Subtypes + * + * @see Table A-6 of audio10.pdf + */ +typedef enum { + UAC_AS_DESCRIPTOR_UNDEFINED = 0x00, + UAC_AS_GENERAL = 0x01, + UAC_AS_FORMAT_TYPE = 0x02, + UAC_AS_FORMAT_SPECIFIC = 0x03 +} uac_as_descriptor_subtype_t; + +/** + * @brief Processing Unit Process Types + * + * @see Table A-7 of audio10.pdf + */ +typedef enum { + UAC_PROCESS_UNDEFINED = 0x00, + UAC_UP_DOWNMIX_PROCESS = 0x01, + UAC_DOLBY_PROLOGIC_PROCESS = 0x02, + UAC_3D_STEREO_EXTENDER_PROCESS = 0x03, + UAC_REVERBERATION_PROCESS = 0x04, + UAC_CHORUS_PROCESS = 0x05, + UAC_DYN_RANGE_COMP_PROCESS = 0x06 +} uac_process_type_t; + +/** + * @brief Audio Class-Specific Endpoint Descriptor Subtypes + * + * @see Table A-8 of audio10.pdf + */ +typedef enum { + UAC_EP_DESCRIPTOR_UNDEFINED = 0x00, + UAC_EP_GENERAL = 0x01 +} uac_ep_descriptor_subtype_t; + +/** + * @brief Audio Class-Specific Request Codes + * + * @see Table A-9 of audio10.pdf + */ +typedef enum { + UAC_REQUEST_UNDEFINED = 0x00, + UAC_SET_CUR = 0x01, + UAC_GET_CUR = 0x81, + UAC_SET_MIN = 0x02, + UAC_GET_MIN = 0x82, + UAC_SET_MAX = 0x03, + UAC_GET_MAX = 0x83, + UAC_SET_RES = 0x04, + UAC_GET_RES = 0x84, + UAC_SET_MEM = 0x05, + UAC_GET_MEM = 0x85, + UAC_GET_STAT = 0xFF +} uac_request_code_t; + +/********************************* A.10 Control Selector Codes ****************************/ + +/** + * @brief Terminal Control Selectors + * + * @see Table A-10 of audio10.pdf + */ +typedef enum { + UAC_TE_CONTROL_UNDEFINED = 0x00, + UAC_COPY_PROTECT_CONTROL = 0x01 +} uac_te_control_selector_t; + +/** + * @brief Feature Unit Control Selectors + * + * @see Table A-11 of audio10.pdf + */ +typedef enum { + UAC_FU_CONTROL_UNDEFINED = 0x00, + UAC_MUTE_CONTROL = 0x01, + UAC_VOLUME_CONTROL = 0x02, + UAC_BASS_CONTROL = 0x03, + UAC_MID_CONTROL = 0x04, + UAC_TREBLE_CONTROL = 0x05, + UAC_GRAPHIC_EQUALIZER_CONTROL = 0x06, + UAC_AUTOMATIC_GAIN_CONTROL = 0x07, + UAC_DELAY_CONTROL = 0x08, + UAC_BASS_BOOST_CONTROL = 0x09, + UAC_LOUDNESS_CONTROL = 0x0A +} uac_fu_control_selector_t; + +/******************************** A.10.3 Processing Unit Control Selectors *********************/ + +/** + * @brief UP/DOWNMIX Processing Unit Control Selectors + * + * @see Table A-12 of audio10.pdf + */ +typedef enum { + UAC_UD_CONTROL_UNDEFINED = 0x00, + UAC_UD_ENABLE_CONTROL = 0x01, + UAC_UD_MODE_SELECT_CONTROL = 0x02 +} uac_ud_control_selector_t; + +/** + * @brief Dolby Prologic Processing Unit Control Selectors + * + * @see Table A-13 of audio10.pdf + */ +typedef enum { + UAC_DP_CONTROL_UNDEFINED = 0x00, + UAC_DP_ENABLE_CONTROL = 0x01, + UAC_DP_MODE_SELECT_CONTROL = 0x02 +} uac_dp_control_selector_t; + +/** + * @brief 3D Stereo Extender Processing Unit Control Selectors + * + * @see Table A-14 of audio10.pdf + */ +typedef enum { + UAC_3DSE_CONTROL_UNDEFINED = 0x00, + UAC_3DSE_ENABLE_CONTROL = 0x01, + UAC_3DSE_SPACIOUSNESS_CONTROL = 0x03 +} uac_3dse_control_selector_t; + +/** + * @brief Reverberation Processing Unit Control Selectors + * + * @see Table A-15 of audio10.pdf + */ +typedef enum { + UAC_RV_CONTROL_UNDEFINED = 0x00, + UAC_RV_ENABLE_CONTROL = 0x01, + UAC_REVERB_LEVEL_CONTROL = 0x02, + UAC_REVERB_TIME_CONTROL = 0x03, + UAC_REVERB_FEEDBACK_CONTROL = 0x04 +} uac_rv_control_selector_t; + +/** + * @brief Chorus Processing Unit Control Selectors + * + * @see Table A-16 of audio10.pdf + */ +typedef enum { + UAC_CH_CONTROL_UNDEFINED = 0x00, + UAC_CH_ENABLE_CONTROL = 0x01, + UAC_CHORUS_LEVEL_CONTROL = 0x02, + UAC_CHORUS_RATE_CONTROL = 0x03, + UAC_CHORUS_DEPTH_CONTROL = 0x04 +} uac_ch_control_selector_t; + +/** + * @brief Dynamic Range Compressor Processing Unit Control Selectors + * + * @see Table A-17 of audio10.pdf + */ +typedef enum { + UAC_DR_CONTROL_UNDEFINED = 0x00, + UAC_DR_ENABLE_CONTROL = 0x01, + UAC_COMPRESSION_RATE_CONTROL = 0x02, + UAC_MAXAMPL_CONTROL = 0x03, + UAC_THRESHOLD_CONTROL = 0x04, + UAC_ATTACK_TIME = 0x05, + UAC_RELEASE_TIME = 0x06 +} uac_dr_control_selector_t; + +/******************************** A.10.4 Extension Unit Control Selectors *********************/ + +/** + * @brief Extension Unit Control Selectors + * + * @see Table A-18 of audio10.pdf + * + */ +typedef enum { + UAC_XU_CONTROL_UNDEFINED = 0x00, + UAC_XU_ENABLE_CONTROL = 0x01 +} uac_xu_control_selector_t; + +/******************************** A.10.5 Endpoint Control Selectors ****************************/ + +/** + * @brief Endpoint Control Selectors + * + * @see Table A-19 of audio10.pdf + */ +typedef enum { + UAC_EP_CONTROL_UNDEFINED = 0x00, + UAC_SAMPLING_FREQ_CONTROL = 0x01, + UAC_PITCH_CONTROL = 0x02 +} uac_ep_control_selector_t; + +/** + * @brief Feature Unit Control Position + * + * @see Table 4-7 of audio10.pdf + */ +typedef enum { + UAC_FU_CONTROL_POS_MUTE = 0x0001, + UAC_FU_CONTROL_POS_VOLUME = 0x0002, + UAC_FU_CONTROL_POS_BASS = 0x0004, + UAC_FU_CONTROL_POS_MID = 0x0008, + UAC_FU_CONTROL_POS_TREBLE = 0x0010, + UAC_FU_CONTROL_POS_GRAPHIC_EQUALIZER = 0x0020, + UAC_FU_CONTROL_POS_AUTOMATIC_GAIN = 0x0040, + UAC_FU_CONTROL_POS_DELAY = 0x0080, + UAC_FU_CONTROL_POS_BASS_BOOST = 0x0100, + UAC_FU_CONTROL_POS_LOUDNESS = 0x0200, +} uac_fu_control_pos_t; + +/******************************** Refer termt10.pdf ***************************************************/ + +/** +* @brief USB Terminal Types +* +* @see Table 2-1 of termt10.pdf +*/ +typedef enum { + UAC_USB_TERMINAL_TYPE_USB_UNDEFINED = 0x0100, + UAC_USB_TERMINAL_TYPE_USB_STREAMING = 0x0101, + UAC_USB_TERMINAL_TYPE_VENDOR_SPECIFIC = 0x01FF +} uac_usb_terminal_type_t; + +/** +* @brief Input Terminal Types +* +* @see Table 2-2 of termt10.pdf +*/ +typedef enum { + UAC_INPUT_TERMINAL_UNDEFINED = 0x0200, + UAC_INPUT_TERMINAL_MICROPHONE = 0x0201, + UAC_INPUT_TERMINAL_DESKTOP_MICROPHONE = 0x0202, + UAC_INPUT_TERMINAL_PERSONAL_MICROPHONE = 0x0203, + UAC_INPUT_TERMINAL_OMNI_DIRECTIONAL_MICROPHONE = 0x0204, + UAC_INPUT_TERMINAL_MICROPHONE_ARRAY = 0x0205, + UAC_INPUT_TERMINAL_PROCESSING_MICROPHONE_ARRAY = 0x0206 +} uac_input_terminal_type_t; + +/** + * @brief Output Terminal Types + * + * @see Table 2-3 of termt10.pdf + */ +typedef enum { + UAC_OUTPUT_TERMINAL_UNDEFINED = 0x0300, + UAC_OUTPUT_TERMINAL_SPEAKER = 0x0301, + UAC_OUTPUT_TERMINAL_HEADPHONES = 0x0302, + UAC_OUTPUT_TERMINAL_HEAD_MOUNTED_DISPLAY_AUDIO = 0x0303, + UAC_OUTPUT_TERMINAL_DESKTOP_SPEAKER = 0x0304, + UAC_OUTPUT_TERMINAL_ROOM_SPEAKER = 0x0305, + UAC_OUTPUT_TERMINAL_COMMUNICATION_SPEAKER = 0x0306, + UAC_OUTPUT_TERMINAL_LOW_FREQUENCY_EFFECTS_SPEAKER = 0x0307 +} uac_output_terminal_type_t; + +/******************************** Refer frmts10.pdf ***************************************************/ + +/** + * @brief Audio Data Format Type I Codes + * + * @see Table A-1 of frmts10.pdf + */ +typedef enum { + UAC_TYPE_I_UNDEFINED = 0x0000, + UAC_TYPE_I_PCM = 0x0001, + UAC_TYPE_I_PCM8 = 0x0002, + UAC_TYPE_I_IEEE_FLOAT = 0x0003, + UAC_TYPE_I_ALAW = 0x0004, + UAC_TYPE_I_MULAW = 0x0005 +} uac_type_i_format_t; + +/** + * @brief Format Type Codes + * + * @see Table A-4 of frmts10.pdf + */ +typedef enum { + UAC_FORMAT_TYPE_UNDEFINED = 0x00, + UAC_FORMAT_TYPE_I = 0x01, + UAC_FORMAT_TYPE_II = 0x02, + UAC_FORMAT_TYPE_III = 0x03 +} uac_format_type_t; + +/** + * @brief Audio Class-Specific Interface Descriptor Common Header + * + */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; +} __attribute__((packed)) uac_desc_header_t; + +/** + * @brief Audio Class-Specific AC Interface Header Descriptor (bInCollection=2) + * + * @see Table 4-2 of audio10.pdf + */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint16_t bcdADC; + uint16_t wTotalLength; + uint8_t bInCollection; + uint8_t baInterfaceNr[2]; +} __attribute__((packed)) uac_ac_header_desc_t; + +/** + * @brief Audio Class-Specific AC Input Terminal Descriptor + * + * @see Table 4-3 of audio10.pdf + */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bNrChannels; + uint16_t wChannelConfig; + uint8_t iChannelNames; + uint8_t iTerminal; +} __attribute__((packed)) uac_ac_input_terminal_desc_t; + +/** + * @brief Audio Class-Specific AC Output Terminal Descriptor + * + * @see Table 4-4 of audio10.pdf + */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bSourceID; + uint8_t iTerminal; +} __attribute__((packed)) uac_ac_output_terminal_desc_t; + +/** + * @brief Audio Class-Specific AC Mixer Unit Descriptor + * + * @see Table 4-5 of audio10.pdf + */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bUnitID; + uint8_t bNrInPins; + uint8_t baSourceID[2]; + uint8_t bNrChannels; + uint16_t wChannelConfig; + uint8_t iChannelNames; + uint8_t bmControls; + uint8_t iMixer; +} __attribute__((packed)) uac_ac_mixer_unit_desc_t; + +/** + * @brief Audio Class-Specific AC Selector Unit Descriptor + * + * @see Table 4-6 of audio10.pdf + */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bUnitID; + uint8_t bNrInPins; + uint8_t baSourceID[2]; + uint8_t iSelector; +} __attribute__((packed)) uac_ac_selector_unit_desc_t; + +/** + * @brief Audio Class-Specific AC Feature Unit Descriptor (ch=2, bControlSize=2) + * + * @see Table 4-7 of audio10.pdf + */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bUnitID; + uint8_t bSourceID; + uint8_t bControlSize; + uint8_t bmaControls[3 * 2]; // 2 channels + channel 0, 2 bytes each + uint8_t iFeature; +} __attribute__((packed)) uac_ac_feature_unit_desc_t; + +/** + * @brief Audio Class-Specific AS General Descriptor + * + * @see Table 4-19 of audio10.pdf + */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalLink; + uint8_t bDelay; + uint16_t wFormatTag; +} __attribute__((packed)) uac_as_general_desc_t; + +#define UAC_FREQ_NUM_MAX CONFIG_UAC_FREQ_NUM_MAX +/** + * @brief Audio Class-Specific AS Type I Format Type Descriptor + * + * @see Table 2-1 of frmts10.pdf + */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bFormatType; + uint8_t bNrChannels; + uint8_t bSubframeSize; + uint8_t bBitResolution; + uint8_t bSamFreqType; + uint8_t tSamFreq[3 * UAC_FREQ_NUM_MAX]; +} __attribute__((packed)) uac_as_type_I_format_desc_t; + +/** + * @brief Audio Class-Specific AS Isochronous Audio Data Endpoint Descriptor + * + * @see Table 4-21 of audio10.pdf + */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bmAttributes; + uint8_t bLockDelayUnits; + uint16_t wLockDelay; +} __attribute__((packed)) uac_as_cs_ep_desc_t; + +/** + * @brief Print UAC device full configuration descriptor + * + * @param cfg_desc + */ +void print_uac_descriptors(const usb_config_desc_t *cfg_desc); + +#ifdef __cplusplus +} +#endif //__cplusplus diff --git a/host/class/uac/usb_host_uac/include/usb/uac_host.h b/host/class/uac/usb_host_uac/include/usb/uac_host.h new file mode 100644 index 00000000..f9733f9b --- /dev/null +++ b/host/class/uac/usb_host_uac/include/usb/uac_host.h @@ -0,0 +1,400 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "uac.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief USB UAC HOST string descriptor maximal length + * + * The maximum possible number of characters in an embedded string is device specific. + * For USB devices, the maximum string length is 126 wide characters (not including the terminating NULL character). + * This is a length, which is available to upper level application during getting information + * of UAC Device with 'uac_host_get_device_info' call. + * + * To decrease memory usage 32 wide characters (64 bytes per every string) is used. +*/ +#define UAC_STR_DESC_MAX_LENGTH (32) + +/** + * @brief Flags to control stream work flow + * + * FLAG_STREAM_SUSPEND_AFTER_START: do not start stream transfer during start, only claim interface and prepare memory + * @note User should call uac_host_device_resume to start stream transfer when needed +*/ +#define FLAG_STREAM_SUSPEND_AFTER_START (1 << 0) + +typedef struct uac_interface *uac_host_device_handle_t; /*!< Logic Device Handle. Handle to a particular UAC interface */ + +// ------------------------ USB UAC Host events -------------------------------- +/** + * @brief USB UAC HOST Driver event id +*/ +typedef enum { + UAC_HOST_DRIVER_EVENT_RX_CONNECTED = 0x00, /*!< UAC RX Device has been found in connected USB device */ + UAC_HOST_DRIVER_EVENT_TX_CONNECTED, /*!< UAC TX Device has been found in connected USB device */ +} uac_host_driver_event_t; + +/** + * @brief USB UAC Device (Interface) event id +*/ +typedef enum { + UAC_HOST_DEVICE_EVENT_RX_DONE = 0x00, /*!< RX Done: the receive buffer data size exceeds the threshold */ + UAC_HOST_DEVICE_EVENT_TX_DONE, /*!< TX Done: the transmit buffer data size falls below the threshold */ + UAC_HOST_DEVICE_EVENT_TRANSFER_ERROR, /*!< UAC Device transfer error */ + UAC_HOST_DRIVER_EVENT_DISCONNECTED, /*!< UAC Device has been disconnected */ +} uac_host_device_event_t; + +// ------------------------ USB UAC Host events callbacks ----------------------------- +/** + * @brief USB UAC driver event callback. + * + * @param[in] addr USB Address of connected UAC device + * @param[in] iface_num UAC Interface Number + * @param[in] event UAC driver event + * @param[in] arg User argument from UAC driver configuration structure +*/ +typedef void (*uac_host_driver_event_cb_t)(uint8_t addr, uint8_t iface_num, + const uac_host_driver_event_t event, void *arg); + +/** + * @brief USB UAC logic device/interface event callback. + * + * @param[in] uac_device_handle UAC device handle (UAC Interface) + * @param[in] event UAC device event + * @param[in] arg User argument +*/ +typedef void (*uac_host_device_event_cb_t)(uac_host_device_handle_t uac_device_handle, + const uac_host_device_event_t event, void *arg); + +/** + * @brief USB UAC host class descriptor print callback + * + * @param[in] desc Pointer to the USB configuration descriptor + * @param[in] class Class of the UAC device + * @param[in] subclass Subclass of the UAC device + * @param[in] protocol Protocol of the UAC device + * + */ +typedef void (*print_class_descriptor_with_context_cb)(const usb_standard_desc_t *desc, + uint8_t class, uint8_t subclass, uint8_t protocol); + +/** + * @brief Stream type + * +*/ +typedef enum { + UAC_STREAM_TX = 0, /*!< usb audio TX (eg. speaker stream) */ + UAC_STREAM_RX, /*!< usb audio RX (eg. microphone stream) */ + UAC_STREAM_MAX, /*!< max stream type */ +} uac_host_stream_t; + +/** + * @brief USB UAC logic device/interface information +*/ +typedef struct { + uac_host_stream_t type; /*!< Stream type */ + uint8_t iface_num; /*!< UAC Interface Number */ + uint8_t iface_alt_num; /*!< UAC Interface Alternate Setting Total Number */ + uint8_t addr; /*!< USB Address of connected UAC device */ + uint16_t VID; /*!< Vendor ID */ + uint16_t PID; /*!< Product ID */ + wchar_t iManufacturer[UAC_STR_DESC_MAX_LENGTH]; /*!< Manufacturer string */ + wchar_t iProduct[UAC_STR_DESC_MAX_LENGTH]; /*!< Product string */ + wchar_t iSerialNumber[UAC_STR_DESC_MAX_LENGTH]; /*!< Serial Number string */ +} uac_host_dev_info_t; + +/** + * @brief USB UAC Interface alternate information + * +*/ +typedef struct { + uint8_t format; /*!< audio stream format, currently only support 1 - PCM */ + uint8_t channels; /*!< audio stream channels */ + uint8_t bit_resolution; /*!< audio stream bit resolution */ + uint8_t sample_freq_type; /*!< audio stream sample frequency type, 0 - continuous, 1,2,3 - discrete */ + union { + uint32_t sample_freq[UAC_FREQ_NUM_MAX]; /*!< audio stream sample frequency, first N discrete sample frequency */ + struct { + uint32_t sample_freq_lower; /*!< audio stream sample frequency lower */ + uint32_t sample_freq_upper; /*!< audio stream sample frequency upper */ + }; + }; +} uac_host_dev_alt_param_t; + +/** + * @brief UAC driver configuration structure. +*/ +typedef struct { + bool create_background_task; /*!< When set to true, background task handling USB events is created. + Otherwise user has to periodically call uac_host_handle_events function */ + size_t task_priority; /*!< Task priority of created background task */ + size_t stack_size; /*!< Stack size of created background task */ + BaseType_t core_id; /*!< Select core on which background task will run or tskNO_AFFINITY */ + uac_host_driver_event_cb_t callback; /*!< Callback invoked when UAC driver event occurs. Must not be NULL. */ + void *callback_arg; /*!< User provided argument passed to callback */ +} uac_host_driver_config_t; + +/** + * @brief UAC logic device/interface configuration structure + * +*/ +typedef struct { + uint8_t addr; /*!< USB Address of connected physical device */ + uint8_t iface_num; /*!< UAC Interface Number */ + uint32_t buffer_size; /*!< Audio buffer size */ + uint32_t buffer_threshold; /*!< Audio buffer threshold */ + uac_host_device_event_cb_t callback; /*!< Callback invoked when UAC device event occurs */ + void *callback_arg; /*!< User provided argument passed to callback */ +} uac_host_device_config_t; + +/** + * @brief UAC stream configuration structure + * +*/ +typedef struct { + uint8_t channels; /*!< Audio channel number */ + uint8_t bit_resolution; /*!< Audio bit resolution */ + uint32_t sample_freq; /*!< Audio sample resolution */ + uint16_t flags; /*!< Control flags */ +} uac_host_stream_config_t; + +// ----------------------------- Public --------------------------------------- +/** + * @brief Install USB Host UAC Class driver + * + * @note This function must be called after usb_host_install + * + * @param[in] config UAC driver configuration structure + * @return esp_err_r + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if UAC driver is already installed + * - ESP_ERR_INVALID_ARG if the configuration is invalid + * - ESP_ERR_NO_MEM if memory allocation failed + * +*/ +esp_err_t uac_host_install(const uac_host_driver_config_t *config); + +/** + * @brief Uninstall USB Host UAC Class driver + * + * @note This function can only be called after all open devices have been closed + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if UAC driver is not installed or device is still opened + */ +esp_err_t uac_host_uninstall(void); + +/** + * @brief Open a UAC logic device/interface + * + * @param[in] config Pointer to UAC device configuration structure + * @param[out] uac_dev_handle Pointer to UAC device handle + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if UAC driver is not installed + * - ESP_ERR_INVALID_ARG if the configuration is invalid + * - ESP_ERR_NO_MEM if memory allocation failed + * - ESP_ERR_NOT_SUPPORTED if the UAC version is not supported + */ +esp_err_t uac_host_device_open(const uac_host_device_config_t *config, uac_host_device_handle_t *uac_dev_handle); + +/** + * @brief Close a UAC logic device/interface + * + * @param[in] uac_dev_handle UAC device handle + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if UAC driver is not installed + * - ESP_ERR_INVALID_ARG if the device handle is invalid + */ +esp_err_t uac_host_device_close(uac_host_device_handle_t uac_dev_handle); + +/** + * @brief Get UAC device information + * + * @param[in] uac_dev_handle UAC device handle + * @param[out] uac_dev_info Pointer to UAC device information structure + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if UAC device is not opened + * - ESP_ERR_INVALID_ARG if the device handle is invalid + */ +esp_err_t uac_host_get_device_info(uac_host_device_handle_t uac_dev_handle, uac_host_dev_info_t *uac_dev_info); + +/** + * @brief Get UAC device alt setting parameters by interface alternate index + * + * @param[in] uac_dev_handle UAC device handle + * @param[in] iface_alt Interface alt setting number + * @param[out] uac_alt_param Pointer to UAC device alt setting parameters + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if UAC device is not opened + * - ESP_ERR_INVALID_ARG if the device handle or alt setting number is invalid + */ +esp_err_t uac_host_get_device_alt_param(uac_host_device_handle_t uac_dev_handle, uint8_t iface_alt, uac_host_dev_alt_param_t *uac_alt_param); + +/** + * @brief Print the UAC device information and alternate parameters + * + * @param[in] uac_dev_handle UAC device handle + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if UAC device is not opened + */ +esp_err_t uac_host_printf_device_param(uac_host_device_handle_t uac_dev_handle); + +/** + * @brief UAC Host USB event handler + * + * If UAC Host install was made with create_background_task=false configuration, + * application needs to handle USB Host events itself. + * Do not used if UAC host install was made with create_background_task=true configuration + * + * @param[in] timeout Timeout in ticks. For milliseconds, please use 'pdMS_TO_TICKS()' macros + * @return esp_err_t + * - ESP_OK on success (keep calling this function) + * - ESP_FAIL if the event handling is finished (stop calling this function) + */ +esp_err_t uac_host_handle_events(uint32_t timeout); + +// ------------------------ USB UAC Host driver API ---------------------------- +/** + * @brief Start a UAC stream with specific stream configuration (channels, bit resolution, sample frequency) + * + * @note set flags FLAG_STREAM_SUSPEND_AFTER_START to suspend stream after start + * + * @param[in] uac_dev_handle UAC device handle + * @param[in] stream_config Pointer to UAC stream configuration structure + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the device handle or stream configuration is invalid + * - ESP_ERR_NOT_FOUND if the stream configuration is not supported + * - ESP_ERR_INVALID_STATE if the device is not in the right state + * - ESP_ERR_NO_MEM if memory allocation failed + * - ESP_ERR_TIMEOUT if the control transfer timeout + */ +esp_err_t uac_host_device_start(uac_host_device_handle_t uac_dev_handle, const uac_host_stream_config_t *stream_config); + +/** + * @brief Suspend a UAC stream + * + * @param[in] uac_dev_handle UAC device handle + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the device handle is invalid + * - ESP_ERR_INVALID_STATE if the device is not in the right state + */ +esp_err_t uac_host_device_suspend(uac_host_device_handle_t uac_dev_handle); + +/** + * @brief Resume a UAC stream with same stream configuration + * + * @param[in] uac_dev_handle UAC device handle + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the device handle is invalid + * - ESP_ERR_INVALID_STATE if the device is not in the right state + */ +esp_err_t uac_host_device_resume(uac_host_device_handle_t uac_dev_handle); + +/** + * @brief Stop a UAC stream, stream resources will be released + * + * @param[in] uac_dev_handle UAC device handle + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the device handle is invalid + * - ESP_ERR_INVALID_STATE if the device is not in the right state + */ +esp_err_t uac_host_device_stop(uac_host_device_handle_t uac_dev_handle); + +/** + * @brief Read data from UAC stream buffer, only available after stream started + * + * @param[in] uac_dev_handle UAC device handle + * @param[out] data Pointer to the buffer to store the data + * @param[in] size Number of bytes to read + * @param[out] bytes_read Pointer to the number of bytes read + * @param[in] timeout Timeout in ticks. For milliseconds, please use 'pdMS_TO_TICKS()' macros + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the device handle or data is invalid + * - ESP_ERR_INVALID_STATE if the device is not in the right state + */ +esp_err_t uac_host_device_read(uac_host_device_handle_t uac_dev_handle, uint8_t *data, uint32_t size, + uint32_t *bytes_read, uint32_t timeout); + +/** + * @brief Write data to UAC stream buffer, only can be called after stream started + * + * @note The data will be sent to internal ringbuffer before function return, + * the actual data transfer is scheduled by the background task. + * + * @param[in] uac_dev_handle UAC device handle + * @param[in] data Pointer to the data buffer + * @param[in] size Number of bytes to write + * @param[in] timeout Timeout in ticks. For milliseconds, please use 'pdMS_TO_TICKS()' macros + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the device handle or data is invalid + * - ESP_ERR_INVALID_STATE if the device is not in the right state + * - ESP_FAIL if write failed or timeout +*/ +esp_err_t uac_host_device_write(uac_host_device_handle_t uac_dev_handle, uint8_t *data, uint32_t size, + uint32_t timeout); + +/** + * @brief Mute or un-mute the UAC device + * @param[in] iface Pointer to UAC interface structure + * @param[in] mute True to mute, false to unmute + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the device is not ready or active + * - ESP_ERR_INVALID_ARG if the device handle is invalid + * - ESP_ERR_NOT_SUPPORTED if the device does not support mute control + * - ESP_ERR_TIMEOUT if the control timed out + */ +esp_err_t uac_host_device_set_mute(uac_host_device_handle_t uac_dev_handle, bool mute); + +/** + * @brief Set the volume of the UAC device + * @param[in] iface Pointer to UAC interface structure + * @param[in] volume Volume to set, 0-100 + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the device is not ready or active + * - ESP_ERR_INVALID_ARG if the device handle is invalid or volume is out of range + * - ESP_ERR_NOT_SUPPORTED if the device does not support volume control + * - ESP_ERR_TIMEOUT if the control timed out + */ +esp_err_t uac_host_device_set_volume(uac_host_device_handle_t uac_dev_handle, uint8_t volume); + +/** + * @brief Set the volume of the UAC device in dB + * @param[in] iface Pointer to UAC interface structure + * @param[in] volume_db Volume to set, db + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the device is not ready or active + * - ESP_ERR_INVALID_ARG if the device handle is invalid + * - ESP_ERR_NOT_SUPPORTED if the device does not support volume control + * - ESP_ERR_TIMEOUT if the control timed out + */ +esp_err_t uac_host_device_set_volume_db(uac_host_device_handle_t uac_dev_handle, uint32_t volume_db); + +#ifdef __cplusplus +} +#endif //__cplusplus diff --git a/host/class/uac/usb_host_uac/test_app/CMakeLists.txt b/host/class/uac/usb_host_uac/test_app/CMakeLists.txt new file mode 100644 index 00000000..52a3bdb9 --- /dev/null +++ b/host/class/uac/usb_host_uac/test_app/CMakeLists.txt @@ -0,0 +1,11 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +set(EXTRA_COMPONENT_DIRS + ../../usb_host_uac + ${EXTRA_COMPONENT_DIRS} + ) + +project(test_app_usb_host_uac) \ No newline at end of file diff --git a/host/class/uac/usb_host_uac/test_app/README.md b/host/class/uac/usb_host_uac/test_app/README.md new file mode 100644 index 00000000..2aeb7b47 --- /dev/null +++ b/host/class/uac/usb_host_uac/test_app/README.md @@ -0,0 +1,4 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# USB: UAC Class test application \ No newline at end of file diff --git a/host/class/uac/usb_host_uac/test_app/main/CMakeLists.txt b/host/class/uac/usb_host_uac/test_app/main/CMakeLists.txt new file mode 100644 index 00000000..0cf24d2e --- /dev/null +++ b/host/class/uac/usb_host_uac/test_app/main/CMakeLists.txt @@ -0,0 +1,9 @@ +include($ENV{IDF_PATH}/tools/cmake/version.cmake) + +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity usb usb_host_uac + EMBED_FILES new_epic.wav) + +# force-link test_host_uac.c +set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u test_uac_setup") diff --git a/host/class/uac/usb_host_uac/test_app/main/new_epic.wav b/host/class/uac/usb_host_uac/test_app/main/new_epic.wav new file mode 100644 index 00000000..59a21ab7 Binary files /dev/null and b/host/class/uac/usb_host_uac/test_app/main/new_epic.wav differ diff --git a/host/class/uac/usb_host_uac/test_app/main/test_app_main.c b/host/class/uac/usb_host_uac/test_app/main/test_app_main.c new file mode 100644 index 00000000..8a6bd49f --- /dev/null +++ b/host/class/uac/usb_host_uac/test_app/main/test_app_main.c @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "esp_heap_caps.h" + +static size_t before_free_8bit; +static size_t before_free_32bit; + +#define TEST_MEMORY_LEAK_THRESHOLD (-530) +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void app_main(void) +{ + // ____ ___ ___________________ __ __ + // | | \/ _____/\______ \ _/ |_ ____ _______/ |_ + // | | /\_____ \ | | _/ \ __\/ __ \ / ___/\ __\. + // | | / / \ | | \ | | \ ___/ \___ \ | | + // |______/ /_______ / |______ / |__| \___ >____ > |__| + // \/ \/ \/ \/ + printf(" ____ ___ ___________________ __ __ \r\n"); + printf("| | \\/ _____/\\______ \\ _/ |_ ____ _______/ |_ \r\n"); + printf("| | /\\_____ \\ | | _/ \\ __\\/ __ \\ / ___/\\ __\\\r\n"); + printf("| | / / \\ | | \\ | | \\ ___/ \\___ \\ | | \r\n"); + printf("|______/ /_______ / |______ / |__| \\___ >____ > |__| \r\n"); + printf(" \\/ \\/ \\/ \\/ \r\n"); + + UNITY_BEGIN(); + unity_run_menu(); + UNITY_END(); +} + +extern void test_uac_setup(void); +extern void test_uac_teardown(bool); + +/* setUp runs before every test */ +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + test_uac_setup(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + test_uac_teardown(false); + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} diff --git a/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c b/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c new file mode 100644 index 00000000..9f9629ee --- /dev/null +++ b/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c @@ -0,0 +1,904 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "unity.h" +#include "esp_private/usb_phy.h" +#include "usb/usb_host.h" +#include "usb/uac_host.h" + +// USB PHY for device disconnection emulation +static usb_phy_handle_t phy_hdl = NULL; +const static char *TAG = "UAC_TEST"; + +// ----------------------- Public ------------------------- +static EventGroupHandle_t s_evt_handle; +static QueueHandle_t s_event_queue = NULL; +static EventGroupHandle_t s_evt_handle = NULL; + +#define BIT0_USB_HOST_DRIVER_REMOVED (0x01 << 0) + +// Known microphone device parameters +#define UAC_DEV_PID 0x3307 +#define UAC_DEV_VID 0x349C + +#define UAC_DEV_MIC_IFACE_NUM 3 +#define UAC_DEV_MIC_IFACE_ALT_NUM 1 + +#define UAC_DEV_MIC_IFACE_ALT_1_CHANNELS 1 +#define UAC_DEV_MIC_IFACE_ALT_1_BIT_RESOLUTION 16 +#define UAC_DEV_MIC_IFACE_ALT_1_SAMPLE_FREQ_TYPE 1 +#define UAC_DEV_MIC_IFACE_ALT_1_SAMPLE_FREQ_1 8000 + +static const uint8_t UAC_DEV_MIC_IFACE_CHANNELS_ALT[UAC_DEV_MIC_IFACE_ALT_NUM] = {UAC_DEV_MIC_IFACE_ALT_1_CHANNELS}; +static const uint8_t UAC_DEV_MIC_IFACE_BIT_RESOLUTION_ALT[UAC_DEV_MIC_IFACE_ALT_NUM] = {UAC_DEV_MIC_IFACE_ALT_1_BIT_RESOLUTION}; +static const uint8_t UAC_DEV_MIC_IFACE_SAMPLE_FREQ_TPYE_ALT[UAC_DEV_MIC_IFACE_ALT_NUM] = {UAC_DEV_MIC_IFACE_ALT_1_SAMPLE_FREQ_TYPE}; +static const uint32_t UAC_DEV_MIC_IFACE_SAMPLE_FREQ_ALT[UAC_DEV_MIC_IFACE_ALT_NUM][UAC_DEV_MIC_IFACE_ALT_1_SAMPLE_FREQ_TYPE] = {{UAC_DEV_MIC_IFACE_ALT_1_SAMPLE_FREQ_1}}; + + +// Known speaker device parameters +#define UAC_DEV_SPK_IFACE_NUM 4 +#define UAC_DEV_SPK_IFACE_ALT_NUM 1 + +#define UAC_DEV_SPK_IFACE_ALT_1_CHANNELS 1 +#define UAC_DEV_SPK_IFACE_ALT_1_BIT_RESOLUTION 16 +#define UAC_DEV_SPK_IFACE_ALT_1_SAMPLE_FREQ_TYPE 1 +#define UAC_DEV_SPK_IFACE_ALT_1_SAMPLE_FREQ_1 8000 + +static const uint8_t UAC_DEV_SPK_IFACE_CHANNELS_ALT[UAC_DEV_SPK_IFACE_ALT_NUM] = {UAC_DEV_SPK_IFACE_ALT_1_CHANNELS}; +static const uint8_t UAC_DEV_SPK_IFACE_BIT_RESOLUTION_ALT[UAC_DEV_SPK_IFACE_ALT_NUM] = {UAC_DEV_SPK_IFACE_ALT_1_BIT_RESOLUTION}; +static const uint8_t UAC_DEV_SPK_IFACE_SAMPLE_FREQ_TPYE_ALT[UAC_DEV_SPK_IFACE_ALT_NUM] = {UAC_DEV_SPK_IFACE_ALT_1_SAMPLE_FREQ_TYPE}; +static const uint32_t UAC_DEV_SPK_IFACE_SAMPLE_FREQ_ALT[UAC_DEV_SPK_IFACE_ALT_NUM][UAC_DEV_SPK_IFACE_ALT_1_SAMPLE_FREQ_TYPE] = {{UAC_DEV_SPK_IFACE_ALT_1_SAMPLE_FREQ_1}}; + + +static void force_conn_state(bool connected, TickType_t delay_ticks) +{ + if (!phy_hdl) { + // P4 currently not support phy operation + return; + } + if (delay_ticks > 0) { + //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0. + vTaskDelay(delay_ticks); + } + TEST_ASSERT_EQUAL(ESP_OK, usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN)); +} + +typedef enum { + APP_EVENT = 0, + UAC_DRIVER_EVENT, + UAC_DEVICE_EVENT, +} event_group_t; + +typedef enum { + DRIVER_REMOVE = 1, +} user_event_t; + +typedef struct { + event_group_t event_group; + union { + struct { + uac_host_driver_event_t event; + uint8_t addr; + uint8_t iface_num; + void *arg; + } driver_evt; + struct { + uac_host_driver_event_t event; + uac_host_device_handle_t handle; + void *arg; + } device_evt; + }; +} event_queue_t; + +static void uac_device_callback(uac_host_device_handle_t uac_device_handle, const uac_host_device_event_t event, void *arg) +{ + if (event == UAC_HOST_DRIVER_EVENT_DISCONNECTED) { + ESP_LOGI(TAG, "UAC Device disconnected"); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(uac_device_handle)); + return; + } + // Send uac device event to the event queue + event_queue_t evt_queue = { + .event_group = UAC_DEVICE_EVENT, + .device_evt.handle = uac_device_handle, + .device_evt.event = event, + .device_evt.arg = arg + }; + // should not block here + xQueueSend(s_event_queue, &evt_queue, 0); +} + +static void uac_host_lib_callback(uint8_t addr, uint8_t iface_num, const uac_host_driver_event_t event, void *arg) +{ + // Send uac driver event to the event queue + event_queue_t evt_queue = { + .event_group = UAC_DRIVER_EVENT, + .driver_evt.addr = addr, + .driver_evt.iface_num = iface_num, + .driver_evt.event = event, + .driver_evt.arg = arg + }; + xQueueSend(s_event_queue, &evt_queue, 0); +} + +/** + * @brief Start USB Host install and handle common USB host library events while app pin not low + * + * @param[in] arg Not used + */ +static void usb_lib_task(void *arg) +{ + // Initialize the internal USB PHY to connect to the USB OTG peripheral. + // We manually install the USB PHY for testing + usb_phy_config_t phy_config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl)); + + const usb_host_config_t host_config = { + .skip_phy_setup = true, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + + TEST_ASSERT_EQUAL(ESP_OK, usb_host_install(&host_config)); + ESP_LOGI(TAG, "USB Host installed"); + xTaskNotifyGive(arg); + + bool all_clients_gone = false; + bool all_dev_free = false; + while (!all_clients_gone || !all_dev_free) { + // Start handling system events + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + printf("No more clients\n"); + usb_host_device_free_all(); + all_clients_gone = true; + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + printf("All devices freed\n"); + all_dev_free = true; + } + } + + ESP_LOGI(TAG, "USB Host shutdown"); + // Clean up USB Host + vTaskDelay(10); // Short delay to allow clients clean-up + TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall()); + TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl)); //Tear down USB PHY + phy_hdl = NULL; + // set bit BIT0_USB_HOST_DRIVER_REMOVED to notify driver removed + xEventGroupSetBits(s_evt_handle, BIT0_USB_HOST_DRIVER_REMOVED); + vTaskDelete(NULL); +} + +/** + * @brief Setups UAC testing + * + * - Create USB lib task + * - Install UAC Host driver + */ +void test_uac_setup(void) +{ + // create a queue to handle events + s_event_queue = xQueueCreate(16, sizeof(event_queue_t)); + TEST_ASSERT_NOT_NULL(s_event_queue); + s_evt_handle = xEventGroupCreate(); + TEST_ASSERT_NOT_NULL(s_evt_handle); + static TaskHandle_t uac_task_handle = NULL; + // create USB lib task, pass the current task handle to notify when the task is created + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(usb_lib_task, + "usb_events", + 4096, + xTaskGetCurrentTaskHandle(), + 5, &uac_task_handle, 0)); + + // install uac host driver + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + uac_host_driver_config_t uac_config = { + .create_background_task = true, + .task_priority = 5, + .stack_size = 4096, + .core_id = 0, + .callback = uac_host_lib_callback, + .callback_arg = NULL + }; + + TEST_ASSERT_EQUAL(ESP_OK, uac_host_install(&uac_config)); + ESP_LOGI(TAG, "UAC Class Driver installed"); +} + +void test_uac_queue_reset(void) +{ + xQueueReset(s_event_queue); +} + +void test_uac_teardown(bool force) +{ + if (force) { + force_conn_state(false, pdMS_TO_TICKS(1000)); + } + vTaskDelay(500); + // uninstall uac host driver + ESP_LOGI(TAG, "UAC Driver uninstall"); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_uninstall()); + // Wait for USB lib task to finish + xEventGroupWaitBits(s_evt_handle, BIT0_USB_HOST_DRIVER_REMOVED, pdTRUE, pdTRUE, portMAX_DELAY); + // delete event queue and event group + vQueueDelete(s_event_queue); + vEventGroupDelete(s_evt_handle); + // delay to allow task to delete + vTaskDelay(100); +} + +void test_open_mic_device(uint8_t iface_num, uint32_t buffer_size, uint32_t buffer_threshold, uac_host_device_handle_t *uac_device_handle) +{ + // check if device params as expected + const uac_host_device_config_t dev_config = { + .addr = 1, + .iface_num = iface_num, + .buffer_size = buffer_size, + .buffer_threshold = buffer_threshold, + .callback = uac_device_callback, + .callback_arg = NULL, + }; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_open(&dev_config, uac_device_handle)); +} + +void test_open_spk_device(uint8_t iface_num, uint32_t buffer_size, uint32_t buffer_threshold, uac_host_device_handle_t *uac_device_handle) +{ + // check if device params as expected + const uac_host_device_config_t dev_config = { + .addr = 1, + .iface_num = iface_num, + .buffer_size = buffer_size, + .buffer_threshold = buffer_threshold, + .callback = uac_device_callback, + .callback_arg = NULL, + }; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_open(&dev_config, uac_device_handle)); +} + +void test_close_device(uac_host_device_handle_t uac_device_handle) +{ + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(uac_device_handle)); +} + +void test_handle_dev_connection(uint8_t *iface_num, uint8_t *if_rx) +{ + event_queue_t evt_queue = {0}; + // ignore the first connected event + TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(s_event_queue, &evt_queue, portMAX_DELAY)); + TEST_ASSERT_EQUAL(UAC_DRIVER_EVENT, evt_queue.event_group); + TEST_ASSERT_EQUAL(1, evt_queue.driver_evt.addr); + if (iface_num) { + *iface_num = evt_queue.driver_evt.iface_num; + } + if (if_rx) { + *if_rx = evt_queue.driver_evt.event == UAC_HOST_DRIVER_EVENT_RX_CONNECTED ? 1 : 0; + } +} + +/** + * @brief Test with known UAC device, check if the device's parameters are parsed correctly + * @note please modify the known device parameters if the device is changed + */ +TEST_CASE("test uac device handling", "[uac_host][known_device]") +{ + // handle device connection + uint8_t mic_iface_num = 0; + uint8_t spk_iface_num = 0; + test_handle_dev_connection(&mic_iface_num, NULL); + TEST_ASSERT_EQUAL(UAC_DEV_MIC_IFACE_NUM, mic_iface_num); + test_handle_dev_connection(&spk_iface_num, NULL); + TEST_ASSERT_EQUAL(UAC_DEV_SPK_IFACE_NUM, spk_iface_num); + uint8_t test_counter = 0; + + while (++test_counter < 5) { + // check if mic device params as expected + uac_host_device_handle_t mic_device_handle = NULL; + uac_host_dev_info_t dev_info; + test_open_mic_device(mic_iface_num, 16000, 4000, &mic_device_handle); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_info(mic_device_handle, &dev_info)); + TEST_ASSERT_EQUAL(UAC_STREAM_RX, dev_info.type); + ESP_LOGI(TAG, "UAC Device opened: MIC"); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_printf_device_param(mic_device_handle)); + TEST_ASSERT_EQUAL(UAC_DEV_PID, dev_info.PID); + TEST_ASSERT_EQUAL(UAC_DEV_VID, dev_info.VID); + TEST_ASSERT_EQUAL(UAC_DEV_MIC_IFACE_NUM, dev_info.iface_num); + TEST_ASSERT_EQUAL(UAC_DEV_MIC_IFACE_ALT_NUM, dev_info.iface_alt_num); + printf("iManufacturer: %ls\n", dev_info.iManufacturer); + printf("iProduct: %ls\n", dev_info.iProduct); + printf("iSerialNumber: %ls\n", dev_info.iSerialNumber); + uac_host_dev_alt_param_t iface_alt_params; + for (int i = 0; i < dev_info.iface_alt_num; i++) { + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(mic_device_handle, i + 1, &iface_alt_params)); + TEST_ASSERT_EQUAL(UAC_DEV_MIC_IFACE_CHANNELS_ALT[i], iface_alt_params.channels); + TEST_ASSERT_EQUAL(UAC_DEV_MIC_IFACE_BIT_RESOLUTION_ALT[i], iface_alt_params.bit_resolution); + TEST_ASSERT_EQUAL(UAC_DEV_MIC_IFACE_SAMPLE_FREQ_TPYE_ALT[i], iface_alt_params.sample_freq_type); + // check frequency one by one + for (size_t j = 0; j < iface_alt_params.sample_freq_type; j++) { + TEST_ASSERT_EQUAL(UAC_DEV_MIC_IFACE_SAMPLE_FREQ_ALT[i][j], iface_alt_params.sample_freq[j]); + } + } + + // check if spk device params as expected + uac_host_device_handle_t spk_device_handle = NULL; + test_open_spk_device(spk_iface_num, 16000, 4000, &spk_device_handle); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_info(spk_device_handle, &dev_info)); + TEST_ASSERT_EQUAL(UAC_STREAM_TX, dev_info.type); + ESP_LOGI(TAG, "UAC Device opened: SPK"); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_printf_device_param(spk_device_handle)); + TEST_ASSERT_EQUAL(UAC_DEV_PID, dev_info.PID); + TEST_ASSERT_EQUAL(UAC_DEV_VID, dev_info.VID); + TEST_ASSERT_EQUAL(UAC_DEV_SPK_IFACE_NUM, dev_info.iface_num); + TEST_ASSERT_EQUAL(UAC_DEV_SPK_IFACE_ALT_NUM, dev_info.iface_alt_num); + printf("iManufacturer: %ls\n", dev_info.iManufacturer); + printf("iProduct: %ls\n", dev_info.iProduct); + printf("iSerialNumber: %ls\n", dev_info.iSerialNumber); + + for (int i = 0; i < dev_info.iface_alt_num; i++) { + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(spk_device_handle, i + 1, &iface_alt_params)); + TEST_ASSERT_EQUAL(UAC_DEV_SPK_IFACE_CHANNELS_ALT[i], iface_alt_params.channels); + TEST_ASSERT_EQUAL(UAC_DEV_SPK_IFACE_BIT_RESOLUTION_ALT[i], iface_alt_params.bit_resolution); + TEST_ASSERT_EQUAL(UAC_DEV_SPK_IFACE_SAMPLE_FREQ_TPYE_ALT[i], iface_alt_params.sample_freq_type); + for (size_t j = 0; j < iface_alt_params.sample_freq_type; j++) { + TEST_ASSERT_EQUAL(UAC_DEV_SPK_IFACE_SAMPLE_FREQ_ALT[i][j], iface_alt_params.sample_freq[j]); + } + } + + // close the device + test_close_device(mic_device_handle); + test_close_device(spk_device_handle); + // reset the queue + test_uac_queue_reset(); + } +} + +/** + * @brief record the rx stream data from microphone + */ +TEST_CASE("test uac rx reading", "[uac_host][rx]") +{ + uint8_t mic_iface_num = 0; + uint8_t spk_iface_num = 0; + uint8_t if_rx = false; + test_handle_dev_connection(&mic_iface_num, &if_rx); + if (!if_rx) { + spk_iface_num = mic_iface_num; + test_handle_dev_connection(&mic_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, true); + } else { + test_handle_dev_connection(&spk_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, false); + } + + const uint32_t buffer_threshold = 4800; + const uint32_t buffer_size = 19200; + + uac_host_device_handle_t uac_device_handle = NULL; + test_open_mic_device(mic_iface_num, buffer_size, buffer_threshold, &uac_device_handle); + + // start the device with first alt interface params + uac_host_dev_alt_param_t iface_alt_params; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(uac_device_handle, 1, &iface_alt_params)); + const uac_host_stream_config_t stream_config = { + .channels = iface_alt_params.channels, + .bit_resolution = iface_alt_params.bit_resolution, + .sample_freq = iface_alt_params.sample_freq[0], + }; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(uac_device_handle, &stream_config)); + // Most device support mute and volume control. if not, comment out the following two lines + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(uac_device_handle, 0)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_volume(uac_device_handle, 80)); + + uint8_t *rx_buffer = (uint8_t *)calloc(1, buffer_threshold); + TEST_ASSERT_NOT_NULL(rx_buffer); + uint32_t rx_size = 0; + // got 5s data, then stop the stream + const uint32_t timeout = 5000; + uint32_t time_counter = 0; + event_queue_t evt_queue = {0}; + ESP_LOGI(TAG, "Start reading data from MIC"); + while (1) { + if (xQueueReceive(s_event_queue, &evt_queue, portMAX_DELAY)) { + TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); + uac_host_device_handle_t uac_device_handle = evt_queue.device_evt.handle; + uac_host_device_event_t event = evt_queue.device_evt.event; + switch (event) { + case UAC_HOST_DEVICE_EVENT_RX_DONE: + uac_host_device_read(uac_device_handle, rx_buffer, buffer_threshold, &rx_size, 0); + TEST_ASSERT_EQUAL(buffer_threshold, rx_size); + time_counter += rx_size / (iface_alt_params.channels * iface_alt_params.bit_resolution / 8 * iface_alt_params.sample_freq[0] / 1000); + if (time_counter >= timeout) { + goto exit_rx; + } + break; + default: + TEST_ASSERT(0); + break; + } + } + } +exit_rx: + ESP_LOGI(TAG, "Stop reading data from MIC"); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(uac_device_handle, 1)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(uac_device_handle)); + + free(rx_buffer); +} + +/** + * @brief playback the wav sound to speaker, the wav will be down-sampled + * if the device's sample frequency is not matched + */ +TEST_CASE("test uac tx writing", "[uac_host][tx]") +{ + // handle device connection + uint8_t mic_iface_num = 0; + uint8_t spk_iface_num = 0; + uint8_t if_rx = false; + test_handle_dev_connection(&mic_iface_num, &if_rx); + if (!if_rx) { + spk_iface_num = mic_iface_num; + test_handle_dev_connection(&mic_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, true); + } else { + test_handle_dev_connection(&spk_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, false); + } + + // source wav file format + const uint32_t sample_freq = 48000; + const uint8_t channels = 2; + const uint8_t bit_resolution = 16; + uint32_t tx_buffer_size = 19200; + uint32_t tx_buffer_threshold = 4800; + + uac_host_device_handle_t uac_device_handle = NULL; + test_open_spk_device(spk_iface_num, tx_buffer_size, tx_buffer_threshold, &uac_device_handle); + uac_host_dev_alt_param_t spk_alt_params; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(uac_device_handle, 1, &spk_alt_params)); + + // open the device with the wav file's format + const uac_host_stream_config_t stream_config = { + .channels = spk_alt_params.channels, + .bit_resolution = spk_alt_params.bit_resolution, + .sample_freq = spk_alt_params.sample_freq[0], + .flags = FLAG_STREAM_SUSPEND_AFTER_START, + }; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(uac_device_handle, &stream_config)); + + extern const uint8_t wav_file_start[] asm("_binary_new_epic_wav_start"); + extern const uint8_t wav_file_end[] asm("_binary_new_epic_wav_end"); + + const uint16_t *s_buffer = (uint16_t *)(wav_file_start + 44); + uint16_t *tx_buffer = calloc(1, tx_buffer_threshold); + TEST_ASSERT_NOT_NULL(tx_buffer); + uint32_t tx_size = tx_buffer_threshold; + + int freq_offsite_step = sample_freq / spk_alt_params.sample_freq[0]; + int downsampling_bits = bit_resolution - spk_alt_params.bit_resolution; + int offset_size = tx_size / (spk_alt_params.bit_resolution / 8); + // we can only support adjust from 2 channels to 1 channel + bool channels_adjust = channels != spk_alt_params.channels ? true : false; + + // fill the tx buffer with wav file data + for (size_t i = 0; i < offset_size; i++) { + tx_buffer[i] = s_buffer[i * freq_offsite_step] >> downsampling_bits; + } + + if (downsampling_bits == 8) { + // move buffer to the correct position + for (size_t i = 0; i < offset_size; i++) { + *((uint8_t *)tx_buffer + i) = *((uint8_t *)tx_buffer + i * 2); + } + tx_size = tx_size / 2; + } else if (downsampling_bits) { + ESP_LOGE(TAG, "Unsupported downsampling bits %d", downsampling_bits); + } + + if (channels_adjust) { + // convert stereo to mono + if (downsampling_bits == 8) { + tx_size = tx_size / 2; + for (size_t i = 0; i < tx_size; i++) { + *((uint8_t *)tx_buffer + i) = *((uint8_t *)tx_buffer + i * 2); + } + } else { + tx_size = tx_size / 2; + for (size_t i = 0; i < tx_size; i++) { + tx_buffer[i] = tx_buffer[2 * i]; + } + } + } + s_buffer += offset_size * freq_offsite_step; + + // invalid operate + uint32_t volume = 10; + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, uac_host_device_write(uac_device_handle, (uint8_t *)tx_buffer, tx_size, 0)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(uac_device_handle, 0)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_volume(uac_device_handle, volume)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(uac_device_handle)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_write(uac_device_handle, (uint8_t *)tx_buffer, tx_size, 0)); + + // playback from volume 10 to 100 + const uint32_t volume_max = 100; + event_queue_t evt_queue = {0}; + while (1) { + if (xQueueReceive(s_event_queue, &evt_queue, portMAX_DELAY)) { + TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); + uac_host_device_handle_t uac_device_handle = evt_queue.device_evt.handle; + uac_host_device_event_t event = evt_queue.device_evt.event; + switch (event) { + case UAC_HOST_DEVICE_EVENT_TX_DONE: + if ((uint32_t)(s_buffer + offset_size) > (uint32_t)wav_file_end) { + s_buffer = (uint16_t *)(wav_file_start + 44); + volume += 20; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_volume(uac_device_handle, volume > 100 ? 100 : volume)); + printf("Volume: %" PRIu32 "\n", volume > 100 ? 100 : volume); + } else { + // fill the tx buffer with wav file data + tx_size = tx_buffer_threshold; + for (size_t i = 0; i < offset_size; i++) { + tx_buffer[i] = s_buffer[i * freq_offsite_step] >> downsampling_bits; + } + if (downsampling_bits == 8) { + // move buffer to the correct position + for (size_t i = 0; i < offset_size; i++) { + *((uint8_t *)tx_buffer + i) = *((uint8_t *)tx_buffer + i * 2); + } + tx_size = tx_size / 2; + } else if (downsampling_bits) { + ESP_LOGE(TAG, "Unsupported downsampling bits %d", downsampling_bits); + } + if (channels_adjust) { + // convert stereo to mono + if (downsampling_bits == 8) { + tx_size = tx_size / 2; + for (size_t i = 0; i < tx_size; i++) { + *((uint8_t *)tx_buffer + i) = *((uint8_t *)tx_buffer + i * 2); + } + } else { + tx_size = tx_size / 2; + for (size_t i = 0; i < tx_size; i++) { + tx_buffer[i] = tx_buffer[2 * i]; + } + } + } + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_write(uac_device_handle, (uint8_t *)tx_buffer, tx_size, 0)); + s_buffer += offset_size * freq_offsite_step; + } + if (volume > volume_max) { + goto exit_tx; + } + break; + default: + TEST_ASSERT(0); + break; + } + } + } + +exit_tx: + free(tx_buffer); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(uac_device_handle, 1)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(uac_device_handle)); +} + +/** + * @brief loopback the microphone received data to speaker + * please use a headset with microphone and speaker to test this function + */ +TEST_CASE("test uac tx rx loopback", "[uac_host][tx][rx]") +{ + // handle device connection + uint8_t mic_iface_num = 0; + uint8_t spk_iface_num = 0; + uint8_t if_rx = false; + test_handle_dev_connection(&mic_iface_num, &if_rx); + if (!if_rx) { + spk_iface_num = mic_iface_num; + test_handle_dev_connection(&mic_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, true); + } else { + test_handle_dev_connection(&spk_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, false); + } + + const uint32_t rx_buffer_size = 19200; + const uint32_t rx_buffer_threshold = 4800; + + uac_host_device_handle_t mic_device_handle = NULL; + test_open_mic_device(mic_iface_num, rx_buffer_size, rx_buffer_threshold, &mic_device_handle); + uac_host_device_handle_t spk_device_handle = NULL; + // set same params to spk device + test_open_spk_device(spk_iface_num, rx_buffer_size, rx_buffer_threshold, &spk_device_handle); + + // get mic alt interface 1 params, set same params to spk device + uac_host_dev_alt_param_t mic_alt_params; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(mic_device_handle, 1, &mic_alt_params)); + + uac_host_stream_config_t stream_config = { + .channels = mic_alt_params.channels, + .bit_resolution = mic_alt_params.bit_resolution, + .sample_freq = mic_alt_params.sample_freq[0], + .flags = FLAG_STREAM_SUSPEND_AFTER_START, + }; + + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(mic_device_handle, &stream_config)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(mic_device_handle, 0)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_volume(mic_device_handle, 80)); + + uac_host_dev_alt_param_t spk_alt_params; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(spk_device_handle, 1, &spk_alt_params)); + + // some usb headset may have one channel for mic and two channels for speaker + bool channel_mismatch = false; + if (spk_alt_params.channels != mic_alt_params.channels) { + if (mic_alt_params.channels == 1 && spk_alt_params.channels == 2) { + ESP_LOGW(TAG, "Speaker channels %u and microphone channels %u are not the same", spk_alt_params.channels, mic_alt_params.channels); + stream_config.channels = 2; + channel_mismatch = true; + } else { + ESP_LOGE(TAG, "Speaker channels %u and microphone channels %u are not supported", spk_alt_params.channels, mic_alt_params.channels); + TEST_ASSERT(0); + } + } + + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(spk_device_handle, &stream_config)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(spk_device_handle, 0)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_volume(spk_device_handle, 80)); + + uint8_t *rx_buffer = (uint8_t *)calloc(1, rx_buffer_threshold); + uint8_t *rx_buffer_stereo = NULL; + if (channel_mismatch) { + rx_buffer_stereo = (uint8_t *)calloc(1, rx_buffer_threshold * 2); + TEST_ASSERT_NOT_NULL(rx_buffer_stereo); + } + TEST_ASSERT_NOT_NULL(rx_buffer); + uint32_t rx_size = 0; + // got 5s data, then stop the stream + const uint32_t timeout = 5000; + const uint32_t test_times = 3; + uint32_t time_counter = 0; + uint32_t test_counter = 0; + event_queue_t evt_queue = {0}; + while (1) { + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(mic_device_handle)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(spk_device_handle)); + while (1) { + if (xQueueReceive(s_event_queue, &evt_queue, portMAX_DELAY)) { + TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); + uac_host_device_event_t event = evt_queue.device_evt.event; + esp_err_t ret = ESP_FAIL; + switch (event) { + case UAC_HOST_DEVICE_EVENT_RX_DONE: + // read as much as possible + do { + ret = uac_host_device_read(mic_device_handle, rx_buffer, rx_buffer_threshold, &rx_size, 0); + if (ret == ESP_OK) { + if (channel_mismatch) { + // convert mono to stereo + if (mic_alt_params.bit_resolution == 16) { + for (size_t i = 0; i < rx_size; i += 2) { + rx_buffer_stereo[i * 2] = rx_buffer[i]; + rx_buffer_stereo[i * 2 + 1] = rx_buffer[i + 1]; + rx_buffer_stereo[i * 2 + 2] = rx_buffer[i]; + rx_buffer_stereo[i * 2 + 3] = rx_buffer[i + 1]; + } + ret = uac_host_device_write(spk_device_handle, rx_buffer_stereo, rx_size * 2, 0); + } else { + for (size_t i = 0; i < rx_size; i++) { + rx_buffer_stereo[i * 2] = rx_buffer[i]; + rx_buffer_stereo[i * 2 + 1] = rx_buffer[i]; + } + ret = uac_host_device_write(spk_device_handle, rx_buffer_stereo, rx_size * 2, 0); + } + } else { + ret = uac_host_device_write(spk_device_handle, rx_buffer, rx_size, 0); + } + time_counter += rx_size / (mic_alt_params.channels * mic_alt_params.bit_resolution / 8 * mic_alt_params.sample_freq[0] / 1000); + } + } while (ret == ESP_OK); + + if (time_counter >= timeout) { + goto restart_rx; + } + break; + case UAC_HOST_DEVICE_EVENT_TX_DONE: + // we do nothing here, just wait for the rx done event + break; + default: + TEST_ASSERT(0); + break; + } + } + } +restart_rx: + if (++test_counter >= test_times) { + goto exit_rx; + } + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_suspend(mic_device_handle)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_suspend(spk_device_handle)); + time_counter = 0; + vTaskDelay(100); + } + +exit_rx: + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(spk_device_handle, 1)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(spk_device_handle)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(mic_device_handle, 1)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(mic_device_handle)); + free(rx_buffer); + if (rx_buffer_stereo) { + free(rx_buffer_stereo); + } +} + +/** + * @brief: Test disconnect the device when the stream is running + * @note: Currently, the P4 PHY can't be controlled to emulate the hot-plug event, + * so the test is disabled + */ +#if !CONFIG_IDF_TARGET_ESP32P4 +TEST_CASE("test uac tx rx loopback with disconnect", "[uac_host][tx][rx][hot-plug]") +{ + // handle device connection + uint8_t mic_iface_num = 0; + uint8_t spk_iface_num = 0; + uint8_t if_rx = false; + test_handle_dev_connection(&mic_iface_num, &if_rx); + if (!if_rx) { + spk_iface_num = mic_iface_num; + test_handle_dev_connection(&mic_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, true); + } else { + test_handle_dev_connection(&spk_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, false); + } + + const uint32_t rx_buffer_size = 19200; + const uint32_t rx_buffer_threshold = 4800; + + uac_host_device_handle_t mic_device_handle = NULL; + test_open_mic_device(mic_iface_num, rx_buffer_size, rx_buffer_threshold, &mic_device_handle); + uac_host_device_handle_t spk_device_handle = NULL; + // set same params to spk device + test_open_spk_device(spk_iface_num, rx_buffer_size, rx_buffer_threshold, &spk_device_handle); + + // get mic alt interface 1 params, set same params to spk device + uac_host_dev_alt_param_t mic_alt_params; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(mic_device_handle, 1, &mic_alt_params)); + + uac_host_stream_config_t stream_config = { + .channels = mic_alt_params.channels, + .bit_resolution = mic_alt_params.bit_resolution, + .sample_freq = mic_alt_params.sample_freq[0], + .flags = FLAG_STREAM_SUSPEND_AFTER_START, + }; + + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(mic_device_handle, &stream_config)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(mic_device_handle, 0)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_volume(mic_device_handle, 80)); + + uac_host_dev_alt_param_t spk_alt_params; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(spk_device_handle, 1, &spk_alt_params)); + + // some usb headset may have one channel for mic and two channels for speaker + bool channel_mismatch = false; + if (spk_alt_params.channels != mic_alt_params.channels) { + if (mic_alt_params.channels == 1 && spk_alt_params.channels == 2) { + ESP_LOGW(TAG, "Speaker channels %u and microphone channels %u are not the same", spk_alt_params.channels, mic_alt_params.channels); + stream_config.channels = 2; + channel_mismatch = true; + } else { + ESP_LOGE(TAG, "Speaker channels %u and microphone channels %u are not supported", spk_alt_params.channels, mic_alt_params.channels); + TEST_ASSERT(0); + } + } + + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(spk_device_handle, &stream_config)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(spk_device_handle, 0)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_volume(spk_device_handle, 80)); + + uint8_t *rx_buffer = (uint8_t *)calloc(1, rx_buffer_threshold); + uint8_t *rx_buffer_stereo = NULL; + if (channel_mismatch) { + rx_buffer_stereo = (uint8_t *)calloc(1, rx_buffer_threshold * 2); + TEST_ASSERT_NOT_NULL(rx_buffer_stereo); + } + TEST_ASSERT_NOT_NULL(rx_buffer); + uint32_t rx_size = 0; + // got 5s data, then stop the stream + const uint32_t timeout = 1000; + const uint32_t test_times = 2; + uint32_t time_counter = 0; + uint32_t test_counter = 0; + event_queue_t evt_queue = {0}; + while (1) { + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(mic_device_handle)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(spk_device_handle)); + while (1) { + if (xQueueReceive(s_event_queue, &evt_queue, portMAX_DELAY)) { + TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); + uac_host_device_event_t event = evt_queue.device_evt.event; + esp_err_t ret = ESP_FAIL; + switch (event) { + case UAC_HOST_DEVICE_EVENT_RX_DONE: + // read as much as possible + do { + ret = uac_host_device_read(mic_device_handle, rx_buffer, rx_buffer_threshold, &rx_size, 0); + if (ret == ESP_OK) { + if (channel_mismatch) { + // convert mono to stereo + if (mic_alt_params.bit_resolution == 16) { + for (size_t i = 0; i < rx_size; i += 2) { + rx_buffer_stereo[i * 2] = rx_buffer[i]; + rx_buffer_stereo[i * 2 + 1] = rx_buffer[i + 1]; + rx_buffer_stereo[i * 2 + 2] = rx_buffer[i]; + rx_buffer_stereo[i * 2 + 3] = rx_buffer[i + 1]; + } + ret = uac_host_device_write(spk_device_handle, rx_buffer_stereo, rx_size * 2, 0); + } else { + for (size_t i = 0; i < rx_size; i++) { + rx_buffer_stereo[i * 2] = rx_buffer[i]; + rx_buffer_stereo[i * 2 + 1] = rx_buffer[i]; + } + ret = uac_host_device_write(spk_device_handle, rx_buffer_stereo, rx_size * 2, 0); + } + } else { + ret = uac_host_device_write(spk_device_handle, rx_buffer, rx_size, 0); + } + time_counter += rx_size / (mic_alt_params.channels * mic_alt_params.bit_resolution / 8 * mic_alt_params.sample_freq[0] / 1000); + } + } while (ret == ESP_OK); + + if (time_counter >= timeout) { + goto restart_rx; + } + break; + case UAC_HOST_DEVICE_EVENT_TX_DONE: + // we do nothing here, just wait for the rx done event + break; + default: + TEST_ASSERT(0); + break; + } + } + } +restart_rx: + if (++test_counter >= test_times) { + goto exit_rx; + } + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_suspend(mic_device_handle)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_suspend(spk_device_handle)); + time_counter = 0; + vTaskDelay(100); + } + +exit_rx: + force_conn_state(false, 0); + // Wait device be closed by callback + vTaskDelay(500); + free(rx_buffer); + if (rx_buffer_stereo) { + free(rx_buffer_stereo); + } +} +#endif diff --git a/host/class/uac/usb_host_uac/test_app/partitions.csv b/host/class/uac/usb_host_uac/test_app/partitions.csv new file mode 100644 index 00000000..88358a59 --- /dev/null +++ b/host/class/uac/usb_host_uac/test_app/partitions.csv @@ -0,0 +1,5 @@ +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 24k +phy_init, data, phy, 0xf000, 4k +factory, app, factory, , 2M diff --git a/host/class/uac/usb_host_uac/test_app/pytest_usb_host_uac.py b/host/class/uac/usb_host_uac/test_app/pytest_usb_host_uac.py new file mode 100644 index 00000000..a1ac613b --- /dev/null +++ b/host/class/uac/usb_host_uac/test_app/pytest_usb_host_uac.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +from typing import Tuple + +import pytest +from pytest_embedded_idf.dut import IdfDut + +# No runner marker, unable to mock UAC 1.0 device with tinyusb +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +def test_usb_host_uac(dut: IdfDut) -> None: + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('[uac_host]') + dut.expect_unity_test_output(timeout = 3000) diff --git a/host/class/uac/usb_host_uac/test_app/sdkconfig.defaults b/host/class/uac/usb_host_uac/test_app/sdkconfig.defaults new file mode 100644 index 00000000..4fd2fed0 --- /dev/null +++ b/host/class/uac/usb_host_uac/test_app/sdkconfig.defaults @@ -0,0 +1,12 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=2048 +CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT=y + +# Disable watchdogs, they'd get triggered during unity interactive menu +CONFIG_ESP_INT_WDT=n +CONFIG_ESP_TASK_WDT=n \ No newline at end of file diff --git a/host/class/uac/usb_host_uac/uac_descriptors.c b/host/class/uac/usb_host_uac/uac_descriptors.c new file mode 100644 index 00000000..bc5d6a44 --- /dev/null +++ b/host/class/uac/usb_host_uac/uac_descriptors.c @@ -0,0 +1,357 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "usb/usb_helpers.h" +#include "usb/usb_types_ch9.h" +#include "esp_check.h" +#include "usb/usb_host.h" +#include "usb/uac_host.h" + +// ----------------------------------------------- Descriptor Printing ------------------------------------------------- + +static void print_ep_desc(const usb_ep_desc_t *ep_desc) +{ + const char *ep_type_str; + int type = ep_desc->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK; + + switch (type) { + case USB_BM_ATTRIBUTES_XFER_CONTROL: + ep_type_str = "CTRL"; + break; + case USB_BM_ATTRIBUTES_XFER_ISOC: + ep_type_str = "ISOC"; + break; + case USB_BM_ATTRIBUTES_XFER_BULK: + ep_type_str = "BULK"; + break; + case USB_BM_ATTRIBUTES_XFER_INT: + ep_type_str = "INT"; + break; + default: + ep_type_str = NULL; + break; + } + + printf("\t\t*** Endpoint descriptor ***\n"); + printf("\t\tbLength %d\n", ep_desc->bLength); + printf("\t\tbDescriptorType %d\n", ep_desc->bDescriptorType); + printf("\t\tbEndpointAddress 0x%x\tEP %d %s\n", ep_desc->bEndpointAddress, + USB_EP_DESC_GET_EP_NUM(ep_desc), + USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT"); + printf("\t\tbmAttributes 0x%x\t%s\n", ep_desc->bmAttributes, ep_type_str); + printf("\t\twMaxPacketSize %d\n", USB_EP_DESC_GET_MPS(ep_desc)); + printf("\t\tbInterval %d\n", ep_desc->bInterval); +} + +static void usbh_print_intf_desc(const usb_intf_desc_t *intf_desc) +{ + printf("\t*** Interface descriptor ***\n"); + printf("\tbLength %d\n", intf_desc->bLength); + printf("\tbDescriptorType %d\n", intf_desc->bDescriptorType); + printf("\tbInterfaceNumber %d\n", intf_desc->bInterfaceNumber); + printf("\tbAlternateSetting %d\n", intf_desc->bAlternateSetting); + printf("\tbNumEndpoints %d\n", intf_desc->bNumEndpoints); + printf("\tbInterfaceClass 0x%x\n", intf_desc->bInterfaceClass); + printf("\tbInterfaceSubClass 0x%x\n", intf_desc->bInterfaceSubClass); + printf("\tbInterfaceProtocol 0x%x\n", intf_desc->bInterfaceProtocol); + printf("\tiInterface %d\n", intf_desc->iInterface); +} + +static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) +{ + printf("*** Configuration descriptor ***\n"); + printf("bLength %d\n", cfg_desc->bLength); + printf("bDescriptorType %d\n", cfg_desc->bDescriptorType); + printf("wTotalLength %d\n", cfg_desc->wTotalLength); + printf("bNumInterfaces %d\n", cfg_desc->bNumInterfaces); + printf("bConfigurationValue %d\n", cfg_desc->bConfigurationValue); + printf("iConfiguration %d\n", cfg_desc->iConfiguration); + printf("bmAttributes 0x%x\n", cfg_desc->bmAttributes); + printf("bMaxPower %dmA\n", cfg_desc->bMaxPower * 2); +} + +static void print_iad_desc(const usb_iad_desc_t *iad_desc) +{ + printf("*** Interface Association Descriptor ***\n"); + printf("bLength %d\n", iad_desc->bLength); + printf("bDescriptorType %d\n", iad_desc->bDescriptorType); + printf("bFirstInterface %d\n", iad_desc->bFirstInterface); + printf("bInterfaceCount %d\n", iad_desc->bInterfaceCount); + printf("bFunctionClass 0x%x\n", iad_desc->bFunctionClass); + printf("bFunctionSubClass 0x%x\n", iad_desc->bFunctionSubClass); + printf("bFunctionProtocol 0x%x\n", iad_desc->bFunctionProtocol); + printf("iFunction %d\n", iad_desc->iFunction); +} + +static void print_ac_header_desc(const uint8_t *buff) +{ + const uac_ac_header_desc_t *desc = (const uac_ac_header_desc_t *)buff; + printf("\t*** Audio control header descriptor ***\n"); + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); + printf("\tbcdADC 0x%x\n", desc->bcdADC); + printf("\twTotalLength %d\n", desc->wTotalLength); + printf("\tbInCollection %d\n", desc->bInCollection); + if (desc->bInCollection) { + const uint8_t *p_intf = desc->baInterfaceNr; + for (int i = 0; i < desc->bInCollection; ++i) { + printf("\t\tInterface number[%d] = %d\n", i, p_intf[i]); + } + } +} + +static void print_ac_input_desc(const uint8_t *buff) +{ + const uac_ac_input_terminal_desc_t *desc = (const uac_ac_input_terminal_desc_t *)buff; + printf("\t*** Audio control input terminal descriptor ***\n"); + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); + printf("\tbTerminalID %d\n", desc->bTerminalID); + printf("\twTerminalType 0x%x\n", desc->wTerminalType); + printf("\tbAssocTerminal %d\n", desc->bAssocTerminal); + printf("\tbNrChannels %d\n", desc->bNrChannels); + printf("\twChannelConfig 0x%04x\n", desc->wChannelConfig); + printf("\tiChannelNames %d\n", desc->iChannelNames); + printf("\tiTerminal %d\n", desc->iTerminal); +} + +static void print_ac_output_desc(const uint8_t *buff) +{ + const uac_ac_output_terminal_desc_t *desc = (const uac_ac_output_terminal_desc_t *)buff; + printf("\t*** Audio control output terminal descriptor ***\n"); + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); + printf("\tbTerminalID %d\n", desc->bTerminalID); + printf("\twTerminalType 0x%x\n", desc->wTerminalType); + printf("\tbAssocTerminal %d\n", desc->bAssocTerminal); + printf("\tbSourceID %d\n", desc->bSourceID); + printf("\tiTerminal %d\n", desc->iTerminal); +} + +static void print_ac_feature_desc(const uint8_t *buff) +{ + const uac_ac_feature_unit_desc_t *desc = (const uac_ac_feature_unit_desc_t *)buff; + printf("\t*** Audio control feature unit descriptor ***\n"); + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); + printf("\tbUnitID %d\n", desc->bUnitID); + printf("\tbSourceID %d\n", desc->bSourceID); + printf("\tbControlSize %d\n", desc->bControlSize); + for (size_t i = 0; i < (desc->bLength - 7) / desc->bControlSize; i += desc->bControlSize) { + printf("\tbmaControls[ch%d] 0x%x\n", i, desc->bmaControls[i]); + } + printf("\tiFeature %d\n", desc->iFeature); +} + +static void print_ac_mix_desc(const uint8_t *buff) +{ + const uac_ac_mixer_unit_desc_t *desc = (const uac_ac_mixer_unit_desc_t *)buff; + printf("\t*** Audio control mixer unit descriptor ***\n"); + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); + printf("\tbUnitID %d\n", desc->bUnitID); + printf("\tbNrInPins %d\n", desc->bNrInPins); + for (size_t i = 0; i < desc->bNrInPins; ++i) { + printf("\tbSourceID[%d] %d\n", i, desc->baSourceID[i]); + } + printf("\tbNrChannels %d\n", buff[5 + desc->bNrInPins]); + printf("\twChannelConfig 0x%x\n", buff[6 + desc->bNrInPins] | (buff[7 + desc->bNrInPins] << 8)); + printf("\tiChannelNames %d\n", buff[8 + desc->bNrInPins]); + printf("\tbmControls 0x%x\n", buff[9 + desc->bNrInPins]); + printf("\tiMixer %d\n", buff[10 + desc->bNrInPins]); +} + +static void print_ac_selector_desc(const uint8_t *buff) +{ + const uac_ac_selector_unit_desc_t *desc = (const uac_ac_selector_unit_desc_t *)buff; + printf("\t*** Audio control selector unit descriptor ***\n"); + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); + printf("\tbUnitID %d\n", desc->bUnitID); + printf("\tbNrInPins %d\n", desc->bNrInPins); + for (size_t i = 0; i < desc->bNrInPins; ++i) { + printf("\tbSourceID[%d] %d\n", i, desc->baSourceID[i]); + } + printf("\tiSelector %d\n", buff[5 + desc->bNrInPins]); +} + +static void parse_as_ep_general_desc(const uint8_t *buff) +{ + const uac_as_cs_ep_desc_t *desc = (const uac_as_cs_ep_desc_t *)buff; + printf("\t\t*** Audio stream endpoint general descriptor ***\n"); + printf("\t\tbLength %d\n", desc->bLength); + printf("\t\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\t\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); + printf("\t\tbmAttributes 0x%x\n", desc->bmAttributes); + printf("\t\tbLockDelayUnits %d\n", desc->bLockDelayUnits); + printf("\t\twLockDelay %d\n", desc->wLockDelay); +} + +static void parse_as_general_desc(const uint8_t *buff) +{ + const uac_as_general_desc_t *desc = (const uac_as_general_desc_t *)buff; + printf("\t*** Audio stream general descriptor ***\n"); + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); + printf("\tbTerminalLink %d\n", desc->bTerminalLink); + printf("\tbDelay %d\n", desc->bDelay); + printf("\twFormatTag %d\n", desc->wFormatTag); +} + +static void parse_as_type_desc(const uint8_t *buff) +{ + const uac_as_type_I_format_desc_t *desc = (const uac_as_type_I_format_desc_t *)buff; + printf("\t*** Audio stream format type descriptor ***\n"); + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); + printf("\tbFormatType %d\n", desc->bFormatType); + printf("\tbNrChannels %d\n", desc->bNrChannels); + printf("\tbSubframeSize %d\n", desc->bSubframeSize); + printf("\tbBitResolution %d\n", desc->bBitResolution); + printf("\tbSamFreqType %d\n", desc->bSamFreqType); + if (desc->bSamFreqType == 0) { + // Continuous Frame Intervals + const uint8_t *p_samfreq = desc->tSamFreq; + uint32_t min_samfreq = (p_samfreq[2] << 16) + (p_samfreq[1] << 8) + p_samfreq[0]; + uint32_t max_samfreq = (p_samfreq[5] << 16) + (p_samfreq[4] << 8) + p_samfreq[3]; + printf("\ttLowerSamFreq %"PRIu32"\n", min_samfreq); + printf("\ttUpperSamFreq %"PRIu32"\n", max_samfreq); + } else { + const uint8_t *p_samfreq = desc->tSamFreq; + for (int i = 0; i < desc->bSamFreqType; ++i) { + printf("\ttSamFreq[%d] %"PRIu32"\n", i, (uint32_t)((p_samfreq[3 * i + 2] << 16) + (p_samfreq[3 * i + 1] << 8) + p_samfreq[3 * i])); + } + } +} + +static void print_unknown_desc(const uac_desc_header_t *desc) +{ + printf("\t*** Unknown descriptor ***\n"); + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); +} + +static void print_uac_class_descriptors(const usb_standard_desc_t *desc, uint8_t class, uint8_t subclass, uint8_t protocol) +{ + if (class != USB_CLASS_AUDIO) { + return; + } + const uint8_t *buff = (const uint8_t *)desc; + uac_desc_header_t *header = (uac_desc_header_t *)desc; + if (subclass == UAC_SUBCLASS_AUDIOCONTROL) { + switch (header->bDescriptorSubtype) { + case UAC_AC_HEADER: + print_ac_header_desc(buff); + break; + case UAC_AC_INPUT_TERMINAL: + print_ac_input_desc(buff); + break; + case UAC_AC_OUTPUT_TERMINAL: + print_ac_output_desc(buff); + break; + case UAC_AC_FEATURE_UNIT: + print_ac_feature_desc(buff); + break; + case UAC_AC_MIXER_UNIT: + print_ac_mix_desc(buff); + break; + case UAC_AC_SELECTOR_UNIT: + print_ac_selector_desc(buff); + break; + default: + goto unknown; + break; + } + } else if (subclass == UAC_SUBCLASS_AUDIOSTREAMING && desc->bDescriptorType == UAC_CS_INTERFACE) { + switch (header->bDescriptorSubtype) { + case UAC_AS_GENERAL: + parse_as_general_desc(buff); + break; + case UAC_AS_FORMAT_TYPE: + parse_as_type_desc(buff); + break; + default: + goto unknown; + break; + } + } else if (subclass == UAC_SUBCLASS_AUDIOSTREAMING && desc->bDescriptorType == UAC_CS_ENDPOINT) { + switch (header->bDescriptorSubtype) { + case UAC_EP_GENERAL: + parse_as_ep_general_desc(buff); + break; + default: + break; + } + } else { + printf("\tUnknown subclass 0x%x\n", subclass); + goto unknown; + } + return; +unknown: + print_unknown_desc(header); +} + +// Print the configuration descriptor and all its sub-descriptors with the given class-specific callback +// The subclass and protocol are passed to the class_specific_cb to allow it to interpret the descriptors more accurately +// This function better be added to usb_helpers.c +static void usb_print_config_descriptor_with_context(const usb_config_desc_t *cfg_desc, print_class_descriptor_with_context_cb class_specific_cb) +{ + int offset = 0; + uint16_t wTotalLength = cfg_desc->wTotalLength; + const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)cfg_desc; + uint8_t class = 0; + uint8_t subclass = 0; + uint8_t protocol = 0; + + do { + switch (next_desc->bDescriptorType) { + case USB_B_DESCRIPTOR_TYPE_CONFIGURATION: + usbh_print_cfg_desc((const usb_config_desc_t *)next_desc); + break; + case USB_B_DESCRIPTOR_TYPE_INTERFACE: { + const usb_intf_desc_t *intf_desc = (const usb_intf_desc_t *)next_desc; + usbh_print_intf_desc(intf_desc); + class = intf_desc->bInterfaceClass; + subclass = intf_desc->bInterfaceSubClass; + protocol = intf_desc->bInterfaceProtocol; + break; + } + case USB_B_DESCRIPTOR_TYPE_ENDPOINT: + print_ep_desc((const usb_ep_desc_t *)next_desc); + break; + case USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION: + print_iad_desc((const usb_iad_desc_t *)next_desc); + break; + default: + if (class_specific_cb) { + class_specific_cb(next_desc, class, subclass, protocol); + } + break; + } + + next_desc = usb_parse_next_descriptor(next_desc, wTotalLength, &offset); + + } while (next_desc != NULL); +} + +void print_uac_descriptors(const usb_config_desc_t *cfg_desc) +{ + usb_print_config_descriptor_with_context(cfg_desc, print_uac_class_descriptors); +} diff --git a/host/class/uac/usb_host_uac/uac_host.c b/host/class/uac/usb_host_uac/uac_host.c new file mode 100644 index 00000000..5cb951d4 --- /dev/null +++ b/host/class/uac/usb_host_uac/uac_host.c @@ -0,0 +1,2182 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/ringbuf.h" +#include "usb/usb_host.h" +#include "usb/uac_host.h" +#include "usb/usb_types_ch9.h" + +// UAC spinlock +static portMUX_TYPE uac_lock = portMUX_INITIALIZER_UNLOCKED; +#define UAC_ENTER_CRITICAL() portENTER_CRITICAL(&uac_lock) +#define UAC_EXIT_CRITICAL() portEXIT_CRITICAL(&uac_lock) + +// UAC verification macros +#define UAC_GOTO_ON_FALSE_CRITICAL(exp, err) \ + do { \ + if(!(exp)) { \ + UAC_EXIT_CRITICAL(); \ + ret = err; \ + goto fail; \ + } \ + } while(0) + +#define UAC_RETURN_ON_FALSE_CRITICAL(exp, err) \ + do { \ + if(!(exp)) { \ + UAC_EXIT_CRITICAL(); \ + return err; \ + } \ + } while(0) + +#define UAC_GOTO_ON_ERROR(exp, msg) ESP_GOTO_ON_ERROR(exp, fail, TAG, msg) + +#define UAC_GOTO_ON_FALSE(exp, err, msg) ESP_GOTO_ON_FALSE((exp), err, fail, TAG, msg) + +#define UAC_RETURN_ON_ERROR(exp, msg) ESP_RETURN_ON_ERROR((exp), TAG, msg) + +#define UAC_RETURN_ON_FALSE(exp, err, msg) ESP_RETURN_ON_FALSE((exp), (err), TAG, msg) + +#define UAC_RETURN_ON_INVALID_ARG(exp) ESP_RETURN_ON_FALSE((exp) != NULL, ESP_ERR_INVALID_ARG, TAG, "Argument error") + +// USB Descriptor parsing helping macros +#define GET_NEXT_INTERFACE_DESC(p, max_len, offs) \ + ((const usb_intf_desc_t *)usb_parse_next_descriptor_of_type((const usb_standard_desc_t *)p, \ + max_len, \ + USB_B_DESCRIPTOR_TYPE_INTERFACE, \ + &(offs))) +#define GET_NEXT_UAC_CS_DESC(p, max_len, offs) \ + ((const uac_desc_header_t *)usb_parse_next_descriptor_of_type((const usb_standard_desc_t *)p, \ + max_len, \ + UAC_CS_INTERFACE, \ + &(offs))) +#define GET_NEXT_DESC(p, max_len, offs) \ + ((const usb_standard_desc_t *)usb_parse_next_descriptor((const usb_standard_desc_t *)p, \ + max_len, \ + &(offs))) + +static const char *TAG = "uac-host"; + +#define DEFAULT_CTRL_XFER_TIMEOUT_MS (5000) +#define DEFAULT_ISOC_XFER_TIMEOUT_MS (100) +#define UAC_SPK_VOLUME_MAX (0xfff0) +#define UAC_SPK_VOLUME_MIN (0xe3a0) +#define UAC_SPK_VOLUME_STEP ((UAC_SPK_VOLUME_MAX - UAC_SPK_VOLUME_MIN)/100) +#define INTERFACE_FLAGS_OFFSET (16) +#define FLAG_INTERFACE_WAIT_USER_DELETE (1 << INTERFACE_FLAGS_OFFSET) +#define UAC_EP_DIR_IN (0x80) + +/** + * @brief UAC Device structure. + * + */ +typedef struct uac_host_device { + // dynamic values after device opening, should be protected by critical section + STAILQ_ENTRY(uac_host_device) tailq_entry; /*!< UAC device queue */ + uint8_t opened_cnt; /*!< Device opened counter */ + // constant values after device opening + usb_device_handle_t dev_hdl; /*!< USB device handle */ + uint8_t addr; /*!< USB device address */ + SemaphoreHandle_t device_busy; /*!< UAC device main mutex */ + SemaphoreHandle_t ctrl_xfer_done; /*!< Control transfer semaphore */ + usb_transfer_t *ctrl_xfer; /*!< Pointer to control transfer buffer */ + uint8_t ctrl_iface_num; /*!< Control interface number */ + uint8_t *cs_ac_desc; /*!< Class-Specific Audio Control Interface descriptor */ +} uac_device_t; + +/** + * @brief UAC Interface state +*/ +typedef enum { + UAC_INTERFACE_STATE_NOT_INITIALIZED = 0x00, /*!< UAC Interface not initialized */ + UAC_INTERFACE_STATE_IDLE, /*!< UAC Interface has been opened but not started */ + UAC_INTERFACE_STATE_READY, /*!< UAC Interface has started but stream is suspended */ + UAC_INTERFACE_STATE_ACTIVE, /*!< UAC Interface is streaming */ + UAC_INTERFACE_STATE_MAX +} uac_iface_state_t; + +/** + * @brief UAC Interface alternate setting parameters + */ +typedef struct uac_interface_alt { + // variable only change by app operation, protected by mutex + uint32_t cur_sampling_freq; /*!< current sampling frequency */ + // constant values after interface opening + uint8_t alt_idx; /*!< audio stream alternate setting number */ + uint8_t ep_addr; /*!< audio stream endpoint number */ + uint16_t ep_mps; /*!< audio stream endpoint max size */ + uint8_t ep_attr; /*!< audio stream endpoint attributes */ + uint8_t interval; /*!< audio stream endpoint interval */ + uint8_t connected_terminal; /*!< connected terminal ID */ + uint8_t feature_unit; /*!< connected feature unit ID */ + uint8_t vol_ch_map; /*!< volume channel map */ + uint8_t mute_ch_map; /*!< mute channel map */ + bool freq_ctrl_supported; /*!< sampling frequency control supported */ + uac_host_dev_alt_param_t dev_alt_param; /*!< audio stream alternate setting parameters */ +} uac_iface_alt_t; + +/** + * @brief UAC Interface structure in device to interact with. After UAC device opening keeps the interface configuration + * + */ +typedef struct uac_interface { + // dynamic values after interface opening, should be protected by critical section + STAILQ_ENTRY(uac_interface) tailq_entry; + usb_transfer_t **xfer_list; /*!< Pointer to transfer list */ + usb_transfer_t **free_xfer_list; /*!< Pointer to free transfer list */ + // variable only change by app operation, protected by mutex + SemaphoreHandle_t state_mutex; /*!< UAC device state mutex */ + uac_iface_state_t state; /*!< Interface state */ + uint32_t flags; /*!< Interface flags */ + uint8_t cur_alt; /*!< Current alternate setting (-1) */ + // constant parameters after interface opening + uac_device_t *parent; /*!< Parent USB UAC device */ + uint8_t xfer_num; /*!< Number of transfers */ + uint8_t packet_num; /*!< packets per transfer */ + uint32_t packet_size; /*!< size of each packet */ + uac_host_device_event_cb_t user_cb; /*!< Interface application callback */ + void *user_cb_arg; /*!< Interface application callback arg */ + RingbufHandle_t ringbuf; /*!< Ring buffer for audio data */ + uint32_t ringbuf_size; /*!< Ring buffer size */ + uint32_t ringbuf_threshold; /*!< Ring buffer threshold */ + uac_host_dev_info_t dev_info; /*!< USB device parameters */ + uac_iface_alt_t *iface_alt; /*!< audio stream alternate setting */ +} uac_iface_t; + +/** + * @brief UAC driver default context + * + * This context is created during UAC Host install. + */ +typedef struct { + // dynamic values after UAC Host initialization, should be protected by critical section + STAILQ_HEAD(devices, uac_host_device) uac_devices_tailq; /*!< STAILQ of UAC interfaces */ + STAILQ_HEAD(interfaces, uac_interface) uac_ifaces_tailq; /*!< STAILQ of UAC interfaces */ + volatile bool end_client_event_handling; /*!< Client event handling flag */ + // constant values after UAC Host initialization + bool event_handling_started; /*!< Events handler started flag */ + usb_host_client_handle_t client_handle; /*!< Client task handle */ + uac_host_driver_event_cb_t user_cb; /*!< User application callback */ + void *user_arg; /*!< User application callback args */ + SemaphoreHandle_t all_events_handled; /*!< Events handler semaphore */ +} uac_driver_t; + +/** + * @brief UAC class specific request +*/ +typedef struct uac_cs_request { + uint8_t bmRequestType; /*!< bmRequestType */ + uint8_t bRequest; /*!< bRequest */ + uint16_t wValue; /*!< wValue: Report Type and Report ID */ + uint16_t wIndex; /*!< wIndex: Interface */ + uint16_t wLength; /*!< wLength: Report Length */ + uint8_t *data; /*!< Pointer to data */ +} uac_cs_request_t; + +static uac_driver_t *s_uac_driver; /*!< Internal pointer to UAC driver */ + +// ----------------------- Private Prototypes ---------------------------------- + +static esp_err_t _uac_host_device_add(uint8_t addr, usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc, uac_device_t **uac_device_handle); +static esp_err_t _uac_host_device_delete(uac_device_t *uac_device); +static esp_err_t uac_cs_request_set(uac_device_t *uac_device, const uac_cs_request_t *req); +static esp_err_t uac_cs_request_set_ep_frequency(uac_iface_t *iface, uint8_t ep_addr, uint32_t freq); + +// --------------------------- Buffer Management -------------------------------- +static size_t _ring_buffer_get_len(RingbufHandle_t ringbuf_hdl) +{ + assert(ringbuf_hdl); + size_t size = 0; + vRingbufferGetInfo(ringbuf_hdl, NULL, NULL, NULL, NULL, &size); + return size; +} + +static void _ring_buffer_flush(RingbufHandle_t ringbuf_hdl) +{ + assert(ringbuf_hdl); + size_t read_bytes = 0; + size_t uxItemsWaiting = 0; + vRingbufferGetInfo(ringbuf_hdl, NULL, NULL, NULL, NULL, &uxItemsWaiting); + uint8_t *buf_rcv = xRingbufferReceiveUpTo(ringbuf_hdl, &read_bytes, 0, uxItemsWaiting); + + if (buf_rcv) { + vRingbufferReturnItem(ringbuf_hdl, (void *)(buf_rcv)); + } + + if (uxItemsWaiting > read_bytes) { + // read the second time to flush all data + vRingbufferGetInfo(ringbuf_hdl, NULL, NULL, NULL, NULL, &uxItemsWaiting); + buf_rcv = xRingbufferReceiveUpTo(ringbuf_hdl, &read_bytes, 0, uxItemsWaiting); + if (buf_rcv) { + vRingbufferReturnItem(ringbuf_hdl, (void *)(buf_rcv)); + } + } +} + +static esp_err_t _ring_buffer_push(RingbufHandle_t ringbuf_hdl, uint8_t *buf, size_t write_bytes, TickType_t xTicksToWait) +{ + assert(ringbuf_hdl && buf); + int res = xRingbufferSend(ringbuf_hdl, buf, write_bytes, xTicksToWait); + + if (res != pdTRUE) { + ESP_LOGD(TAG, "buffer is too small, push failed"); + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t _ring_buffer_pop(RingbufHandle_t ringbuf_hdl, uint8_t *buf, size_t req_bytes, size_t *read_bytes, TickType_t ticks_to_wait) +{ + assert(ringbuf_hdl && buf && read_bytes); + uint8_t *buf_rcv = xRingbufferReceiveUpTo(ringbuf_hdl, read_bytes, ticks_to_wait, req_bytes); + + if (!buf_rcv) { + return ESP_FAIL; + } + + memcpy(buf, buf_rcv, *read_bytes); + vRingbufferReturnItem(ringbuf_hdl, (void *)(buf_rcv)); + + size_t read_bytes2 = 0; + if (*read_bytes < req_bytes) { + buf_rcv = xRingbufferReceiveUpTo(ringbuf_hdl, &read_bytes2, 0, req_bytes - *read_bytes); + if (buf_rcv) { + memcpy(buf + *read_bytes, buf_rcv, read_bytes2); + *read_bytes += read_bytes2; + vRingbufferReturnItem(ringbuf_hdl, (void *)(buf_rcv)); + } + } + + return ESP_OK; +} + +/** + * @brief UAC Host driver event handler internal task + * + */ +static void event_handler_task(void *arg) +{ + ESP_LOGD(TAG, "USB UAC handling start"); + while (uac_host_handle_events(portMAX_DELAY) == ESP_OK) { + } + ESP_LOGD(TAG, "USB UAC handling stop"); + vTaskDelete(NULL); +} + +/** + * @brief Return UAC device in devices list by USB device handle + * + * @param[in] usb_device_handle_t USB device handle + * @return uac_device_t Pointer to device, NULL if device not present + */ +static uac_device_t *get_uac_device_by_handle(usb_device_handle_t usb_handle) +{ + uac_device_t *device = NULL; + + UAC_ENTER_CRITICAL(); + STAILQ_FOREACH(device, &s_uac_driver->uac_devices_tailq, tailq_entry) { + if (usb_handle == device->dev_hdl) { + UAC_EXIT_CRITICAL(); + return device; + } + } + UAC_EXIT_CRITICAL(); + return NULL; +} + +/** + * @brief Return UAC Device from usb address + * @param[in] addr USB device address + * @return uac_device_t Pointer to UAC Device + */ +static uac_device_t *get_uac_device_by_addr(uint8_t addr) +{ + uac_device_t *device = NULL; + + UAC_ENTER_CRITICAL(); + STAILQ_FOREACH(device, &s_uac_driver->uac_devices_tailq, tailq_entry) { + if (addr == device->addr) { + UAC_EXIT_CRITICAL(); + return device; + } + } + UAC_EXIT_CRITICAL(); + return NULL; +} + + +/** + * @brief Get UAC Interface pointer by Interface number + * + * @param[in] addr USB device address + * @param[in] iface_num Interface number + * @return uac_iface_t Pointer to UAC Interface configuration structure + */ +static uac_iface_t *get_interface_by_addr(uint8_t addr, uint8_t iface_num) +{ + uac_iface_t *interface = NULL; + + UAC_ENTER_CRITICAL(); + STAILQ_FOREACH(interface, &s_uac_driver->uac_ifaces_tailq, tailq_entry) { + if (addr == interface->dev_info.addr && iface_num == interface->dev_info.iface_num) { + UAC_EXIT_CRITICAL(); + return interface; + } + } + + UAC_EXIT_CRITICAL(); + return NULL; +} + +/** + * @brief Verify presence of Interface in the RAM list + * + * @param[in] iface Pointer to an Interface structure + * @return true Interface is in the list + * @return false Interface is not in the list + */ +static inline bool is_interface_in_list(uac_iface_t *iface) +{ + uac_iface_t *interface = NULL; + + UAC_ENTER_CRITICAL(); + STAILQ_FOREACH(interface, &s_uac_driver->uac_ifaces_tailq, tailq_entry) { + if (iface == interface) { + UAC_EXIT_CRITICAL(); + return true; + } + } + + UAC_EXIT_CRITICAL(); + return false; +} + +/** + * @brief Get UAC Interface pointer by external UAC Device handle with verification in RAM list + * + * @param[in] uac_dev_handle UAC Device handle + * @return uac_iface_t Pointer to an Interface structure + */ +static uac_iface_t *get_iface_by_handle(uac_host_device_handle_t uac_dev_handle) +{ + uac_iface_t *uac_iface = (uac_iface_t *) uac_dev_handle; + + if (!is_interface_in_list(uac_iface)) { + ESP_LOGE(TAG, "UAC interface handle not found"); + return NULL; + } + + return uac_iface; +} + +/** + * @brief Check UAC interface descriptor present + * + * @param[in] config_desc Pointer to Configuration Descriptor + * @return esp_err_t + */ +static bool uac_interface_present(const usb_config_desc_t *config_desc) +{ + assert(config_desc); + int offset = 0; + int total_len = config_desc->wTotalLength; + const usb_intf_desc_t *iface_desc = GET_NEXT_INTERFACE_DESC(config_desc, total_len, offset); + while (iface_desc != NULL) { + if (USB_CLASS_AUDIO == iface_desc->bInterfaceClass) { + return true; + } + iface_desc = GET_NEXT_INTERFACE_DESC(iface_desc, total_len, offset); + } + return false; +} + +/** + * @brief UAC Interface user callback function. + * + * @param[in] uac_iface Pointer to an Interface structure + * @param[in] event UAC Interface event + */ +static inline void uac_host_user_interface_callback(uac_iface_t *uac_iface, const uac_host_device_event_t event) +{ + assert(uac_iface); + // user callback should never block + if (uac_iface->user_cb) { + uac_iface->user_cb(uac_iface, event, uac_iface->user_cb_arg); + } +} + +/** + * @brief UAC Device user callback function. + * + * @param[in] event_id UAC Device event + * @param[in] event UAC Device event + */ +static inline void uac_host_user_device_callback(uint8_t addr, uint8_t iface_num, const uac_host_driver_event_t event) +{ + if (s_uac_driver && s_uac_driver->user_cb) { + s_uac_driver->user_cb(addr, iface_num, event, s_uac_driver->user_arg); + } +} + +static esp_err_t uac_host_string_descriptor_copy(wchar_t *dest, const usb_str_desc_t *src) +{ + if (dest == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (src != NULL) { + size_t len = MIN((src->bLength - USB_STANDARD_DESC_SIZE) / 2, UAC_STR_DESC_MAX_LENGTH - 1); + for (int i = 0; i < len; i++) { + dest[i] = (wchar_t) src->wData[i]; + } + // This should be always true, we just check to avoid LoadProhibited exception + if (dest != NULL) { + dest[len] = 0; + } + } else { + dest[0] = 0; + } + return ESP_OK; +} + +/** Lock UAC interface when user tries to change the interface state + * + * @param[in] iface Pointer to UAC interface structure + * @param[in] timeout_ms Timeout of trying to take the mutex + * @return esp_err_t + */ +static inline esp_err_t uac_host_interface_try_lock(uac_iface_t *iface, uint32_t timeout_ms) +{ + return (xSemaphoreTake(iface->state_mutex, pdMS_TO_TICKS(timeout_ms)) ? ESP_OK : ESP_ERR_TIMEOUT); +} + +/** Unlock UAC interface when user changes the interface state + * + * @param[in] iface Pointer to UAC interface structure + * @return esp_err_t + */ +static inline esp_err_t uac_host_interface_unlock(uac_iface_t *iface) +{ + return (xSemaphoreGive(iface->state_mutex) ? ESP_OK : ESP_FAIL); +} + +static uint8_t _uac_next_linked_uint_id(const uint8_t *desc, uint8_t unit_id, uint8_t **feat_desc) +{ + *feat_desc = NULL; + if (!desc) { + return 0; + } + uac_ac_header_desc_t *header_desc = (uac_ac_header_desc_t *)desc; + const size_t total_length = header_desc->wTotalLength; + int uac_desc_offset = 0; + const uac_desc_header_t *uac_cs_desc = (const uac_desc_header_t *)header_desc; + while (uac_cs_desc) { + switch (uac_cs_desc->bDescriptorSubtype) { + case UAC_AC_HEADER: + case UAC_AC_INPUT_TERMINAL: + break; + case UAC_AC_OUTPUT_TERMINAL: { + const uac_ac_output_terminal_desc_t *output_terminal_desc = (const uac_ac_output_terminal_desc_t *)uac_cs_desc; + if (output_terminal_desc->bSourceID == unit_id) { + return output_terminal_desc->bTerminalID; + } + break; + } + case UAC_AC_FEATURE_UNIT: { + const uac_ac_feature_unit_desc_t *feature_unit_desc = (const uac_ac_feature_unit_desc_t *)uac_cs_desc; + if (feature_unit_desc->bSourceID == unit_id) { + *feat_desc = (uint8_t *)feature_unit_desc; + return feature_unit_desc->bUnitID; + } + break; + } + case UAC_AC_SELECTOR_UNIT: { + const uac_ac_selector_unit_desc_t *selector_unit_desc = (const uac_ac_selector_unit_desc_t *)uac_cs_desc; + for (int i = 0; i < selector_unit_desc->bNrInPins; i++) { + if (selector_unit_desc->baSourceID[i] == unit_id) { + return selector_unit_desc->bUnitID; + } + } + break; + } + case UAC_AC_MIXER_UNIT: { + const uac_ac_mixer_unit_desc_t *mixer_unit_desc = (const uac_ac_mixer_unit_desc_t *)uac_cs_desc; + for (int i = 0; i < mixer_unit_desc->bNrInPins; i++) { + if (mixer_unit_desc->baSourceID[i] == unit_id) { + return mixer_unit_desc->bUnitID; + } + } + break; + } + default: + ESP_LOGD(TAG, "UAC Unknown Descriptor Subtype %d", uac_cs_desc->bDescriptorSubtype); + break; + } + uac_cs_desc = (uac_desc_header_t *)GET_NEXT_DESC(uac_cs_desc, total_length, uac_desc_offset); + } + return 0; +} + +static uint8_t _uac_last_linked_uint_id(const uint8_t *desc, uint8_t unit_id, uint8_t **feat_desc) +{ + *feat_desc = NULL; + if (!desc) { + return 0; + } + uac_ac_header_desc_t *header_desc = (uac_ac_header_desc_t *)desc; + const size_t total_length = header_desc->wTotalLength; + int uac_desc_offset = 0; + const uac_desc_header_t *uac_cs_desc = (const uac_desc_header_t *)header_desc; + while (uac_cs_desc) { + switch (uac_cs_desc->bDescriptorSubtype) { + case UAC_AC_HEADER: + case UAC_AC_INPUT_TERMINAL: + break; + case UAC_AC_OUTPUT_TERMINAL: { + const uac_ac_output_terminal_desc_t *output_terminal_desc = (const uac_ac_output_terminal_desc_t *)uac_cs_desc; + if (output_terminal_desc->bTerminalID == unit_id) { + return output_terminal_desc->bSourceID; + } + break; + } + case UAC_AC_FEATURE_UNIT: { + const uac_ac_feature_unit_desc_t *feature_unit_desc = (const uac_ac_feature_unit_desc_t *)uac_cs_desc; + if (feature_unit_desc->bUnitID == unit_id) { + *feat_desc = (uint8_t *)feature_unit_desc; + return feature_unit_desc->bSourceID; + } + break; + } + case UAC_AC_SELECTOR_UNIT: { + const uac_ac_selector_unit_desc_t *selector_unit_desc = (const uac_ac_selector_unit_desc_t *)uac_cs_desc; + if (selector_unit_desc->bUnitID == unit_id) { + // for basic audio device, the first source should be microphone + return selector_unit_desc->baSourceID[0]; + } + break; + } + case UAC_AC_MIXER_UNIT: { + const uac_ac_mixer_unit_desc_t *mixer_unit_desc = (const uac_ac_mixer_unit_desc_t *)uac_cs_desc; + if (mixer_unit_desc->bUnitID == unit_id) { + return mixer_unit_desc->baSourceID[0]; + } + break; + } + default: + ESP_LOGD(TAG, "UAC Unknown Descriptor Subtype %d", uac_cs_desc->bDescriptorSubtype); + break; + } + uac_cs_desc = (uac_desc_header_t *)GET_NEXT_DESC(uac_cs_desc, total_length, uac_desc_offset); + } + return 0; +} + +static uac_ac_feature_unit_desc_t *_uac_host_device_find_feature_unit(const uint8_t *header_desc, uint8_t terminal_id, bool if_input) +{ + if (!header_desc) { + return NULL; + } + + uint8_t unit_id = terminal_id; + uac_ac_feature_unit_desc_t *feature_unit_desc = NULL; + if (if_input) { + while (feature_unit_desc == NULL && unit_id != 0) { + unit_id = _uac_next_linked_uint_id(header_desc, unit_id, (uint8_t **)&feature_unit_desc); + ESP_LOGD(TAG, "Input Terminal next linked unit ID %d", unit_id); + } + } else { + while (feature_unit_desc == NULL && unit_id != 0) { + unit_id = _uac_last_linked_uint_id(header_desc, unit_id, (uint8_t **)&feature_unit_desc); + ESP_LOGD(TAG, "Output Terminal last linked unit ID %d", unit_id); + } + } + + return feature_unit_desc; +} + +/** + * @brief Add a new logical device/interface to the UAC driver + * @param[in] iface_num Interface number + * @param[out] p_uac_iface Pointer to the UAC interface handle + * @return esp_err_t + */ +static esp_err_t uac_host_interface_add(uac_device_t *uac_device, uint8_t iface_num, uac_iface_t **p_uac_iface) +{ + esp_err_t ret; + *p_uac_iface = NULL; + uac_iface_t *uac_iface = calloc(1, sizeof(uac_iface_t)); + UAC_RETURN_ON_FALSE(uac_iface, ESP_ERR_NO_MEM, "Unable to allocate memory"); + uac_iface->state_mutex = xSemaphoreCreateMutex(); + UAC_GOTO_ON_FALSE(uac_iface->state_mutex, ESP_ERR_NO_MEM, "Unable to create state mutex"); + const usb_config_desc_t *config_desc = NULL; + const usb_intf_desc_t *iface_desc = NULL; + const usb_intf_desc_t *iface_alt_desc = NULL; + const usb_ep_desc_t *ep_desc = NULL; + const usb_standard_desc_t *cs_desc = NULL; + + usb_host_get_active_config_descriptor(uac_device->dev_hdl, &config_desc); + UAC_GOTO_ON_FALSE(config_desc, ESP_ERR_INVALID_STATE, "No active configuration descriptor"); + const size_t total_length = config_desc->wTotalLength; + int iface_alt_offset = 0; + int iface_alt_idx = 0; + + iface_desc = usb_parse_interface_descriptor(config_desc, iface_num, 0, &iface_alt_offset); + iface_alt_desc = GET_NEXT_INTERFACE_DESC(iface_desc, total_length, iface_alt_offset); + // For every alternate setting + while (iface_alt_desc != NULL) { + // Check if the alternate setting is for the same interface + if (iface_alt_desc->bInterfaceNumber != iface_desc->bInterfaceNumber) { + break; + } + ESP_LOGD(TAG, "Found UAC bInterfaceNumber= %d, bAlternateSetting= %d", + iface_alt_desc->bInterfaceNumber, iface_alt_desc->bAlternateSetting); + iface_alt_idx++; + // Allocate memory for the alternate setting + uac_iface->iface_alt = realloc(uac_iface->iface_alt, iface_alt_idx * sizeof(uac_iface_alt_t)); + UAC_GOTO_ON_FALSE(uac_iface->iface_alt, ESP_ERR_NO_MEM, "Unable to allocate memory"); + uac_iface_alt_t *iface_alt = &uac_iface->iface_alt[iface_alt_idx - 1]; + memset(iface_alt, 0, sizeof(uac_iface_alt_t)); + iface_alt->alt_idx = iface_alt_desc->bAlternateSetting; + // Parse each descriptor following the alternate interface descriptor + int cs_offset = iface_alt_offset; + cs_desc = GET_NEXT_DESC(iface_alt_desc, total_length, cs_offset); + bool parse_continue = true; + while (cs_desc != NULL && parse_continue) { + switch (cs_desc->bDescriptorType) { + case UAC_CS_INTERFACE: { + const uac_desc_header_t *uac_desc = (const uac_desc_header_t *)cs_desc; + if (uac_desc->bDescriptorSubtype == UAC_AS_GENERAL) { + const uac_as_general_desc_t *as_general_desc = (const uac_as_general_desc_t *)uac_desc; + iface_alt->dev_alt_param.format = as_general_desc->wFormatTag; + iface_alt->connected_terminal = as_general_desc->bTerminalLink; + } else if (uac_desc->bDescriptorSubtype == UAC_AS_FORMAT_TYPE) { + const uac_as_type_I_format_desc_t *as_format_type_desc = (const uac_as_type_I_format_desc_t *)uac_desc; + if (as_format_type_desc->bFormatType != UAC_FORMAT_TYPE_I) { + ESP_LOGE(TAG, "UAC Format Type %d", as_format_type_desc->bFormatType); + UAC_GOTO_ON_FALSE(0, ESP_ERR_NOT_SUPPORTED, "UAC Format Type not supported"); + } + iface_alt->dev_alt_param.channels = as_format_type_desc->bNrChannels; + iface_alt->dev_alt_param.bit_resolution = as_format_type_desc->bBitResolution; + iface_alt->dev_alt_param.sample_freq_type = as_format_type_desc->bSamFreqType; + if (as_format_type_desc->bSamFreqType == 0) { + iface_alt->dev_alt_param.sample_freq_lower = (as_format_type_desc->tSamFreq[2] << 16) | (as_format_type_desc->tSamFreq[1] << 8) | as_format_type_desc->tSamFreq[0]; + iface_alt->dev_alt_param.sample_freq_upper = (as_format_type_desc->tSamFreq[5] << 16) | (as_format_type_desc->tSamFreq[4] << 8) | as_format_type_desc->tSamFreq[3]; + } else { + for (int i = 0; i < as_format_type_desc->bSamFreqType && i < UAC_FREQ_NUM_MAX; i++) { + iface_alt->dev_alt_param.sample_freq[i] = (as_format_type_desc->tSamFreq[i * 3 + 2] << 16) | (as_format_type_desc->tSamFreq[i * 3 + 1] << 8) | as_format_type_desc->tSamFreq[i * 3]; + } + if (as_format_type_desc->bSamFreqType > UAC_FREQ_NUM_MAX) { + ESP_LOGW(TAG, "UAC Interface %d->%d, Frequency Number %d exceed the maximum %d", iface_desc->bInterfaceNumber, iface_alt->alt_idx, as_format_type_desc->bSamFreqType, UAC_FREQ_NUM_MAX); + } + } + ESP_LOGD(TAG, "UAC AS Format Type %d, Bit Resolution %d, Sample Freq Type %d", as_format_type_desc->bFormatType, as_format_type_desc->bBitResolution, as_format_type_desc->bSamFreqType); + } else { + ESP_LOGW(TAG, "UAC CS bDescriptorSubtype=%d not supported", uac_desc->bDescriptorSubtype); + } + break; + } + case USB_B_DESCRIPTOR_TYPE_ENDPOINT: { + ep_desc = (const usb_ep_desc_t *)cs_desc; + iface_alt->ep_addr = ep_desc->bEndpointAddress; + iface_alt->ep_mps = ep_desc->wMaxPacketSize; + iface_alt->ep_attr = ep_desc->bmAttributes; + iface_alt->interval = ep_desc->bInterval; + uac_iface->dev_info.type = (ep_desc->bEndpointAddress & UAC_EP_DIR_IN) ? UAC_STREAM_RX : UAC_STREAM_TX; + uac_ac_feature_unit_desc_t *feature_unit_desc = _uac_host_device_find_feature_unit((uint8_t *)uac_device->cs_ac_desc, + iface_alt->connected_terminal, !(ep_desc->bEndpointAddress & UAC_EP_DIR_IN)); + if (feature_unit_desc) { + iface_alt->feature_unit = feature_unit_desc->bUnitID; + uint8_t ch_num = 0; + for (size_t i = 0; i < (feature_unit_desc->bLength - 7) / feature_unit_desc->bControlSize; i++) { + if (feature_unit_desc->bmaControls[i * feature_unit_desc->bControlSize] & UAC_FU_CONTROL_POS_VOLUME) { + iface_alt->vol_ch_map |= (1 << ch_num); + } + if (feature_unit_desc->bmaControls[i * feature_unit_desc->bControlSize] & UAC_FU_CONTROL_POS_MUTE) { + iface_alt->mute_ch_map |= (1 << ch_num); + } + ch_num++; + } + ESP_LOGD(TAG, "UAC %s Feature Unit ID %d, Volume Ch Map %02X, Mute Ch Map %02X", uac_iface->dev_info.type == UAC_STREAM_RX ? "RX" : "TX", + feature_unit_desc->bUnitID, iface_alt->vol_ch_map, iface_alt->mute_ch_map); + } + ESP_LOGD(TAG, "UAC Endpoint 0x%02X, Max Packet Size %d, Attributes 0x%02X, Interval %d", ep_desc->bEndpointAddress, ep_desc->wMaxPacketSize, ep_desc->bmAttributes, ep_desc->bInterval); + break; + } + case UAC_CS_ENDPOINT: { + // we has got enough information to fill the uac_iface_alt_t, so we can break + const uac_as_cs_ep_desc_t *cs_ep_desc = (const uac_as_cs_ep_desc_t *)cs_desc; + if (cs_ep_desc->bDescriptorSubtype == UAC_EP_GENERAL) { + iface_alt->freq_ctrl_supported = cs_ep_desc->bmAttributes & UAC_SAMPLING_FREQ_CONTROL; + parse_continue = false; + ESP_LOGD(TAG, "UAC EP General, Attributes 0x%02X", cs_ep_desc->bmAttributes); + ESP_LOGD(TAG, "UAC EP Frequency Control %d", iface_alt->freq_ctrl_supported); + } + break; + } + default: + break; + } + cs_desc = GET_NEXT_DESC(cs_desc, total_length, cs_offset); + } + // Get next alternate setting + iface_alt_desc = GET_NEXT_INTERFACE_DESC(iface_alt_desc, total_length, iface_alt_offset); + } + uac_iface->state = UAC_INTERFACE_STATE_NOT_INITIALIZED; + uac_iface->parent = uac_device; + uac_iface->dev_info.addr = uac_device->addr; + uac_iface->dev_info.iface_num = iface_desc->bInterfaceNumber; + uac_iface->dev_info.iface_alt_num = iface_alt_idx; + ESP_LOGD(TAG, "UAC Interface %d, found total alternate %d", iface_desc->bInterfaceNumber, iface_alt_idx); + + // Fill descriptor device information + const usb_device_desc_t *desc; + usb_device_info_t dev_info; + UAC_GOTO_ON_ERROR(usb_host_get_device_descriptor(uac_device->dev_hdl, &desc), "Unable to get device descriptor"); + UAC_GOTO_ON_ERROR(usb_host_device_info(uac_device->dev_hdl, &dev_info), "Unable to get USB device info"); + // VID, PID + uac_iface->dev_info.VID = desc->idVendor; + uac_iface->dev_info.PID = desc->idProduct; + // Strings + uac_host_string_descriptor_copy(uac_iface->dev_info.iManufacturer, dev_info.str_desc_manufacturer); + uac_host_string_descriptor_copy(uac_iface->dev_info.iProduct, dev_info.str_desc_product); + uac_host_string_descriptor_copy(uac_iface->dev_info.iSerialNumber, dev_info.str_desc_serial_num); + + *p_uac_iface = uac_iface; + + UAC_ENTER_CRITICAL(); + STAILQ_INSERT_TAIL(&s_uac_driver->uac_ifaces_tailq, uac_iface, tailq_entry); + UAC_EXIT_CRITICAL(); + + return ESP_OK; + +fail: + if (uac_iface && uac_iface->state_mutex) { + vSemaphoreDelete(uac_iface->state_mutex); + } + free(uac_iface->iface_alt); + free(uac_iface); + return ret; +} + +/** + * @brief Remove interface from a list + * + * + * @param[in] uac_iface UAC interface handle + * @return esp_err_t + */ +static esp_err_t uac_host_interface_delete(uac_iface_t *uac_iface) +{ + uac_iface->state = UAC_INTERFACE_STATE_NOT_INITIALIZED; + UAC_ENTER_CRITICAL(); + STAILQ_REMOVE(&s_uac_driver->uac_ifaces_tailq, uac_iface, uac_interface, tailq_entry); + UAC_EXIT_CRITICAL(); + vSemaphoreDelete(uac_iface->state_mutex); + free(uac_iface->iface_alt); + free(uac_iface); + return ESP_OK; +} + +/** + * @brief Check every interface in the USB device, notify user about connected interfaces/logic devices + * + * @param[in] addr USB device address + * @param[in] config_desc Pointer to Configuration Descriptor + * @return esp_err_t + * @retval ESP_OK UAC Interface found + * @retval ESP_ERR_NOT_FOUND UAC Interface not found + */ +static esp_err_t uac_host_interface_check(uint8_t addr, const usb_config_desc_t *config_desc) +{ + assert(config_desc); + size_t total_length = config_desc->wTotalLength; + const usb_intf_desc_t *iface_desc = NULL; + int iface_offset = 0; + bool is_uac_interface = false; + + // Get first Interface descriptor + iface_desc = GET_NEXT_INTERFACE_DESC(config_desc, total_length, iface_offset); + // Check every uac stream interface + while (iface_desc != NULL) { + if (iface_desc->bInterfaceClass == USB_CLASS_AUDIO && iface_desc->bInterfaceSubClass == UAC_SUBCLASS_AUDIOSTREAMING) { + // notify user about the connected Interfaces + is_uac_interface = true; + const usb_intf_desc_t *iface_alt_desc = GET_NEXT_INTERFACE_DESC(iface_desc, total_length, iface_offset); + int ep_offset = iface_offset; + const usb_ep_desc_t *ep_desc = usb_parse_endpoint_descriptor_by_index(iface_alt_desc, 0, total_length, &ep_offset); + if (ep_desc == NULL) { + ESP_LOGW(TAG, "No endpoint descriptor found"); + } else if (ep_desc->bEndpointAddress & UAC_EP_DIR_IN) { + // notify user about the connected Interfaces + uac_host_user_device_callback(addr, iface_desc->bInterfaceNumber, UAC_HOST_DRIVER_EVENT_RX_CONNECTED); + } else { + // notify user about the connected Interfaces + uac_host_user_device_callback(addr, iface_desc->bInterfaceNumber, UAC_HOST_DRIVER_EVENT_TX_CONNECTED); + } + // Skip all alternate settings belonging to the current interface + while (iface_alt_desc != NULL) { + // Check if the alternate setting is for the same interface + if (iface_alt_desc->bInterfaceNumber != iface_desc->bInterfaceNumber) { + break; + } + iface_alt_desc = GET_NEXT_INTERFACE_DESC(iface_alt_desc, total_length, iface_offset); + } + iface_desc = iface_alt_desc; + continue; + } + iface_desc = GET_NEXT_INTERFACE_DESC(iface_desc, total_length, iface_offset); + } + + return is_uac_interface ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +/** + * @brief Handler for USB device connected event + * + * @param[in] addr USB device physical address + * @return esp_err_t + */ +static esp_err_t _uac_host_device_connected(uint8_t addr) +{ + bool is_uac_device = false; + usb_device_handle_t dev_hdl; + const usb_config_desc_t *config_desc = NULL; + + if (usb_host_device_open(s_uac_driver->client_handle, addr, &dev_hdl) == ESP_OK) { + if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) == ESP_OK) { + is_uac_device = uac_interface_present(config_desc); + } + UAC_RETURN_ON_ERROR(usb_host_device_close(s_uac_driver->client_handle, dev_hdl), "Unable to close USB device"); + } + + // Create UAC interfaces list in RAM, connected to the particular USB dev + if (is_uac_device) { +#ifdef CONFIG_PRINTF_UAC_CONFIGURATION_DESCRIPTOR + print_uac_descriptors(config_desc); +#endif + // Create Interfaces list for a possibility to claim Interface + UAC_RETURN_ON_ERROR(uac_host_interface_check(addr, config_desc), "uac stream interface not found"); + } else { + ESP_LOGW(TAG, "USB device with addr(%d) is not UAC device", addr); + } + + return is_uac_device ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +/** + * @brief USB device was removed we need to shutdown UAC Interface + * + * @param[in] uac_dev_handle Handle of the UAC device to close + * @return esp_err_t + */ +static esp_err_t uac_host_interface_shutdown(uac_host_device_handle_t uac_dev_handle) +{ + uac_iface_t *uac_iface = get_iface_by_handle(uac_dev_handle); + + UAC_RETURN_ON_INVALID_ARG(uac_iface); + + if (uac_iface->user_cb) { + // Let user handle the remove process + // clear the flag FLAG_INTERFACE_WAIT_USER_DELETE; + uac_iface->flags &= ~FLAG_INTERFACE_WAIT_USER_DELETE; + uac_host_user_interface_callback(uac_iface, UAC_HOST_DRIVER_EVENT_DISCONNECTED); + } else { + // Remove UAC Interface from the list right now + uac_iface->flags &= ~FLAG_INTERFACE_WAIT_USER_DELETE; + uac_host_device_close(uac_dev_handle); + } + + return ESP_OK; +} + +/** + * @brief Handler for USB device disconnected event + * + * @param[in] dev_hdl USB device handle + * @return esp_err_t + */ +static esp_err_t _uac_host_device_disconnected(usb_device_handle_t dev_hdl) +{ + uac_device_t *uac_device = get_uac_device_by_handle(dev_hdl); + uac_iface_t *uac_iface = NULL; + // Device should be in the list + assert(uac_device); + + UAC_ENTER_CRITICAL(); + while (!STAILQ_EMPTY(&s_uac_driver->uac_ifaces_tailq)) { + uac_iface = STAILQ_FIRST(&s_uac_driver->uac_ifaces_tailq); + UAC_EXIT_CRITICAL(); + if (uac_iface->parent && (uac_iface->parent->addr == uac_device->addr)) { + uac_iface->flags |= FLAG_INTERFACE_WAIT_USER_DELETE; + UAC_RETURN_ON_ERROR(uac_host_device_close(uac_iface), "Unable to close device"); + UAC_RETURN_ON_ERROR(uac_host_interface_shutdown(uac_iface), "Unable to shutdown interface"); + } + UAC_ENTER_CRITICAL(); + } + UAC_EXIT_CRITICAL(); + + return ESP_OK; +} + +/** + * @brief USB Host Client's event callback + * + * @param[in] event Client event message + * @param[in] arg Argument, does not used + */ +static void client_event_cb(const usb_host_client_event_msg_t *event, void *arg) +{ + if (event->event == USB_HOST_CLIENT_EVENT_NEW_DEV) { + _uac_host_device_connected(event->new_dev.address); + } else if (event->event == USB_HOST_CLIENT_EVENT_DEV_GONE) { + _uac_host_device_disconnected(event->dev_gone.dev_hdl); + } +} + +/** + * @brief UAC Host release Interface and free transfers, change state to IDLE + * + * @param[in] iface Pointer to Interface structure, + * @return esp_err_t + */ +static esp_err_t uac_host_interface_release_and_free_transfer(uac_iface_t *iface) +{ + UAC_RETURN_ON_INVALID_ARG(iface); + UAC_RETURN_ON_INVALID_ARG(iface->parent); + + UAC_RETURN_ON_FALSE(is_interface_in_list(iface), ESP_ERR_NOT_FOUND, "Interface handle not found"); + UAC_RETURN_ON_ERROR(usb_host_interface_release(s_uac_driver->client_handle, iface->parent->dev_hdl, iface->dev_info.iface_num), "Unable to release UAC Interface"); + + if (iface->free_xfer_list) { + for (int i = 0; i < iface->xfer_num; i++) { + if (iface->free_xfer_list[i]) { + ESP_ERROR_CHECK(usb_host_transfer_free(iface->free_xfer_list[i])); + } + } + free(iface->free_xfer_list); + } + + if (iface->xfer_list) { + for (int i = 0; i < iface->xfer_num; i++) { + if (iface->xfer_list[i]) { + ESP_ERROR_CHECK(usb_host_transfer_free(iface->xfer_list[i])); + } + } + free(iface->xfer_list); + } + + // Change state + iface->state = UAC_INTERFACE_STATE_IDLE; + return ESP_OK; +} + +/** + * @brief UAC Host claim Interface and prepare transfer, change state to READY + * + * @param[in] iface Pointer to Interface structure, + * @return esp_err_t + */ +static esp_err_t uac_host_interface_claim_and_prepare_transfer(uac_iface_t *iface) +{ + esp_err_t ret = ESP_OK; + // Claim Interface + + UAC_RETURN_ON_ERROR(usb_host_interface_claim(s_uac_driver->client_handle, iface->parent->dev_hdl, iface->dev_info.iface_num, + iface->cur_alt + 1), "Unable to claim Interface"); + // alloc a list of usb transfer + uint32_t packet_size = iface->iface_alt[iface->cur_alt].ep_mps; + iface->xfer_list = calloc(iface->xfer_num, sizeof(usb_transfer_t *)); + UAC_GOTO_ON_FALSE(iface->xfer_list, ESP_ERR_NO_MEM, "Unable to allocate transfer list"); + iface->free_xfer_list = calloc(iface->xfer_num, sizeof(usb_transfer_t *)); + UAC_GOTO_ON_FALSE(iface->free_xfer_list, ESP_ERR_NO_MEM, "Unable to allocate free transfer list"); + for (int i = 0; i < iface->xfer_num; i++) { + UAC_GOTO_ON_ERROR(usb_host_transfer_alloc(packet_size * iface->packet_num, iface->packet_num, &iface->free_xfer_list[i]), + "Unable to allocate transfer buffer for EP IN"); + } + // Change state + iface->state = UAC_INTERFACE_STATE_READY; + return ESP_OK; + +fail: + uac_host_interface_release_and_free_transfer(iface); + return ret; +} + +/** + * @brief UAC IN Transfer complete callback + * + * @param[in] transfer Pointer to transfer data structure + */ +static void stream_rx_xfer_done(usb_transfer_t *in_xfer) +{ + assert(in_xfer); + + uac_iface_t *iface = in_xfer->context; + assert(iface); + + if (iface->state != UAC_INTERFACE_STATE_ACTIVE) { + in_xfer->status = USB_TRANSFER_STATUS_CANCELED; + } + + switch (in_xfer->status) { + case USB_TRANSFER_STATUS_COMPLETED: { + + // if ringbuffer will overflow, notify user to read data + size_t data_len = _ring_buffer_get_len(iface->ringbuf); + if (data_len + in_xfer->actual_num_bytes >= iface->ringbuf_size) { + uac_host_user_interface_callback(iface, UAC_HOST_DEVICE_EVENT_RX_DONE); + } + + // if ringbuffer overflow (happens if user not read in above callback), the data will be dropped + data_len = _ring_buffer_get_len(iface->ringbuf); + if (data_len + in_xfer->actual_num_bytes > iface->ringbuf_size) { + ESP_LOGD(TAG, "RX Ringbuffer overflow"); + } else { + // else push data to ringbuffer + for (int i = 0; i < in_xfer->num_isoc_packets; i++) { + if (in_xfer->isoc_packet_desc[i].status != USB_TRANSFER_STATUS_COMPLETED) { + // copy data to ringbuffer + ESP_LOGD(TAG, "Bad RX Isoc packet %d status %d", i, in_xfer->isoc_packet_desc[i].status); + continue; + } + int requested_num_bytes = in_xfer->isoc_packet_desc[i].num_bytes; + int actual_num_bytes = in_xfer->isoc_packet_desc[i].actual_num_bytes; + // in UAC, the actual_num_bytes may less than requested_num_bytes + // eg. the packet_size is 64, but the endpoint size is 100 + assert(requested_num_bytes >= actual_num_bytes); + // copy data to ringbuffer + _ring_buffer_push(iface->ringbuf, in_xfer->data_buffer + i * requested_num_bytes, actual_num_bytes, 0); + } + } + // Relaunch transfer + usb_host_transfer_submit(in_xfer); + + // if ringbuffer is reach the threshold, notify user to read out + data_len = _ring_buffer_get_len(iface->ringbuf); + if (data_len >= iface->ringbuf_threshold) { + uac_host_user_interface_callback(iface, UAC_HOST_DEVICE_EVENT_RX_DONE); + } + + return; + } + case USB_TRANSFER_STATUS_NO_DEVICE: + case USB_TRANSFER_STATUS_CANCELED: + // User is notified about device disconnection from usb_event_cb + // No need to do anything + return; + default: + // Any other error + break; + } + + ESP_LOGE(TAG, "Transfer failed, status %d", in_xfer->status); + // Notify user about transfer or any other error + uac_host_user_interface_callback(iface, UAC_HOST_DEVICE_EVENT_TRANSFER_ERROR); +} + +static void stream_tx_xfer_submit(usb_transfer_t *out_xfer) +{ + uac_iface_t *iface = out_xfer->context; + assert(iface); + + size_t data_len = _ring_buffer_get_len(iface->ringbuf); + if (data_len >= iface->packet_size * iface->packet_num) { + data_len = iface->packet_size * iface->packet_num; + size_t actual_num_bytes = 0; + _ring_buffer_pop(iface->ringbuf, out_xfer->data_buffer, data_len, &actual_num_bytes, 0); + assert(actual_num_bytes == data_len); + // Relaunch transfer, as the pipe state may change + // the transfer may fail eg. the device is disconnected or the pipe is suspended + // the data in ringbuffer will be dropped without notify user + usb_host_transfer_submit(out_xfer); + data_len = _ring_buffer_get_len(iface->ringbuf); + if (data_len <= iface->ringbuf_threshold) { + // Notify user send done + uac_host_user_interface_callback(iface, UAC_HOST_DEVICE_EVENT_TX_DONE); + } + } else { + // add the transfer to free list + UAC_ENTER_CRITICAL(); + for (int i = 0; i < iface->xfer_num; i++) { + if (iface->xfer_list[i] == out_xfer) { + iface->free_xfer_list[i] = out_xfer; + iface->xfer_list[i] = NULL; + break; + } + } + UAC_EXIT_CRITICAL(); + // Notify user send done + uac_host_user_interface_callback(iface, UAC_HOST_DEVICE_EVENT_TX_DONE); + } +} + +/** + * @brief UAC OUT Transfer complete callback + * + * @param[in] transfer Pointer to transfer data structure + */ +static void stream_tx_xfer_done(usb_transfer_t *out_xfer) +{ + assert(out_xfer); + + uac_iface_t *iface = out_xfer->context; + assert(iface); + + // If the iface is not active, cancel the transfer + if (iface->state != UAC_INTERFACE_STATE_ACTIVE) { + out_xfer->status = USB_TRANSFER_STATUS_CANCELED; + } + + switch (out_xfer->status) { + case USB_TRANSFER_STATUS_COMPLETED: { + // Submit the next transfer + stream_tx_xfer_submit(out_xfer); + return; + } + case USB_TRANSFER_STATUS_NO_DEVICE: + case USB_TRANSFER_STATUS_CANCELED: + // User is notified about device disconnection from usb_event_cb + // No need to do anything + return; + default: + // Any other error, add the transfer to free list + UAC_ENTER_CRITICAL(); + for (int i = 0; i < iface->xfer_num; i++) { + if (iface->xfer_list[i] == out_xfer) { + iface->free_xfer_list[i] = out_xfer; + iface->xfer_list[i] = NULL; + break; + } + } + UAC_EXIT_CRITICAL(); + break; + } + + ESP_LOGE(TAG, "Transfer failed, status %d", out_xfer->status); + // Notify user about transfer or any other error + uac_host_user_interface_callback(iface, UAC_HOST_DEVICE_EVENT_TRANSFER_ERROR); +} + +/** + * @brief Suspend active interface, the interface will be in READY state + * + * @param[in] iface Pointer to Interface structure + * @return esp_err_t + */ +static esp_err_t uac_host_interface_suspend(uac_iface_t *iface) +{ + UAC_RETURN_ON_INVALID_ARG(iface); + UAC_RETURN_ON_INVALID_ARG(iface->parent); + UAC_RETURN_ON_INVALID_ARG(iface->free_xfer_list); + UAC_RETURN_ON_FALSE(is_interface_in_list(iface), ESP_ERR_NOT_FOUND, "Interface handle not found"); + UAC_RETURN_ON_FALSE((UAC_INTERFACE_STATE_ACTIVE == iface->state), ESP_ERR_INVALID_STATE, "Interface wrong state"); + + // Set Interface alternate setting to 0 + usb_setup_packet_t request; + USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, iface->dev_info.iface_num, 0); + esp_err_t ret = uac_cs_request_set(iface->parent, (uac_cs_request_t *)&request); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Set Interface %d-%d Failed", iface->dev_info.iface_num, 0); + } else { + ESP_LOGI(TAG, "Set Interface %d-%d", iface->dev_info.iface_num, 0); + } + + uint8_t ep_addr = iface->iface_alt[iface->cur_alt].ep_addr; + UAC_RETURN_ON_ERROR(usb_host_endpoint_halt(iface->parent->dev_hdl, ep_addr), "Unable to HALT EP"); + UAC_RETURN_ON_ERROR(usb_host_endpoint_flush(iface->parent->dev_hdl, ep_addr), "Unable to FLUSH EP"); + usb_host_endpoint_clear(iface->parent->dev_hdl, ep_addr); + _ring_buffer_flush(iface->ringbuf); + + // add all the transfer to free list + UAC_ENTER_CRITICAL(); + for (int i = 0; i < iface->xfer_num; i++) { + if (iface->xfer_list[i]) { + iface->free_xfer_list[i] = iface->xfer_list[i]; + iface->xfer_list[i] = NULL; + } + } + UAC_EXIT_CRITICAL(); + // Change state + iface->state = UAC_INTERFACE_STATE_READY; + + return ESP_OK; +} + +/** + * @brief Resume suspended interface, the interface will be in ACTIVE state + * + * @param[in] iface Pointer to Interface structure + * @return esp_err_t + */ +static esp_err_t uac_host_interface_resume(uac_iface_t *iface) +{ + UAC_RETURN_ON_INVALID_ARG(iface); + UAC_RETURN_ON_INVALID_ARG(iface->parent); + UAC_RETURN_ON_INVALID_ARG(iface->free_xfer_list); + UAC_RETURN_ON_FALSE(is_interface_in_list(iface), ESP_ERR_NOT_FOUND, "Interface handle not found"); + UAC_RETURN_ON_FALSE((UAC_INTERFACE_STATE_READY == iface->state), ESP_ERR_INVALID_STATE, "Interface wrong state"); + + // Set Interface alternate setting + usb_setup_packet_t request; + USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, iface->dev_info.iface_num, iface->cur_alt + 1); + UAC_RETURN_ON_ERROR(uac_cs_request_set(iface->parent, (uac_cs_request_t *)&request), "Unable to set Interface alternate"); + ESP_LOGI(TAG, "Set Interface %d-%d", iface->dev_info.iface_num, iface->cur_alt + 1); + // Set endpoint frequency control + if (iface->iface_alt[iface->cur_alt].freq_ctrl_supported) { + ESP_LOGI(TAG, "Set EP %02X frequency %"PRIu32, iface->iface_alt[iface->cur_alt].ep_addr, iface->iface_alt[iface->cur_alt].cur_sampling_freq); + UAC_RETURN_ON_ERROR(uac_cs_request_set_ep_frequency(iface, iface->iface_alt[iface->cur_alt].ep_addr, + iface->iface_alt[iface->cur_alt].cur_sampling_freq), "Unable to set endpoint frequency"); + } + // for RX, we just submit all the transfers + if (iface->dev_info.type == UAC_STREAM_RX) { + assert(iface->iface_alt[iface->cur_alt].ep_addr & 0x80); + for (int i = 0; i < iface->xfer_num; i++) { + assert(iface->free_xfer_list[i]); + iface->free_xfer_list[i]->device_handle = iface->parent->dev_hdl; + iface->free_xfer_list[i]->callback = stream_rx_xfer_done; + iface->free_xfer_list[i]->context = iface; + iface->free_xfer_list[i]->timeout_ms = DEFAULT_ISOC_XFER_TIMEOUT_MS; + iface->free_xfer_list[i]->bEndpointAddress = iface->iface_alt[iface->cur_alt].ep_addr; + // we request the size same as the MPS of the endpoint, but the actual size should be checked in the callback + iface->free_xfer_list[i]->num_bytes = iface->iface_alt[iface->cur_alt].ep_mps * iface->packet_num; + // set request nub_bytes of each packet + for (int j = 0; j < iface->packet_num; j++) { + iface->free_xfer_list[i]->isoc_packet_desc[j].num_bytes = iface->iface_alt[iface->cur_alt].ep_mps; + } + iface->xfer_list[i] = iface->free_xfer_list[i]; + iface->free_xfer_list[i] = NULL; + UAC_RETURN_ON_ERROR(usb_host_transfer_submit(iface->xfer_list[i]), "Unable to submit RX transfer"); + } + } else if (iface->dev_info.type == UAC_STREAM_TX) { + assert(!(iface->iface_alt[iface->cur_alt].ep_addr & 0x80)); + // for TX, we submit the first transfer with data 0 to make the speaker quiet + for (int i = 0; i < iface->xfer_num; i++) { + assert(iface->free_xfer_list[i]); + iface->free_xfer_list[i]->device_handle = iface->parent->dev_hdl; + iface->free_xfer_list[i]->callback = stream_tx_xfer_done; + iface->free_xfer_list[i]->context = iface; + iface->free_xfer_list[i]->timeout_ms = DEFAULT_ISOC_XFER_TIMEOUT_MS; + iface->free_xfer_list[i]->bEndpointAddress = iface->iface_alt[iface->cur_alt].ep_addr; + // set the data buffer to 0 + memset(iface->free_xfer_list[i]->data_buffer, 0, iface->free_xfer_list[i]->data_buffer_size); + // for synchronous transfer type, the packet size depends on the actual sample rate, channels and bit resolution. + for (int j = 0; j < iface->packet_num; j++) { + iface->free_xfer_list[i]->isoc_packet_desc[j].num_bytes = iface->packet_size; + } + iface->free_xfer_list[i]->num_bytes = iface->packet_num * iface->packet_size; + } + } + + // for TX, we check if data is available in the ringbuffer, if yes, we submit the transfer + iface->state = UAC_INTERFACE_STATE_ACTIVE; + + return ESP_OK; +} + +/** + * @brief Add UAC physical device to the list + * @param[in] addr USB device address + * @param[in] dev_hdl USB device handle + * @param[in] config_desc Pointer to Configuration Descriptor + * @param[out] uac_device_handle Pointer to UAC device handle + * @return esp_err_t + */ +static esp_err_t _uac_host_device_add(uint8_t addr, usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc, uac_device_t **uac_device_handle) +{ + assert(config_desc); + esp_err_t ret; + uac_device_t *uac_device; + UAC_GOTO_ON_FALSE(uac_device = calloc(1, sizeof(uac_device_t)), ESP_ERR_NO_MEM, "Unable to allocate memory for UAC Device"); + + uac_device->addr = addr; + uac_device->dev_hdl = dev_hdl; + + // Parse the configuration descriptor to find the UAC control interface + const size_t total_length = config_desc->wTotalLength; + const usb_intf_desc_t *iface_desc = NULL; + const uac_desc_header_t *uac_cs_desc = NULL; + int iface_offset = 0; + int uac_desc_offset = 0; + // Get first Interface descriptor + iface_desc = GET_NEXT_INTERFACE_DESC(config_desc, total_length, iface_offset); + while (iface_desc != NULL) { + uac_desc_offset = iface_offset; + // 1. Parse the configuration descriptor to find only the first the UAC control interface + // 2. Parse each class specific audio control interface + // 2.1. for header descriptor, check if it is supported uac version + // 2.2. save the class specific audio control descriptor for future use + if (iface_desc->bInterfaceClass == USB_CLASS_AUDIO && iface_desc->bInterfaceSubClass == UAC_SUBCLASS_AUDIOCONTROL) { + ESP_LOGD(TAG, "Found UAC Control, bInterfaceNumber=%d", iface_desc->bInterfaceNumber); + uac_cs_desc = GET_NEXT_UAC_CS_DESC(iface_desc, total_length, uac_desc_offset); + uac_device->ctrl_iface_num = iface_desc->bInterfaceNumber; + // break if all UAC control class specific descriptors are parsed + while (uac_cs_desc != NULL) { + switch (uac_cs_desc->bDescriptorSubtype) { + case UAC_AC_HEADER: { + const uac_ac_header_desc_t *header_desc = (const uac_ac_header_desc_t *)uac_cs_desc; + if (header_desc->bcdADC != 0x0100) { + ESP_LOGW(TAG, "UAC version %04X not supported", header_desc->bcdADC); + free(uac_device); + return ESP_ERR_NOT_SUPPORTED; + } + uint8_t *cs_ac_desc = calloc(header_desc->wTotalLength, sizeof(uint8_t)); + UAC_GOTO_ON_FALSE(cs_ac_desc, ESP_ERR_NO_MEM, "Unable to allocate memory for UAC Control CS descriptor"); + memcpy(cs_ac_desc, uac_cs_desc, header_desc->wTotalLength); + uac_device->cs_ac_desc = cs_ac_desc; + ESP_LOGD(TAG, "UAC version %04X", header_desc->bcdADC); + break; + } + default: + break; + } + uac_cs_desc = (uac_desc_header_t *)GET_NEXT_DESC(uac_cs_desc, total_length, uac_desc_offset); + if (uac_cs_desc->bDescriptorType != UAC_CS_INTERFACE) { + // parse stop if the next descriptor is not UAC class specific interface descriptor + break; + } + } + // all parse stop, only parse the first UAC control interface + break; + } + iface_desc = GET_NEXT_INTERFACE_DESC(iface_desc, total_length, iface_offset); + } + + // Create Semaphore for control transfer + UAC_GOTO_ON_FALSE(uac_device->ctrl_xfer_done = xSemaphoreCreateBinary(), ESP_ERR_NO_MEM, "Unable to create semaphore"); + UAC_GOTO_ON_FALSE(uac_device->device_busy = xSemaphoreCreateMutex(), ESP_ERR_NO_MEM, "Unable to create mutex"); + + // Allocate control transfer buffer + UAC_GOTO_ON_ERROR(usb_host_transfer_alloc(64, 0, &uac_device->ctrl_xfer), "Unable to allocate transfer buffer"); + + UAC_GOTO_ON_FALSE_CRITICAL(s_uac_driver, ESP_ERR_INVALID_STATE); + UAC_GOTO_ON_FALSE_CRITICAL(s_uac_driver->client_handle, ESP_ERR_INVALID_STATE); + UAC_ENTER_CRITICAL(); + STAILQ_INSERT_TAIL(&s_uac_driver->uac_devices_tailq, uac_device, tailq_entry); + UAC_EXIT_CRITICAL(); + + if (uac_device_handle) { + *uac_device_handle = uac_device; + } + + return ESP_OK; + +fail: + _uac_host_device_delete(uac_device); + return ret; +} + +/** + * @brief Remove UAC physical device from the list + * @param[in] uac_device Pointer to UAC device structure + * @return esp_err_t + */ +static esp_err_t _uac_host_device_delete(uac_device_t *uac_device) +{ + UAC_RETURN_ON_INVALID_ARG(uac_device); + + if (uac_device->ctrl_xfer) { + UAC_RETURN_ON_ERROR(usb_host_transfer_free(uac_device->ctrl_xfer), "Unable to free transfer buffer for EP0"); + } + + if (uac_device->ctrl_xfer_done) { + vSemaphoreDelete(uac_device->ctrl_xfer_done); + } + + if (uac_device->device_busy) { + vSemaphoreDelete(uac_device->device_busy); + } + + if (uac_device->cs_ac_desc) { + free(uac_device->cs_ac_desc); + } + + ESP_LOGD(TAG, "Remove addr %d device from list", uac_device->addr); + + UAC_ENTER_CRITICAL(); + STAILQ_REMOVE(&s_uac_driver->uac_devices_tailq, uac_device, uac_host_device, tailq_entry); + UAC_EXIT_CRITICAL(); + + free(uac_device); + return ESP_OK; +} + +/** Lock UAC device from other task + * + * @param[in] uac_device Pointer to UAC device structure + * @param[in] timeout_ms Timeout of trying to take the mutex + * @return esp_err_t + */ +static inline esp_err_t _uac_host_device_try_lock(uac_device_t *uac_device, uint32_t timeout_ms) +{ + return (xSemaphoreTake(uac_device->device_busy, pdMS_TO_TICKS(timeout_ms)) ? ESP_OK : ESP_ERR_TIMEOUT); +} + +/** Unlock UAC device from other task + * + * @param[in] uac_device Pointer to UAC device structure + * @param[in] timeout_ms Timeout of trying to take the mutex + * @return esp_err_t + */ +static inline void _uac_host_device_unlock(uac_device_t *uac_device) +{ + xSemaphoreGive(uac_device->device_busy); +} + +/** + * @brief UAC Control transfer complete callback + * + * @param[in] ctrl_xfer Pointer to transfer data structure + */ +static void ctrl_xfer_done(usb_transfer_t *ctrl_xfer) +{ + assert(ctrl_xfer); + uac_device_t *uac_device = (uac_device_t *)ctrl_xfer->context; + xSemaphoreGive(uac_device->ctrl_xfer_done); +} + +/** + * @brief UAC control transfer synchronous. + * + * @param[in] uac_device Pointer to UAC device structure + * @param[in] len Number of bytes to transfer + * @param[in] timeout_ms Timeout in ms + * @return esp_err_t + */ +static esp_err_t uac_control_transfer(uac_device_t *uac_device, int len, uint32_t timeout_ms) +{ + + usb_transfer_t *ctrl_xfer = uac_device->ctrl_xfer; + + ctrl_xfer->device_handle = uac_device->dev_hdl; + ctrl_xfer->callback = ctrl_xfer_done; + ctrl_xfer->context = uac_device; + ctrl_xfer->bEndpointAddress = 0; + ctrl_xfer->timeout_ms = timeout_ms; + ctrl_xfer->num_bytes = len; + + UAC_RETURN_ON_ERROR(usb_host_transfer_submit_control(s_uac_driver->client_handle, ctrl_xfer), "Unable to submit control transfer"); + + BaseType_t received = xSemaphoreTake(uac_device->ctrl_xfer_done, pdMS_TO_TICKS(ctrl_xfer->timeout_ms)); + + if (received != pdTRUE) { + // Transfer was not finished, error in USB LIB. Reset the endpoint + ESP_LOGE(TAG, "Control Transfer Timeout"); + UAC_RETURN_ON_ERROR(usb_host_endpoint_halt(uac_device->dev_hdl, ctrl_xfer->bEndpointAddress), "Unable to HALT EP"); + UAC_RETURN_ON_ERROR(usb_host_endpoint_flush(uac_device->dev_hdl, ctrl_xfer->bEndpointAddress), "Unable to FLUSH EP"); + usb_host_endpoint_clear(uac_device->dev_hdl, ctrl_xfer->bEndpointAddress); + return ESP_ERR_TIMEOUT; + } + + UAC_RETURN_ON_FALSE(ctrl_xfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, "Control transfer error"); + UAC_RETURN_ON_FALSE(ctrl_xfer->actual_num_bytes <= ctrl_xfer->num_bytes, ESP_ERR_INVALID_RESPONSE, "Incorrect number of bytes transferred"); + ESP_LOG_BUFFER_HEXDUMP(TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_DEBUG); + + return ESP_OK; +} + +/** + * @brief UAC class specific request Set + * + * @param[in] uac_device Pointer to UAC device structure + * @param[in] req Pointer to a class specific request structure + * @return esp_err_t + */ +static esp_err_t uac_cs_request_set(uac_device_t *uac_device, const uac_cs_request_t *req) +{ + esp_err_t ret; + usb_transfer_t *ctrl_xfer = uac_device->ctrl_xfer; + UAC_RETURN_ON_INVALID_ARG(uac_device); + UAC_RETURN_ON_INVALID_ARG(uac_device->ctrl_xfer); + + UAC_RETURN_ON_ERROR(_uac_host_device_try_lock(uac_device, DEFAULT_CTRL_XFER_TIMEOUT_MS), "UAC Device is busy by other task"); + + usb_setup_packet_t *setup = (usb_setup_packet_t *)ctrl_xfer->data_buffer; + + setup->bmRequestType = req->bmRequestType; + setup->bRequest = req->bRequest; + setup->wValue = req->wValue; + setup->wIndex = req->wIndex; + setup->wLength = req->wLength; + + if (setup->bmRequestType == 0) { + setup->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS + | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + } + + if (req->wLength && req->data) { + memcpy(ctrl_xfer->data_buffer + USB_SETUP_PACKET_SIZE, req->data, req->wLength); + } + + ret = uac_control_transfer(uac_device, USB_SETUP_PACKET_SIZE + setup->wLength, DEFAULT_CTRL_XFER_TIMEOUT_MS); + + _uac_host_device_unlock(uac_device); + + return ret; +} + +/** + * @brief UAC class specific request - Set Endpoint Frequency + * @param[in] iface Pointer to UAC interface structure + * @param[in] ep_addr Endpoint address + * @param[in] freq Frequency to set + * @return esp_err_t + */ +static esp_err_t uac_cs_request_set_ep_frequency(uac_iface_t *iface, uint8_t ep_addr, uint32_t freq) +{ + uint8_t tmp[3] = { 0, 0, 0 }; + + const uac_cs_request_t set_freq = { + .bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS + | USB_BM_REQUEST_TYPE_RECIP_ENDPOINT, + .bRequest = UAC_SET_CUR, + .wValue = UAC_SAMPLING_FREQ_CONTROL << 8, + .wIndex = (0x00ff & ep_addr), + .wLength = 3, + .data = tmp + }; + + tmp[0] = freq & 0xff; + tmp[1] = (freq >> 8) & 0xff; + tmp[2] = (freq >> 16) & 0xff; + return uac_cs_request_set(iface->parent, &set_freq); +} + +/** + * @brief UAC class specific request - Set Volume + * @param[in] iface Pointer to UAC interface structure + * @param[in] volume Volume to set, db + * @return esp_err_t + */ +static esp_err_t uac_cs_request_set_volume(uac_iface_t *iface, uint32_t volume) +{ + uint8_t feature_unit = iface->iface_alt[iface->cur_alt].feature_unit; + uint8_t vol_ch_map = iface->iface_alt[iface->cur_alt].vol_ch_map; + UAC_RETURN_ON_FALSE(feature_unit && vol_ch_map, ESP_ERR_NOT_SUPPORTED, "volume control not supported"); + uint8_t ctrl_iface_num = iface->parent->ctrl_iface_num; + uint8_t tmp[2] = { 0, 0 }; + esp_err_t ret = ESP_OK; + + uac_cs_request_t set_volume = { + .bRequest = UAC_SET_CUR, + .wIndex = (feature_unit << 8) | (ctrl_iface_num & 0xff), + .wLength = 2, + .data = tmp + }; + + tmp[0] = volume & 0xff; + tmp[1] = (volume >> 8) & 0xff; + + // set volume for all logical channels + // we not support separate volume control for each channel + for (size_t i = 0; i < 8; i++) { + if (vol_ch_map & (1 << i)) { + set_volume.wValue = (UAC_VOLUME_CONTROL << 8) | i, + ret = uac_cs_request_set(iface->parent, &set_volume); + if (ret != ESP_OK) { + return ret; + } + } + } + ESP_LOGD(TAG, "Set volume 0x%04X db", (unsigned int)volume); + return ret; +} + +/** + * @brief UAC class specific request - Set Mute + * @param[in] iface Pointer to UAC interface structure + * @param[in] mute True to mute, false to unmute + * @return esp_err_t + */ +static esp_err_t uac_cs_request_set_mute(uac_iface_t *iface, bool mute) +{ + uint8_t feature_unit = iface->iface_alt[iface->cur_alt].feature_unit; + uint8_t mute_ch_map = iface->iface_alt[iface->cur_alt].mute_ch_map; + UAC_RETURN_ON_FALSE(feature_unit, ESP_ERR_NOT_SUPPORTED, "mute control not supported"); + uint8_t ctrl_iface_num = iface->parent->ctrl_iface_num; + uint8_t tmp[1] = { 0 }; + esp_err_t ret = ESP_OK; + + uac_cs_request_t set_mute = { + .bRequest = UAC_SET_CUR, + .wIndex = (feature_unit << 8) | (ctrl_iface_num & 0xff), + .wLength = 1, + .data = tmp + }; + + tmp[0] = mute; + + // set mute for all logical channels + // we not support separate mute control for each channel + for (size_t i = 0; i < 8; i++) { + if (mute_ch_map & (1 << i)) { + set_mute.wValue = (UAC_MUTE_CONTROL << 8) | i, + ret = uac_cs_request_set(iface->parent, &set_mute); + if (ret != ESP_OK) { + return ret; + } + } + } + ESP_LOGD(TAG, "Set mute %d", mute); + return ret; +} + +esp_err_t uac_host_install(const uac_host_driver_config_t *config) +{ + esp_err_t ret; + // Make sure uac driver is not installed + UAC_RETURN_ON_FALSE(!s_uac_driver, ESP_ERR_INVALID_STATE, "UAC Host driver is already installed"); + UAC_RETURN_ON_INVALID_ARG(config); + UAC_RETURN_ON_INVALID_ARG(config->callback); + + if (config->create_background_task) { + UAC_RETURN_ON_FALSE(config->stack_size != 0, ESP_ERR_INVALID_ARG, "Wrong stack size value"); + UAC_RETURN_ON_FALSE(config->task_priority != 0, ESP_ERR_INVALID_ARG, "Wrong task priority value"); + UAC_RETURN_ON_FALSE(config->core_id < 2, ESP_ERR_INVALID_ARG, "Wrong core id value"); + } + + uac_driver_t *driver = heap_caps_calloc(1, sizeof(uac_driver_t), MALLOC_CAP_DEFAULT); + UAC_RETURN_ON_FALSE(driver, ESP_ERR_NO_MEM, "Unable to allocate memory"); + + driver->user_cb = config->callback; + driver->user_arg = config->callback_arg; + driver->end_client_event_handling = false; + driver->all_events_handled = xSemaphoreCreateBinary(); + UAC_GOTO_ON_FALSE(driver->all_events_handled, ESP_ERR_NO_MEM, "Unable to create semaphore"); + + usb_host_client_config_t client_config = { + .is_synchronous = false, + .async.client_event_callback = client_event_cb, + .async.callback_arg = NULL, + .max_num_event_msg = 16, + }; + UAC_GOTO_ON_ERROR(usb_host_client_register(&client_config, &driver->client_handle), "Unable to register USB Host client"); + + UAC_ENTER_CRITICAL(); + s_uac_driver = driver; + STAILQ_INIT(&s_uac_driver->uac_devices_tailq); + STAILQ_INIT(&s_uac_driver->uac_ifaces_tailq); + UAC_EXIT_CRITICAL(); + + if (config->create_background_task) { + BaseType_t task_created = xTaskCreatePinnedToCore(event_handler_task, "USB UAC Host", config->stack_size, + NULL, config->task_priority, NULL, config->core_id); + UAC_GOTO_ON_FALSE(task_created, ESP_ERR_NO_MEM, "Unable to create USB UAC Host task"); + } + + return ESP_OK; + +fail: + s_uac_driver = NULL; + if (driver->client_handle) { + usb_host_client_deregister(driver->client_handle); + } + if (driver->all_events_handled) { + vSemaphoreDelete(driver->all_events_handled); + } + free(driver); + return ret; +} + +esp_err_t uac_host_uninstall(void) +{ + // Make sure uac driver is installed, + UAC_RETURN_ON_FALSE(s_uac_driver, ESP_OK, "UAC Host driver was not installed"); + + // Make sure that no uac device is registered + UAC_ENTER_CRITICAL(); + UAC_RETURN_ON_FALSE_CRITICAL(!s_uac_driver->end_client_event_handling, ESP_ERR_INVALID_STATE); + // assert that not all devices and interfaces are closed before uninstalling + UAC_RETURN_ON_FALSE_CRITICAL(STAILQ_EMPTY(&s_uac_driver->uac_devices_tailq), ESP_ERR_INVALID_STATE); + UAC_RETURN_ON_FALSE_CRITICAL(STAILQ_EMPTY(&s_uac_driver->uac_ifaces_tailq), ESP_ERR_INVALID_STATE); + s_uac_driver->end_client_event_handling = true; + UAC_EXIT_CRITICAL(); + + if (s_uac_driver->event_handling_started) { + ESP_ERROR_CHECK(usb_host_client_unblock(s_uac_driver->client_handle)); + // In case the event handling started, we must wait until it finishes + xSemaphoreTake(s_uac_driver->all_events_handled, portMAX_DELAY); + } + vSemaphoreDelete(s_uac_driver->all_events_handled); + ESP_ERROR_CHECK(usb_host_client_deregister(s_uac_driver->client_handle)); + free(s_uac_driver); + s_uac_driver = NULL; + return ESP_OK; +} + +esp_err_t uac_host_device_open(const uac_host_device_config_t *config, uac_host_device_handle_t *uac_dev_handle) +{ + UAC_RETURN_ON_INVALID_ARG(uac_dev_handle); + UAC_RETURN_ON_INVALID_ARG(config); + *uac_dev_handle = NULL; + + UAC_RETURN_ON_FALSE(s_uac_driver, ESP_ERR_INVALID_STATE, "UAC Driver is not installed"); + UAC_RETURN_ON_FALSE(config->addr, ESP_ERR_INVALID_ARG, "Invalid device address"); + UAC_RETURN_ON_FALSE(config->iface_num, ESP_ERR_INVALID_ARG, "Invalid interface number"); + UAC_RETURN_ON_FALSE(config->buffer_size, ESP_ERR_INVALID_ARG, "Invalid buffer size"); + UAC_RETURN_ON_FALSE(config->buffer_size > config->buffer_threshold, ESP_ERR_INVALID_ARG, "Invalid buffer threshold"); + + ESP_LOGD(TAG, "Open Device addr %d, iface %d", config->addr, config->iface_num); + // Check if the logic device/interface is already added + uac_iface_t *uac_iface = get_interface_by_addr(config->addr, config->iface_num); + if (uac_iface) { + *uac_dev_handle = (uac_host_device_handle_t)uac_iface; + ESP_LOGD(TAG, "Device addr %d, iface %d already opened", config->addr, config->iface_num); + return ESP_OK; + } + + // Check if the physical device is already added + esp_err_t ret; + uac_device_t *uac_device = get_uac_device_by_addr(config->addr); + bool new_device = false; + usb_device_handle_t dev_hdl = NULL; + if (!uac_device) { + UAC_GOTO_ON_ERROR(usb_host_device_open(s_uac_driver->client_handle, config->addr, &dev_hdl), "Unable to open USB device"); + ESP_LOGD(TAG, "line %d, Open Device addr %d", __LINE__, config->addr); + const usb_config_desc_t *config_desc; + UAC_GOTO_ON_ERROR(usb_host_get_active_config_descriptor(dev_hdl, &config_desc), "Unable to get active config descriptor"); + // Proceed, add UAC device to the list, parse device params from descriptors + UAC_GOTO_ON_ERROR(_uac_host_device_add(config->addr, dev_hdl, config_desc, &uac_device), "Unable to add UAC device"); + new_device = true; + uac_device->opened_cnt = 0; + } + + UAC_GOTO_ON_ERROR(uac_host_interface_add(uac_device, config->iface_num, &uac_iface), "Unable to add interface"); + + // Save UAC Interface callback + uac_iface->user_cb = config->callback; + uac_iface->user_cb_arg = config->callback_arg; + // create a ringbuffer for the incoming/outgoing data + uac_iface->ringbuf = xRingbufferCreate(config->buffer_size, RINGBUF_TYPE_BYTEBUF); + UAC_GOTO_ON_FALSE(uac_iface->ringbuf, ESP_ERR_NO_MEM, "Unable to create ringbuffer"); + uac_iface->ringbuf_size = config->buffer_size; + // if the threshold is not set, set it to 25% of the buffer size + uac_iface->ringbuf_threshold = config->buffer_threshold ? config->buffer_threshold : config->buffer_size / 4; + uac_iface->state = UAC_INTERFACE_STATE_IDLE; + *uac_dev_handle = (uac_host_device_handle_t)uac_iface; + UAC_ENTER_CRITICAL(); + uac_device->opened_cnt++; + UAC_EXIT_CRITICAL(); + return ESP_OK; + +fail: + if (uac_iface) { + uac_host_interface_delete(uac_iface); + } + if (new_device) { + _uac_host_device_delete(uac_device); + } + if (dev_hdl) { + usb_host_device_close(s_uac_driver->client_handle, dev_hdl); + } + if (uac_iface->ringbuf) { + vRingbufferDelete(uac_iface->ringbuf); + } + return ret; +} + +esp_err_t uac_host_device_close(uac_host_device_handle_t uac_dev_handle) +{ + uac_iface_t *uac_iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_INVALID_ARG(uac_iface); + + esp_err_t ret = ESP_OK; + ESP_LOGD(TAG, "Close addr %d, iface %d, state %d", uac_iface->dev_info.addr, uac_iface->dev_info.iface_num, uac_iface->state); + + UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(uac_iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "UAC Interface is busy by other task"); + if (UAC_INTERFACE_STATE_ACTIVE == uac_iface->state) { + UAC_GOTO_ON_ERROR(uac_host_interface_suspend(uac_iface), "Unable to disable UAC Interface"); + } + + if (UAC_INTERFACE_STATE_READY == uac_iface->state) { + UAC_GOTO_ON_ERROR(uac_host_interface_release_and_free_transfer(uac_iface), "Unable to release UAC Interface"); + } + + // Wait for user delete + if (FLAG_INTERFACE_WAIT_USER_DELETE & uac_iface->flags) { + uac_host_interface_unlock(uac_iface); + return ESP_OK; + } + + UAC_ENTER_CRITICAL(); + if (--uac_iface->parent->opened_cnt == 0) { + UAC_EXIT_CRITICAL(); + UAC_GOTO_ON_ERROR(usb_host_device_close(s_uac_driver->client_handle, uac_iface->parent->dev_hdl), "Unable to close USB device"); + ESP_LOGD(TAG, "line %d, Close Device addr %d", __LINE__, uac_iface->parent->addr); + UAC_GOTO_ON_ERROR(_uac_host_device_delete(uac_iface->parent), "Unable to delete UAC device"); + uac_iface->parent = NULL; + UAC_ENTER_CRITICAL(); + } + UAC_EXIT_CRITICAL(); + + // To delete the ringbuffer safely + // We should unblock the task that is waiting for the ringbuffer + if (uac_iface->ringbuf) { + if (uac_iface->dev_info.type == UAC_STREAM_RX) { + // Unblock the task that is waiting for read from the ringbuffer + uint8_t dummy = 0; + xRingbufferSend(uac_iface->ringbuf, &dummy, sizeof(dummy), 0); + } else { + // Unblock the task that is waiting for the ringbuffer + _ring_buffer_flush(uac_iface->ringbuf); + } + // Wait 10 ms for low priority task to unblock + vTaskDelay(pdMS_TO_TICKS(10)); + vRingbufferDelete(uac_iface->ringbuf); + uac_iface->ringbuf = NULL; + } + + uac_iface->user_cb = NULL; + uac_iface->user_cb_arg = NULL; + ESP_LOGD(TAG, "User Remove addr %d, iface %d from list", uac_iface->dev_info.addr, uac_iface->dev_info.iface_num); + uac_host_interface_delete(uac_iface); + + return ESP_OK; + +fail: + uac_host_interface_unlock(uac_iface); + return ret; +} + +esp_err_t uac_host_handle_events(uint32_t timeout) +{ + UAC_RETURN_ON_FALSE(s_uac_driver != NULL, ESP_ERR_INVALID_STATE, "UAC Driver is not installed"); + s_uac_driver->event_handling_started = true; + esp_err_t ret = usb_host_client_handle_events(s_uac_driver->client_handle, timeout); + UAC_ENTER_CRITICAL(); + if (s_uac_driver->end_client_event_handling) { + UAC_EXIT_CRITICAL(); + xSemaphoreGive(s_uac_driver->all_events_handled); + return ESP_FAIL; + } + UAC_EXIT_CRITICAL(); + return ret; +} + +esp_err_t uac_host_get_device_alt_param(uac_host_device_handle_t uac_dev_handle, uint8_t iface_alt, uac_host_dev_alt_param_t *uac_alt_param) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_FALSE(iface, ESP_ERR_INVALID_STATE, "UAC Interface not found"); + UAC_RETURN_ON_FALSE(uac_alt_param, ESP_ERR_INVALID_ARG, "Wrong argument"); + UAC_RETURN_ON_FALSE(iface_alt > 0, ESP_ERR_INVALID_ARG, "Invalid alt setting"); + UAC_RETURN_ON_FALSE(iface_alt <= iface->dev_info.iface_alt_num, ESP_ERR_INVALID_ARG, "Invalid alt setting"); + memcpy(uac_alt_param, &iface->iface_alt[iface_alt - 1].dev_alt_param, sizeof(uac_host_dev_alt_param_t)); + return ESP_OK; +} + +esp_err_t uac_host_printf_device_param(uac_host_device_handle_t uac_dev_handle) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_FALSE(iface, ESP_ERR_INVALID_STATE, "UAC Interface not found"); + uac_host_dev_info_t dev_info; + UAC_RETURN_ON_ERROR(uac_host_get_device_info(uac_dev_handle, &dev_info), "Unable to get UAC device params"); + printf("find UAC 1.0 %s device\n", dev_info.type == UAC_STREAM_TX ? "Speaker" : "Microphone"); + printf("interface number: %d\n", dev_info.iface_num); + printf("total alt interfaces number: %d\n", dev_info.iface_alt_num); + + for (int i = 1; i <= dev_info.iface_alt_num; i++) { + uac_host_dev_alt_param_t iface_alt_params; + ESP_ERROR_CHECK(uac_host_get_device_alt_param(uac_dev_handle, i, &iface_alt_params)); + printf("--------alt interface[%d]--------- \nchannels = %d \nbit_resolution = %d \nsample_freq: \n", + i, iface_alt_params.channels, iface_alt_params.bit_resolution); + if (iface_alt_params.sample_freq_type) { + for (int j = 0; j < iface_alt_params.sample_freq_type; j++) { + printf("\t%" PRIu32 "\n", iface_alt_params.sample_freq[j]); + } + } else { + printf(">= \t%" PRIu32 "\n", iface_alt_params.sample_freq_lower); + printf("<= \t%" PRIu32 "\n", iface_alt_params.sample_freq_upper); + } + } + return ESP_OK; +} + +// ------------------------ USB UAC Host driver API ---------------------------- + +esp_err_t uac_host_device_start(uac_host_device_handle_t uac_dev_handle, const uac_host_stream_config_t *stream_config) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + + UAC_RETURN_ON_INVALID_ARG(iface); + UAC_RETURN_ON_INVALID_ARG(iface->parent); + UAC_RETURN_ON_FALSE(stream_config->bit_resolution, ESP_ERR_INVALID_ARG, "Invalid bit resolution"); + UAC_RETURN_ON_FALSE(stream_config->channels, ESP_ERR_INVALID_ARG, "Invalid number of channels"); + UAC_RETURN_ON_FALSE(stream_config->sample_freq, ESP_ERR_INVALID_ARG, "Invalid sample frequency"); + + // get the mutex first to change the device/interface state + UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "Unable to lock UAC Interface"); + if (UAC_INTERFACE_STATE_ACTIVE == iface->state || UAC_INTERFACE_STATE_READY == iface->state) { + uac_host_interface_unlock(iface); + return ESP_OK; + } + + esp_err_t ret = ESP_OK; + bool iface_claimed = false; + UAC_GOTO_ON_FALSE((UAC_INTERFACE_STATE_IDLE == iface->state), ESP_ERR_INVALID_STATE, "Interface wrong state"); + + // check if any alt setting meets the channels, sample frequency and bit resolution requirements + // if not exist, return error. If exist, claim the interface and prepare transfer + iface->cur_alt = UINT8_MAX; + for (int i = 0; i < iface->dev_info.iface_alt_num; i++) { + if (iface->iface_alt[i].dev_alt_param.channels == stream_config->channels && + iface->iface_alt[i].dev_alt_param.bit_resolution == stream_config->bit_resolution) { + // check if the sample frequency is in the list or in the range + if (iface->iface_alt[i].dev_alt_param.sample_freq_type > 0) { + for (int j = 0; j < iface->iface_alt[i].dev_alt_param.sample_freq_type; j++) { + if (iface->iface_alt[i].dev_alt_param.sample_freq[j] == stream_config->sample_freq) { + iface->cur_alt = i; + iface->iface_alt[i].cur_sampling_freq = stream_config->sample_freq; + break; + } + } + } else if (iface->iface_alt[i].dev_alt_param.sample_freq_lower <= stream_config->sample_freq && + iface->iface_alt[i].dev_alt_param.sample_freq_upper >= stream_config->sample_freq) { + iface->cur_alt = i; + iface->iface_alt[i].cur_sampling_freq = stream_config->sample_freq; + break; + } + } + } + + UAC_GOTO_ON_FALSE(iface->cur_alt != UINT8_MAX, ESP_ERR_NOT_FOUND, "No suitable alt setting found"); + + // enqueue multiple transfers to make sure the data is not lost + iface->xfer_num = CONFIG_UAC_NUM_ISOC_URBS; + iface->packet_num = CONFIG_UAC_NUM_PACKETS_PER_URB; + iface->packet_size = iface->iface_alt[iface->cur_alt].cur_sampling_freq * stream_config->channels * stream_config->bit_resolution / 8 / 1000; + iface->flags |= stream_config->flags; + // if the packet size is not an integer, we need to add one more byte + if (iface->iface_alt[iface->cur_alt].cur_sampling_freq * stream_config->channels * stream_config->bit_resolution / 8 % 1000) { + ESP_LOGD(TAG, "packet_size %" PRIu32 " is not an integer, add one more byte", iface->packet_size); + iface->packet_size++; + } + assert(iface->packet_size <= iface->iface_alt[iface->cur_alt].ep_mps); + + // Claim Interface and prepare transfer + UAC_GOTO_ON_ERROR(uac_host_interface_claim_and_prepare_transfer(iface), "Unable to claim Interface"); + iface_claimed = true; + + if (!(iface->flags & FLAG_STREAM_SUSPEND_AFTER_START)) { + UAC_GOTO_ON_ERROR(uac_host_interface_resume(iface), "Unable to enable UAC Interface"); + } + uac_host_interface_unlock(iface); + return ESP_OK; + +fail: + if (iface_claimed) { + uac_host_interface_release_and_free_transfer(iface); + } + uac_host_interface_unlock(iface); + return ret; +} + +esp_err_t uac_host_device_suspend(uac_host_device_handle_t uac_dev_handle) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_INVALID_ARG(iface); + + UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "Unable to lock UAC Interface"); + if (UAC_INTERFACE_STATE_READY == iface->state) { + uac_host_interface_unlock(iface); + return ESP_OK; + } + esp_err_t ret = ESP_OK; + UAC_GOTO_ON_FALSE((UAC_INTERFACE_STATE_ACTIVE == iface->state), ESP_ERR_INVALID_STATE, "device not active"); + UAC_GOTO_ON_ERROR(uac_host_interface_suspend(iface), "Unable to disable UAC Interface"); + + uac_host_interface_unlock(iface); + return ESP_OK; + +fail: + uac_host_interface_unlock(iface); + return ret; +} + +esp_err_t uac_host_device_resume(uac_host_device_handle_t uac_dev_handle) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_INVALID_ARG(iface); + + UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "Unable to lock UAC Interface"); + if (UAC_INTERFACE_STATE_ACTIVE == iface->state) { + uac_host_interface_unlock(iface); + return ESP_OK; + } + + esp_err_t ret = ESP_OK; + UAC_GOTO_ON_FALSE((UAC_INTERFACE_STATE_READY == iface->state), ESP_ERR_INVALID_STATE, "device not ready"); + UAC_GOTO_ON_ERROR(uac_host_interface_resume(iface), "Unable to enable UAC Interface"); + + uac_host_interface_unlock(iface); + return ESP_OK; + +fail: + uac_host_interface_unlock(iface); + return ret; +} + +esp_err_t uac_host_device_stop(uac_host_device_handle_t uac_dev_handle) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_INVALID_ARG(iface); + + esp_err_t ret = ESP_OK; + UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "Unable to lock UAC Interface"); + if (UAC_INTERFACE_STATE_ACTIVE == iface->state) { + UAC_GOTO_ON_ERROR(uac_host_interface_suspend(iface), "Unable to disable UAC Interface"); + } + + if (UAC_INTERFACE_STATE_READY == iface->state) { + UAC_GOTO_ON_ERROR(uac_host_interface_release_and_free_transfer(iface), "Unable to release UAC Interface"); + } + + uac_host_interface_unlock(iface); + return ESP_OK; + +fail: + uac_host_interface_unlock(iface); + return ret; +} + +esp_err_t uac_host_device_read(uac_host_device_handle_t uac_dev_handle, uint8_t *data, uint32_t size, uint32_t *bytes_read, uint32_t timeout) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_INVALID_ARG(iface); + UAC_RETURN_ON_INVALID_ARG(data); + UAC_RETURN_ON_INVALID_ARG(bytes_read); + + UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "Unable to lock UAC Interface"); + if (UAC_INTERFACE_STATE_ACTIVE != iface->state) { + uac_host_interface_unlock(iface); + return ESP_ERR_INVALID_STATE; + } + uac_host_interface_unlock(iface); + + size_t data_len = _ring_buffer_get_len(iface->ringbuf); + if (data_len > size) { + data_len = size; + } + esp_err_t ret = _ring_buffer_pop(iface->ringbuf, data, data_len, (size_t *)bytes_read, timeout); + + if (ESP_OK != ret) { + ESP_LOGD(TAG, "RX Ringbuffer read failed"); + return ret; + } + + return ESP_OK; +} + +esp_err_t uac_host_device_write(uac_host_device_handle_t uac_dev_handle, uint8_t *data, uint32_t size, uint32_t timeout) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_INVALID_ARG(iface); + UAC_RETURN_ON_INVALID_ARG(data); + + // Only guarantee the data is written to the ringbuffer, in the case + // 1. the interface is active when the write is called + // Data will not be sent to the device and dropped in any of the following cases: + // 1. the pipe state changed to inactive during or after the ringbuffer write + // 2. the pipe state changed to inactive during continuous transfer submit + UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "Unable to lock UAC Interface"); + if (UAC_INTERFACE_STATE_ACTIVE != iface->state) { + uac_host_interface_unlock(iface); + return ESP_ERR_INVALID_STATE; + } + uac_host_interface_unlock(iface); + + esp_err_t ret = _ring_buffer_push(iface->ringbuf, data, size, timeout); + + if (ESP_OK != ret) { + ESP_LOGD(TAG, "TX Ringbuffer write failed"); + return ret; + } + + // We need to submit the transfer if there is free transfer in the list + for (int i = 0; i < iface->xfer_num; i++) { + UAC_ENTER_CRITICAL(); + if (iface->free_xfer_list[i]) { + size_t data_len = _ring_buffer_get_len(iface->ringbuf); + if (data_len == 0) { + goto exit_critical; + } + // if interface state changed to inactive during blocking write + // we need to return invalid state to safely exit the write function + if (UAC_INTERFACE_STATE_ACTIVE != iface->state) { + ret = ESP_ERR_INVALID_STATE; + goto exit_critical; + } + iface->xfer_list[i] = iface->free_xfer_list[i]; + iface->free_xfer_list[i] = NULL; + iface->xfer_list[i]->status = USB_TRANSFER_STATUS_COMPLETED; + UAC_EXIT_CRITICAL(); + stream_tx_xfer_submit(iface->xfer_list[i]); + UAC_ENTER_CRITICAL(); + } +exit_critical: + UAC_EXIT_CRITICAL(); + } + + return ret; +} + +esp_err_t uac_host_get_device_info(uac_host_device_handle_t uac_dev_handle, uac_host_dev_info_t *uac_dev_info) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_FALSE(iface, ESP_ERR_INVALID_STATE, "UAC Interface not found"); + UAC_RETURN_ON_FALSE(uac_dev_info, ESP_ERR_INVALID_ARG, "Wrong argument"); + memcpy(uac_dev_info, &iface->dev_info, sizeof(uac_host_dev_info_t)); + return ESP_OK; +} + +esp_err_t uac_host_device_set_mute(uac_host_device_handle_t uac_dev_handle, bool mute) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_INVALID_ARG(iface); + // Check if the device is active or ready + UAC_RETURN_ON_FALSE((UAC_INTERFACE_STATE_ACTIVE == iface->state || UAC_INTERFACE_STATE_READY == iface->state), + ESP_ERR_INVALID_STATE, "device not ready or active"); + + UAC_RETURN_ON_ERROR(uac_cs_request_set_mute(iface, mute), "Unable to set mute"); + return ESP_OK; +} + +esp_err_t uac_host_device_set_volume(uac_host_device_handle_t uac_dev_handle, uint8_t volume) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_INVALID_ARG(iface); + UAC_RETURN_ON_FALSE(volume <= 100, ESP_ERR_INVALID_ARG, "Invalid volume value"); + // Check if the device is active or ready + UAC_RETURN_ON_FALSE((UAC_INTERFACE_STATE_ACTIVE == iface->state || UAC_INTERFACE_STATE_READY == iface->state), + ESP_ERR_INVALID_STATE, "device not ready or active"); + + uint32_t volume_db = volume * UAC_SPK_VOLUME_STEP + UAC_SPK_VOLUME_MIN; + UAC_RETURN_ON_ERROR(uac_cs_request_set_volume(iface, volume_db), "Unable to set volume"); + return ESP_OK; +} + +esp_err_t uac_host_device_set_volume_db(uac_host_device_handle_t uac_dev_handle, uint32_t volume_db) +{ + uac_iface_t *iface = get_iface_by_handle(uac_dev_handle); + UAC_RETURN_ON_INVALID_ARG(iface); + // Check if the device is active or ready + UAC_RETURN_ON_FALSE((UAC_INTERFACE_STATE_ACTIVE == iface->state || UAC_INTERFACE_STATE_READY == iface->state), + ESP_ERR_INVALID_STATE, "device not ready or active"); + + UAC_RETURN_ON_ERROR(uac_cs_request_set_volume(iface, volume_db), "Unable to set volume"); + return ESP_OK; +}