Skip to content

Commit

Permalink
Merge pull request #45 from leeebo/feat/host_uac_open_with_vid_pid
Browse files Browse the repository at this point in the history
feat(host_uac): support open device with known vid pid
  • Loading branch information
tore-espressif authored Aug 13, 2024
2 parents 561bc3c + a04eaab commit 84ac23b
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 5 deletions.
9 changes: 9 additions & 0 deletions host/class/uac/usb_host_uac/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# Changelog for USB Host UAC

## 1.1.0

### Improvements

- Added add `uac_host_device_open_with_vid_pid` to open connected audio devices with known VID and PID
- Print component version message `uac-host: Install Succeed, Version: 1.1.0` in `uac_host_install` function

## 1.0.0

- Initial version
3 changes: 3 additions & 0 deletions host/class/uac/usb_host_uac/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
idf_component_register( SRCS "uac_descriptors.c" "uac_host.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES usb esp_ringbuf)

include(package_manager)
cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR})
6 changes: 6 additions & 0 deletions host/class/uac/usb_host_uac/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ menu "USB Host UAC"
default n
help
Print UAC Configuration Descriptor to console.
config UAC_DEV_ADDR_LIST_MAX
int "Max USB Device Address List"
default 6
help
Max USB Device Address List to find the UAC device. If more devices are connected.
The driver will only use the first UAC_DEV_ADDR_LIST_MAX devices.
config UAC_FREQ_NUM_MAX
int "Max Number of Frequencies each Alt-interface supports"
default 4
Expand Down
3 changes: 2 additions & 1 deletion host/class/uac/usb_host_uac/idf_component.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
## IDF Component Manager Manifest File
version: "1.0.0"
version: "1.1.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"
cmake_utilities: "0.5.*"
targets:
- esp32s2
- esp32s3
Expand Down
25 changes: 24 additions & 1 deletion host/class/uac/usb_host_uac/include/usb/uac_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,10 @@ esp_err_t uac_host_install(const uac_host_driver_config_t *config);
esp_err_t uac_host_uninstall(void);

/**
* @brief Open a UAC logic device/interface
* @brief Open a UAC logic device/interface when new UAC device is connected
*
* @note The config->addr and config->iface_num should be retrieved from the UAC driver event callback
* after UAC_HOST_DRIVER_EVENT_RX_CONNECTED or UAC_HOST_DRIVER_EVENT_TX_CONNECTED event
* @param[in] config Pointer to UAC device configuration structure
* @param[out] uac_dev_handle Pointer to UAC device handle
* @return esp_err_t
Expand All @@ -210,6 +212,27 @@ esp_err_t uac_host_uninstall(void);
*/
esp_err_t uac_host_device_open(const uac_host_device_config_t *config, uac_host_device_handle_t *uac_dev_handle);

/**
* @brief Open a UAC logic device/interface with specific VID, PID and interface number
*
* @note The config->addr is not used in this function, the VID, PID and config->iface_num should be provided by user.
* If multiple hardware devices with the same VID and PID are connected through USB hub, the first one will be opened.
*
* @param[in] vid Vendor ID
* @param[in] pid Product ID
* @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_NOT_FOUND if the device is not found
*/
esp_err_t uac_host_device_open_with_vid_pid(uint16_t vid, uint16_t pid, const uac_host_device_config_t *config,
uac_host_device_handle_t *uac_dev_handle);

/**
* @brief Close a UAC logic device/interface
*
Expand Down
89 changes: 89 additions & 0 deletions host/class/uac/usb_host_uac/test_app/main/test_host_uac.c
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,95 @@ TEST_CASE("test uac device handling", "[uac_host][known_device]")
}
}

/**
* @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 with known pid vid", "[uac_host][known_device]")
{
uint8_t mic_iface_num = UAC_DEV_MIC_IFACE_NUM;
uint8_t spk_iface_num = UAC_DEV_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;
// check if device params as expected
uac_host_device_config_t dev_config = {
.iface_num = mic_iface_num,
.buffer_size = 16000,
.buffer_threshold = 4000,
.callback = uac_device_callback,
.callback_arg = NULL,
};

do {
esp_err_t ret = uac_host_device_open_with_vid_pid(UAC_DEV_VID, UAC_DEV_PID, &dev_config, &mic_device_handle);
if (ret == ESP_ERR_NOT_FOUND) {
ESP_LOGI(TAG, "Device not found, please connect the device");
vTaskDelay(1000);
}
} while (mic_device_handle == NULL);

TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_open_with_vid_pid(UAC_DEV_VID, UAC_DEV_PID, &dev_config, &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;
dev_config.iface_num = spk_iface_num;
TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_open_with_vid_pid(UAC_DEV_VID, UAC_DEV_PID, &dev_config, &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
*/
Expand Down
97 changes: 94 additions & 3 deletions host/class/uac/usb_host_uac/uac_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,26 @@ static uac_device_t *get_uac_device_by_addr(uint8_t addr)
return NULL;
}

/**
* @brief Return UAC Device from USB device VID and PID
* @param[in] vid USB device VID
* @param[in] pid USB device PID
* @return uac_device_t Pointer to UAC Device
*/
static uac_device_t *get_uac_device_by_vid_pid(uint16_t vid, uint16_t pid)
{
uac_iface_t *interface = NULL;

UAC_ENTER_CRITICAL();
STAILQ_FOREACH(interface, &s_uac_driver->uac_ifaces_tailq, tailq_entry) {
if (vid == interface->dev_info.VID && pid == interface->dev_info.PID) {
UAC_EXIT_CRITICAL();
return interface->parent;
}
}
UAC_EXIT_CRITICAL();
return NULL;
}

/**
* @brief Get UAC Interface pointer by Interface number
Expand All @@ -345,6 +365,29 @@ static uac_iface_t *get_interface_by_addr(uint8_t addr, uint8_t iface_num)
return NULL;
}

/**
* @brief Get UAC Interface pointer by vid, pid and Interface number
* @param[in] vid USB device VID
* @param[in] pid USB device PID
* @param[in] iface_num Interface number
* @return uac_iface_t Pointer to UAC Interface configuration structure
*/
static uac_iface_t *get_interface_by_vid_pid(uint16_t vid, uint16_t pid, uint8_t iface_num)
{
uac_iface_t *interface = NULL;

UAC_ENTER_CRITICAL();
STAILQ_FOREACH(interface, &s_uac_driver->uac_ifaces_tailq, tailq_entry) {
if (vid == interface->dev_info.VID && pid == interface->dev_info.PID && 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
*
Expand Down Expand Up @@ -1331,15 +1374,15 @@ static esp_err_t _uac_host_device_add(uint8_t addr, usb_device_handle_t dev_hdl,
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);
ESP_LOGW(TAG, "UAC version 0x%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);
ESP_LOGD(TAG, "UAC version 0x%04X", header_desc->bcdADC);
break;
}
default:
Expand Down Expand Up @@ -1678,7 +1721,7 @@ esp_err_t uac_host_install(const uac_host_driver_config_t *config)
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");
}

ESP_LOGI(TAG, "Install Succeed, Version: %d.%d.%d", USB_HOST_UAC_VER_MAJOR, USB_HOST_UAC_VER_MINOR, USB_HOST_UAC_VER_PATCH);
return ESP_OK;

fail:
Expand Down Expand Up @@ -1790,6 +1833,54 @@ esp_err_t uac_host_device_open(const uac_host_device_config_t *config, uac_host_
return ret;
}

esp_err_t uac_host_device_open_with_vid_pid(uint16_t vid, uint16_t pid, 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_RETURN_ON_FALSE(config->iface_num, ESP_ERR_INVALID_ARG, "Invalid interface number");
UAC_RETURN_ON_FALSE(s_uac_driver, ESP_ERR_INVALID_STATE, "UAC Driver is not installed");
UAC_RETURN_ON_FALSE(vid, ESP_ERR_INVALID_ARG, "Invalid vendor ID");
UAC_RETURN_ON_FALSE(pid, ESP_ERR_INVALID_ARG, "Invalid product ID");

// check if the device is already opened
*uac_dev_handle = get_interface_by_vid_pid(vid, pid, config->iface_num);
if (*uac_dev_handle) {
ESP_LOGD(TAG, "Device VID 0x%04X, PID 0x%04X, Interface %d already opened", vid, pid, config->iface_num);
return ESP_OK;
}

// check all connected devices
int actual_count = 0;
uint8_t dev_addr_list[CONFIG_UAC_DEV_ADDR_LIST_MAX] = {0};
usb_host_device_addr_list_fill(sizeof(dev_addr_list), dev_addr_list, &actual_count);
if (actual_count == 0) {
return ESP_ERR_NOT_FOUND;
}

for (int i = 0; i < actual_count; i++) {
const usb_device_desc_t *dev_desc = NULL;
usb_device_handle_t dev_hdl = NULL;
uac_device_t *uac_dev = get_uac_device_by_vid_pid(vid, pid);
uac_host_device_config_t config_copy = *config;
config_copy.addr = dev_addr_list[i];
if (uac_dev) {
ESP_LOGD(TAG, "Found Device VID 0x%04X, PID 0x%04X", vid, pid);
return uac_host_device_open(&config_copy, uac_dev_handle);
} else {
UAC_RETURN_ON_ERROR(usb_host_device_open(s_uac_driver->client_handle, dev_addr_list[i], &dev_hdl), "Unable to open USB device");
UAC_RETURN_ON_ERROR(usb_host_get_device_descriptor(dev_hdl, &dev_desc), "Unable to get device descriptor");
ESP_LOGD(TAG, "Found Device VID 0x%04X, PID 0x%04X", dev_desc->idVendor, dev_desc->idProduct);
if (dev_desc->idVendor == vid && dev_desc->idProduct == pid) {
usb_host_device_close(s_uac_driver->client_handle, dev_hdl);
return uac_host_device_open(&config_copy, uac_dev_handle);
}
usb_host_device_close(s_uac_driver->client_handle, dev_hdl);
}
}

return ESP_ERR_NOT_FOUND;
}

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);
Expand Down

0 comments on commit 84ac23b

Please sign in to comment.