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 7a828a3f..630de283 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 @@ -26,16 +26,55 @@ enum uvc_host_driver_event { UVC_HOST_DRIVER_EVENT_DEVICE_CONNECTED = 0x0, }; +/** + * @brief Formats supported by this driver + */ +enum uvc_host_stream_format { + UVC_VS_FORMAT_UNDEFINED = 0, // Invalid format. Do not request this format from the camera. + UVC_VS_FORMAT_MJPEG, + UVC_VS_FORMAT_YUY2, + UVC_VS_FORMAT_H264, + UVC_VS_FORMAT_H265, +}; + +/** + * @brief Frame information + * + */ typedef struct { - enum uvc_host_driver_event type; + enum uvc_host_stream_format format; /**< Format of this frame buffer */ + unsigned h_res; /**< Horizontal resolution */ + unsigned v_res; /**< Vertical resolution */ + uint32_t default_interval; /**< Default frame interval */ + uint8_t interval_type; /**< 0: Continuous frame interval, 1..255: The number of discrete frame intervals supported (n) */ union { struct { - uint8_t dev_addr; - uint8_t iface_num; //!< Disconnection event - } device_connected; // UVC_HOST_DEVICE_DISCONNECTED + uint32_t interval_min; /**< Minimum frame interval */ + uint32_t interval_max; /**< Maximum frame interval */ + uint32_t interval_step; /**< Frame interval step */ + }; + uint32_t interval[3]; /**< We must put a fixed size here because of the union type. This is flexible size array though */ + }; +} uvc_host_frame_info_t; + +typedef struct { + enum uvc_host_driver_event type; /**< Event type */ + union { + struct { + uint8_t dev_addr; /**< Device address */ + uint8_t uvc_stream_index; /**< Index of UVC function you want to use. Set to 0 to use first available UVC function */ + uvc_host_frame_info_t *frame_info; /**< Frame information list, it points to the first element of the array, which resides in a contiguous block of memory.*/ + size_t frame_info_num; /**< Number of frame information list */ + } device_connected; /**< UVC_HOST_DEVICE_CONNECTED event */ }; } uvc_host_driver_event_data_t; +/** + * @brief USB Host UVC driver event callback function + * + * @param[out] event Event structure + * @param[out] user_ctx User's argument passed to open function + */ typedef void (*uvc_host_driver_event_callback_t)(const uvc_host_driver_event_data_t *event, void *user_ctx); /** @@ -83,17 +122,6 @@ typedef struct { }; } uvc_host_stream_event_data_t; -/** - * @brief Formats supported by this driver - */ -enum uvc_host_stream_format { - UVC_VS_FORMAT_UNDEFINED = 0, // Invalid format. Do not request this format from the camera. - UVC_VS_FORMAT_MJPEG, - UVC_VS_FORMAT_YUY2, - UVC_VS_FORMAT_H264, - UVC_VS_FORMAT_H265, -}; - typedef struct { unsigned h_res; /**< Horizontal resolution */ unsigned v_res; /**< Vertical resolution */ 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 676f25d6..2c549258 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 @@ -76,10 +76,53 @@ 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); +/** + * @brief Check if the given USB configuration descriptor belongs to a UVC (USB Video Class) device. + * + * This function iterates through the descriptors in the provided configuration descriptor to determine + * if there is any interface descriptor indicating the device is a UVC device. + * + * @param[in] cfg_desc Pointer to the USB configuration descriptor. + * + * @return + * - true: If the configuration descriptor contains a UVC interface. + * - false: Otherwise. + */ bool uvc_desc_is_uvc_device(const usb_config_desc_t *cfg_desc); +/** + * @brief Print UVC specific descriptor in human readable form + * + * This is a callback function that is called from USB Host library, + * when it wants to print full configuration descriptor to stdout. + * + * @param[in] _desc UVC specific descriptor + */ void uvc_print_desc(const usb_standard_desc_t *_desc); +/** + * @brief Retrieve the list of frame descriptors for a specific streaming interface in a UVC device. + * + * This function extracts all frame descriptors associated with the given interface number + * and organizes them into a list of `uvc_host_frame_info_t` structures. + * + * @param[in] config_desc Pointer to the USB configuration descriptor. + * @param[in] bInterfaceNumber The interface number to search for frame descriptors. + * @param[out] frame_info_list Pointer to a list of frame info structures (allocated dynamically). + * @param[out] list_size Pointer to store the number of frames in the list. + * + * @return + * - ESP_OK: Success. + * - ESP_ERR_INVALID_ARG: One or more invalid arguments. + * - ESP_ERR_NOT_FOUND: Input header descriptor not found. + * - ESP_ERR_NO_MEM: Memory allocation failure. + */ +esp_err_t uvc_desc_get_frame_list( + const usb_config_desc_t *config_desc, + uint8_t bInterfaceNumber, + uvc_host_frame_info_t **frame_info_list, + size_t *list_size); + #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 3ffb8c52..fc2795a8 100644 --- a/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c +++ b/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c @@ -391,3 +391,85 @@ bool uvc_desc_is_uvc_device(const usb_config_desc_t *cfg_desc) } return false; } + +esp_err_t uvc_desc_get_frame_list(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uvc_host_frame_info_t **frame_info_list, size_t *list_size) +{ + esp_err_t ret = ESP_OK; + UVC_CHECK(config_desc && frame_info_list && list_size, ESP_ERR_INVALID_ARG); + uvc_host_frame_info_t *new_frame_info = NULL; + size_t num_frame = 0; + *list_size = 0; + const uvc_vs_input_header_desc_t *input_header = uvc_desc_get_streaming_input_header(config_desc, bInterfaceNumber); + UVC_CHECK(input_header, ESP_ERR_NOT_FOUND); + + // Find requested Format descriptors + int format_offset = 0; + const usb_standard_desc_t *current_desc = (const usb_standard_desc_t *)input_header; + while ((current_desc = usb_parse_next_descriptor_of_type(current_desc, input_header->wTotalLength, UVC_CS_INTERFACE, &format_offset))) { + if (!uvc_desc_is_format_desc(current_desc)) { + continue; + } + + const uvc_format_desc_t *this_format = (const uvc_format_desc_t *)(current_desc); + enum uvc_host_stream_format format_type = uvc_desc_parse_format(this_format); + if (UVC_VS_FORMAT_UNDEFINED == format_type) { + continue; + } + + num_frame = this_format->bNumFrameDescriptors; + new_frame_info = calloc(num_frame, sizeof(uvc_host_frame_info_t)); + UVC_CHECK(new_frame_info != NULL, ESP_ERR_NO_MEM); + + // We found required Format Descriptor + // Now we look for correct Frame Descriptors which should be directly after Format + while ((current_desc = usb_parse_next_descriptor_of_type(current_desc, input_header->wTotalLength, UVC_CS_INTERFACE, &format_offset))) { + if (!uvc_desc_is_frame_desc(current_desc)) { + break; + } + uvc_frame_desc_t *this_frame = (uvc_frame_desc_t *)current_desc; + + uvc_host_frame_info_t *frame_info = &(new_frame_info)[*list_size]; + frame_info->format = format_type; + frame_info->h_res = this_frame->wWidth; + frame_info->v_res = this_frame->wHeight; + switch (format_type) { + case UVC_VS_FORMAT_MJPEG: + frame_info->default_interval = this_frame->mjpeg_uncompressed.dwDefaultFrameInterval; + frame_info->interval_type = this_frame->mjpeg_uncompressed.bFrameIntervalType; + if (frame_info->interval_type == 0) { + frame_info->interval_min = this_frame->mjpeg_uncompressed.dwMinFrameInterval; + frame_info->interval_max = this_frame->mjpeg_uncompressed.dwMaxFrameInterval; + frame_info->interval_step = this_frame->mjpeg_uncompressed.dwFrameIntervalStep; + } else { + // TODO: make 3 can be configured + for (int i = 0; i < 3; i ++) { + frame_info->interval[i] = this_frame->mjpeg_uncompressed.dwFrameInterval[i]; + } + } + break; + case UVC_VS_FORMAT_H265: + case UVC_VS_FORMAT_H264: + frame_info->default_interval = this_frame->frame_based.dwDefaultFrameInterval; + frame_info->interval_type = this_frame->frame_based.bFrameIntervalType; + if (frame_info->interval_type == 0) { + frame_info->interval_min = this_frame->frame_based.dwMinFrameInterval; + frame_info->interval_max = this_frame->frame_based.dwMaxFrameInterval; + frame_info->interval_step = this_frame->frame_based.dwFrameIntervalStep; + } else { + // TODO: make 3 can be configured + for (int i = 0; i < 3; i ++) { + frame_info->interval[i] = this_frame->frame_based.dwFrameInterval[i]; + } + } + break; + default: + break; + } + + (*list_size)++; + } + } + *frame_info_list = new_frame_info; + assert(*list_size == num_frame); + return ret; +} \ No newline at end of file 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 bd21baaa..283c9562 100644 --- a/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c +++ b/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c @@ -357,14 +357,6 @@ static void print_class_specific_desc(const usb_standard_desc_t *_desc) } } -/** - * @brief Print UVC specific descriptor in human readable form - * - * This is a callback function that is called from USB Host library, - * when it wants to print full configuration descriptor to stdout. - * - * @param[in] _desc UVC specific descriptor - */ void uvc_print_desc(const usb_standard_desc_t *_desc) { switch (_desc->bDescriptorType) { diff --git a/host/class/uvc/usb_host_uvc/uvc_host.c b/host/class/uvc/usb_host_uvc/uvc_host.c index f70ecc1c..585f6ca5 100644 --- a/host/class/uvc/usb_host_uvc/uvc_host.c +++ b/host/class/uvc/usb_host_uvc/uvc_host.c @@ -66,6 +66,7 @@ static esp_err_t uvc_host_interface_check(uint8_t addr, const usb_config_desc_t size_t total_length = config_desc->wTotalLength; int iface_offset = 0; bool is_uvc_interface = false; + uint8_t uvc_stream_index = 0; // Get first Interface descriptor // Check every uac stream interface @@ -75,24 +76,25 @@ static esp_err_t uvc_host_interface_check(uint8_t addr, const usb_config_desc_t if (USB_CLASS_VIDEO == iface_desc->bInterfaceClass && UVC_SC_VIDEOSTREAMING == iface_desc->bInterfaceSubClass) { // notify user about the connected Interfaces is_uvc_interface = true; + uvc_stream_index++; if (p_uvc_host_driver->user_cb) { + uvc_host_frame_info_t *frame_info = NULL; + size_t frame_info_num = 0; + if (uvc_desc_get_frame_list(config_desc, iface_desc->bInterfaceNumber, &frame_info, &frame_info_num) != ESP_OK) { + ESP_LOGE(TAG, "Failed to get frame list for interface %d", iface_desc->bInterfaceNumber); + return ESP_FAIL; + } + 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, + .device_connected.uvc_stream_index = uvc_stream_index, + .device_connected.frame_info = frame_info, + .device_connected.frame_info_num = frame_info_num }; 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); + free(frame_info); } } }