Skip to content

Commit

Permalink
feat(usb_host_uvc): support get frame list when device insert
Browse files Browse the repository at this point in the history
  • Loading branch information
lijunru-hub committed Jan 8, 2025
1 parent 3fa43ce commit 290d524
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 34 deletions.
58 changes: 43 additions & 15 deletions host/class/uvc/usb_host_uvc/include/usb/uvc_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/**
Expand Down Expand Up @@ -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 */
Expand Down
43 changes: 43 additions & 0 deletions host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
82 changes: 82 additions & 0 deletions host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Check warning

Code scanning / clang-tidy

Potential leak of memory pointed to by 'frame_info' [clang-analyzer-unix.Malloc] Warning

Potential leak of memory pointed to by 'frame_info' [clang-analyzer-unix.Malloc]

Check warning

Code scanning / clang-tidy

Potential leak of memory pointed to by 'new_frame_info' [clang-analyzer-unix.Malloc] Warning

Potential leak of memory pointed to by 'new_frame_info' [clang-analyzer-unix.Malloc]

// 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;
}
8 changes: 0 additions & 8 deletions host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
24 changes: 13 additions & 11 deletions host/class/uvc/usb_host_uvc/uvc_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
}
}
Expand Down

0 comments on commit 290d524

Please sign in to comment.