From 3fa43ce9c864a2e2f4cacfb70f2eff67f41917b0 Mon Sep 17 00:00:00 2001 From: Li Junru Date: Fri, 3 Jan 2025 20:27:35 +0800 Subject: [PATCH] feat(usb_host_uvc): support dual camera with hub --- .../uvc/usb_host_uvc/include/usb/uvc_host.h | 20 +++ .../private_include/uvc_descriptors_priv.h | 5 + .../uvc/usb_host_uvc/uvc_descriptor_parsing.c | 16 +++ .../usb_host_uvc/uvc_descriptor_printing.c | 2 +- host/class/uvc/usb_host_uvc/uvc_host.c | 127 +++++++++++++++--- 5 files changed, 152 insertions(+), 18 deletions(-) diff --git a/host/class/uvc/usb_host_uvc/include/usb/uvc_host.h b/host/class/uvc/usb_host_uvc/include/usb/uvc_host.h index 195b7ffe..7a828a3f 100644 --- a/host/class/uvc/usb_host_uvc/include/usb/uvc_host.h +++ b/host/class/uvc/usb_host_uvc/include/usb/uvc_host.h @@ -14,6 +14,7 @@ // Use this macros for opening a UVC stream with any VID or PID #define UVC_HOST_ANY_VID (0) #define UVC_HOST_ANY_PID (0) +#define UVC_HOST_ANY_DEV_ADDR (0) #ifdef __cplusplus extern "C" { @@ -21,6 +22,22 @@ extern "C" { typedef struct uvc_host_stream_s *uvc_host_stream_hdl_t; +enum uvc_host_driver_event { + UVC_HOST_DRIVER_EVENT_DEVICE_CONNECTED = 0x0, +}; + +typedef struct { + enum uvc_host_driver_event type; + union { + struct { + uint8_t dev_addr; + uint8_t iface_num; //!< Disconnection event + } device_connected; // UVC_HOST_DEVICE_DISCONNECTED + }; +} uvc_host_driver_event_data_t; + +typedef void (*uvc_host_driver_event_callback_t)(const uvc_host_driver_event_data_t *event, void *user_ctx); + /** * @brief Configuration structure of USB Host UVC driver */ @@ -30,6 +47,8 @@ typedef struct { int xCoreID; /**< Core affinity of the driver's task */ bool create_background_task; /**< When set to true, background task handling usb events is created. Otherwise user has to periodically call uvc_host_handle_events function */ + uvc_host_driver_event_callback_t event_cb; /**< Callback function to handle events */ + void *user_ctx; } uvc_host_driver_config_t; /** @@ -122,6 +141,7 @@ typedef struct { uvc_host_frame_callback_t frame_cb; /**< Stream's frame callback function */ void *user_ctx; /**< User's argument that will be passed to the callbacks */ struct { + uint8_t dev_addr; /**< USB address of device. Set to 0 for any. */ uint16_t vid; /**< Device's Vendor ID. Set to 0 for any */ uint16_t pid; /**< Device's Product ID. Set to 0 for any */ uint8_t uvc_stream_index; /**< Index of UVC function you want to use. Set to 0 to use first available UVC function */ diff --git a/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h b/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h index b110ff56..676f25d6 100644 --- a/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h +++ b/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h @@ -15,6 +15,7 @@ #define UVC_DESC_FPS_TO_DWFRAMEINTERVAL(fps) (((fps) != 0) ? 10000000.0f / (fps) : 0) #define UVC_DESC_DWFRAMEINTERVAL_TO_FPS(dwFrameInterval) (((dwFrameInterval) != 0) ? 10000000.0f / ((float)(dwFrameInterval)) : 0) + #ifdef __cplusplus extern "C" { #endif @@ -75,6 +76,10 @@ esp_err_t uvc_desc_get_frame_format_by_format( const uvc_format_desc_t **format_desc_ret, const uvc_frame_desc_t **frame_desc_ret); +bool uvc_desc_is_uvc_device(const usb_config_desc_t *cfg_desc); + +void uvc_print_desc(const usb_standard_desc_t *_desc); + #ifdef __cplusplus } #endif diff --git a/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c b/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c index 2f891609..3ffb8c52 100644 --- a/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c +++ b/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c @@ -375,3 +375,19 @@ esp_err_t uvc_desc_get_streaming_interface_num( } return ret; } + +bool uvc_desc_is_uvc_device(const usb_config_desc_t *cfg_desc) +{ + assert(cfg_desc); + int offset = 0; + int total_len = cfg_desc->wTotalLength; + + const usb_standard_desc_t *current_desc = (const usb_standard_desc_t *)cfg_desc; + while ((current_desc = usb_parse_next_descriptor_of_type((const usb_standard_desc_t *)current_desc, total_len, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset))) { + const usb_intf_desc_t *iface_desc = (const usb_intf_desc_t *)current_desc; + if (USB_CLASS_VIDEO == iface_desc->bInterfaceClass) { + return true; + } + } + return false; +} diff --git a/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c b/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c index e0d54a3d..bd21baaa 100644 --- a/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c +++ b/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c @@ -365,7 +365,7 @@ static void print_class_specific_desc(const usb_standard_desc_t *_desc) * * @param[in] _desc UVC specific descriptor */ -static void uvc_print_desc(const usb_standard_desc_t *_desc) +void uvc_print_desc(const usb_standard_desc_t *_desc) { switch (_desc->bDescriptorType) { case UVC_CS_INTERFACE: diff --git a/host/class/uvc/usb_host_uvc/uvc_host.c b/host/class/uvc/usb_host_uvc/uvc_host.c index c9b71303..f70ecc1c 100644 --- a/host/class/uvc/usb_host_uvc/uvc_host.c +++ b/host/class/uvc/usb_host_uvc/uvc_host.c @@ -48,16 +48,90 @@ void bulk_transfer_callback(usb_transfer_t *transfer); // UVC driver object typedef struct { - usb_host_client_handle_t usb_client_hdl; /*!< USB Host handle reused for all UVC devices in the system */ - SemaphoreHandle_t open_close_mutex; /*!< Protects list of opened devices from concurrent access */ - EventGroupHandle_t driver_status; /*!< Holds status of the driver */ - usb_transfer_t *ctrl_transfer; /*!< CTRL (endpoint 0) transfer */ - SemaphoreHandle_t ctrl_mutex; /*!< CTRL mutex */ + usb_host_client_handle_t usb_client_hdl; /*!< USB Host handle reused for all UVC devices in the system */ + SemaphoreHandle_t open_close_mutex; /*!< Protects list of opened devices from concurrent access */ + EventGroupHandle_t driver_status; /*!< Holds status of the driver */ + usb_transfer_t *ctrl_transfer; /*!< CTRL (endpoint 0) transfer */ + SemaphoreHandle_t ctrl_mutex; /*!< CTRL mutex */ + uvc_host_driver_event_callback_t user_cb; /*!< Callback function to handle events */ + void *user_ctx; SLIST_HEAD(list_dev, uvc_host_stream_s) uvc_stream_list; /*!< List of open streams */ } uvc_host_driver_t; static uvc_host_driver_t *p_uvc_host_driver = NULL; +static esp_err_t uvc_host_interface_check(uint8_t addr, const usb_config_desc_t *config_desc) +{ + assert(config_desc); + size_t total_length = config_desc->wTotalLength; + int iface_offset = 0; + bool is_uvc_interface = false; + + // Get first Interface descriptor + // Check every uac stream interface + const usb_standard_desc_t *current_desc = (const usb_standard_desc_t *)config_desc; + while ((current_desc = usb_parse_next_descriptor_of_type((const usb_standard_desc_t *)current_desc, total_length, USB_B_DESCRIPTOR_TYPE_INTERFACE, &iface_offset))) { + const usb_intf_desc_t *iface_desc = (const usb_intf_desc_t *)current_desc; + if (USB_CLASS_VIDEO == iface_desc->bInterfaceClass && UVC_SC_VIDEOSTREAMING == iface_desc->bInterfaceSubClass) { + // notify user about the connected Interfaces + is_uvc_interface = true; + + if (p_uvc_host_driver->user_cb) { + const uvc_host_driver_event_data_t conn_event = { + .type = UVC_HOST_DRIVER_EVENT_DEVICE_CONNECTED, + .device_connected.dev_addr = addr, + .device_connected.iface_num = iface_desc->bInterfaceNumber, + }; + p_uvc_host_driver->user_cb(&conn_event, p_uvc_host_driver->user_ctx); + } + + const usb_intf_desc_t *iface_alt_desc = GET_NEXT_INTERFACE_DESC(iface_desc, total_length, iface_offset); + // 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); + } + } + } + + return is_uvc_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 uvc_host_device_connected(uint8_t addr) +{ + bool is_uvc_device = false; + usb_device_handle_t dev_hdl; + const usb_config_desc_t *config_desc = NULL; + + if (usb_host_device_open(p_uvc_host_driver->usb_client_hdl, addr, &dev_hdl) == ESP_OK) { + if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) == ESP_OK) { + is_uvc_device = uvc_desc_is_uvc_device(config_desc); + } + ESP_RETURN_ON_ERROR(usb_host_device_close(p_uvc_host_driver->usb_client_hdl, dev_hdl), TAG, "Unable to close USB device"); + } + + // Create UAC interfaces list in RAM, connected to the particular USB dev + if (is_uvc_device) { + // TODO: add config to control it + usb_print_config_descriptor(config_desc, &uvc_print_desc); + // Create Interfaces list for a possibility to claim Interface + ESP_RETURN_ON_ERROR(uvc_host_interface_check(addr, config_desc), TAG, "uvc stream interface not found"); + } else { + ESP_LOGW(TAG, "USB device with addr(%d) is not UVC device", addr); + } + + return is_uvc_device ? ESP_OK : ESP_ERR_NOT_FOUND; +} + /** * @brief USB Host Client event callback * @@ -71,6 +145,7 @@ static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg switch (event_msg->event) { case USB_HOST_CLIENT_EVENT_NEW_DEV: ESP_LOGD(TAG, "New device connected"); + uvc_host_device_connected(event_msg->new_dev.address); break; case USB_HOST_CLIENT_EVENT_DEV_GONE: { ESP_LOGD(TAG, "Device suddenly disconnected"); @@ -247,10 +322,12 @@ static void uvc_device_remove(uvc_stream_t *uvc_stream) * @brief Open USB device with requested VID/PID * * This function has two regular return paths: + * TODO: dev_addr * 1. USB device with matching VID/PID is already opened by this driver: allocate new UVC device on top of the already opened USB device. * 2. USB device with matching VID/PID is NOT opened by this driver yet: poll USB connected devices until it is found. * * @note This function will block for timeout_ticks, if the device is not enumerated at the moment of calling this function. + * @param[in] dev_addr Device address * @param[in] vid Vendor ID * @param[in] pid Product ID * @param[in] timeout_ticks Connection timeout in FreeRTOS ticks @@ -259,7 +336,7 @@ static void uvc_device_remove(uvc_stream_t *uvc_stream) * - ESP_OK: Success - device opened * - ESP_ERR_NOT_FOUND: Device not found in given timeout */ -static esp_err_t uvc_find_and_open_usb_device(uint16_t vid, uint16_t pid, TickType_t timeout_ticks, uvc_stream_t **dev) +static esp_err_t uvc_find_and_open_usb_device(uint8_t dev_addr, uint16_t vid, uint16_t pid, TickType_t timeout_ticks, uvc_stream_t **dev) { assert(p_uvc_host_driver); assert(dev); @@ -274,11 +351,15 @@ static esp_err_t uvc_find_and_open_usb_device(uint16_t vid, uint16_t pid, TickTy uvc_stream_t *uvc_stream; SLIST_FOREACH(uvc_stream, &p_uvc_host_driver->uvc_stream_list, list_entry) { const usb_device_desc_t *device_desc; + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(uvc_stream->constant.dev_hdl, &dev_info)); ESP_ERROR_CHECK(usb_host_get_device_descriptor(uvc_stream->constant.dev_hdl, &device_desc)); if ((vid == device_desc->idVendor || vid == UVC_HOST_ANY_VID) && - (pid == device_desc->idProduct || pid == UVC_HOST_ANY_PID)) { - // Return path 1: + (pid == device_desc->idProduct || pid == UVC_HOST_ANY_PID) && + (dev_addr == dev_info.dev_addr || dev_addr == UVC_HOST_ANY_DEV_ADDR)) { + // Return path 1: t (*dev)->constant.dev_hdl = uvc_stream->constant.dev_hdl; + printf("UVC device already opened by this driver\n"); return ESP_OK; } } @@ -297,18 +378,28 @@ static esp_err_t uvc_find_and_open_usb_device(uint16_t vid, uint16_t pid, TickTy // Go through device address list and find the one we are looking for for (int i = 0; i < num_of_devices; i++) { usb_device_handle_t current_device; + bool is_uvc_device = false; + const usb_config_desc_t *config_desc = NULL; + // Open USB device if (usb_host_device_open(p_uvc_host_driver->usb_client_hdl, dev_addr_list[i], ¤t_device) != ESP_OK) { continue; // In case we failed to open this device, continue with next one in the list } - assert(current_device); - const usb_device_desc_t *device_desc; - ESP_ERROR_CHECK(usb_host_get_device_descriptor(current_device, &device_desc)); - if ((vid == device_desc->idVendor || vid == UVC_HOST_ANY_VID) && - (pid == device_desc->idProduct || pid == UVC_HOST_ANY_PID)) { - // Return path 2: - (*dev)->constant.dev_hdl = current_device; - return ESP_OK; + // Skip non-UVC devices + if (usb_host_get_active_config_descriptor(current_device, &config_desc) == ESP_OK) { + is_uvc_device = uvc_desc_is_uvc_device(config_desc); + } + if (is_uvc_device) { + assert(current_device); + const usb_device_desc_t *device_desc; + ESP_ERROR_CHECK(usb_host_get_device_descriptor(current_device, &device_desc)); + if ((vid == device_desc->idVendor || vid == UVC_HOST_ANY_VID) && + (pid == device_desc->idProduct || pid == UVC_HOST_ANY_PID) && + (dev_addr == dev_addr_list[i] || dev_addr == UVC_HOST_ANY_DEV_ADDR)) { + // Return path 2: + (*dev)->constant.dev_hdl = current_device; + return ESP_OK; + } } usb_host_device_close(p_uvc_host_driver->usb_client_hdl, current_device); } @@ -431,6 +522,8 @@ esp_err_t uvc_host_install(const uvc_host_driver_config_t *driver_config) uvc_obj->ctrl_transfer->bEndpointAddress = 0; uvc_obj->ctrl_transfer->timeout_ms = 5000; uvc_obj->ctrl_transfer->callback = ctrl_xfer_cb; + uvc_obj->user_cb = driver_config->event_cb; + uvc_obj->user_ctx = driver_config->user_ctx; // Between 1st call of this function and following section, another task might try to install this driver: // Make sure that there is only one instance of this driver in the system @@ -530,7 +623,7 @@ esp_err_t uvc_host_stream_open(const uvc_host_stream_config_t *stream_config, in xSemaphoreTake(p_uvc_host_driver->open_close_mutex, portMAX_DELAY); // Find underlying USB device - ret = uvc_find_and_open_usb_device(stream_config->usb.vid, stream_config->usb.pid, timeout, &uvc_stream); + ret = uvc_find_and_open_usb_device(stream_config->usb.dev_addr, stream_config->usb.vid, stream_config->usb.pid, timeout, &uvc_stream); if (ESP_OK != ret) { goto not_found; }