Skip to content

Commit afe693e

Browse files
committed
feat(usb_host_uvc): add support for user-provided frame buffers
1 parent 63a4825 commit afe693e

File tree

10 files changed

+177
-19
lines changed

10 files changed

+177
-19
lines changed

host/class/uvc/usb_host_uvc/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 2.4.0
2+
3+
- Added support for user-provided frame buffers via `user_frame_buffers` field in `uvc_host_stream_config_t.advanced`
4+
- Fixed assertion failure when receiving packets with unexpected `frame_id`
5+
16
## 2.3.1
27

38
- Added support for ESP32-H4

host/class/uvc/usb_host_uvc/host_test/main/streaming/test_streaming.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ void run_streaming_frame_reconstruction_scenario(void)
6969
return true;
7070
};
7171

72-
REQUIRE(uvc_frame_allocate(&stream, 1, 100 * 1024, 0) == ESP_OK);
72+
REQUIRE(uvc_frame_allocate(&stream, 1, 100 * 1024, 0, NULL) == ESP_OK);
7373
uvc_frame_format_update(&stream, &logo_jpg_format);
7474

7575
// Test
@@ -147,7 +147,7 @@ void run_streaming_frame_reconstruction_scenario(void)
147147
enum uvc_host_dev_event *event_type = static_cast<enum uvc_host_dev_event *>(user_ctx);
148148
*event_type = event->type;
149149
};
150-
REQUIRE(uvc_frame_allocate(&stream, 1, logo_jpg.size() - 100, 0) == ESP_OK);
150+
REQUIRE(uvc_frame_allocate(&stream, 1, logo_jpg.size() - 100, 0, NULL) == ESP_OK);
151151

152152
WHEN("The frame is too big") {
153153
send_function_wrapper(1024, &stream, std::span(logo_jpg));
@@ -170,7 +170,7 @@ void run_streaming_frame_reconstruction_scenario(void)
170170
};
171171

172172
uvc_host_frame_t *temp_frame = nullptr;
173-
REQUIRE(uvc_frame_allocate(&stream, 1, 100 * 1024, 0) == ESP_OK);
173+
REQUIRE(uvc_frame_allocate(&stream, 1, 100 * 1024, 0, NULL) == ESP_OK);
174174
temp_frame = uvc_frame_get_empty(&stream);
175175
REQUIRE(temp_frame != nullptr);
176176

@@ -236,7 +236,7 @@ SCENARIO("Bulk stream frame reconstruction", "[streaming][bulk]")
236236

237237
GIVEN("Streaming enabled and frame allocated") {
238238
REQUIRE(uvc_host_stream_unpause(&stream) == ESP_OK);
239-
REQUIRE(uvc_frame_allocate(&stream, 1, 100 * 1024, 0) == ESP_OK);
239+
REQUIRE(uvc_frame_allocate(&stream, 1, 100 * 1024, 0, NULL) == ESP_OK);
240240
uvc_frame_format_update(&stream, &logo_jpg_format);
241241

242242
WHEN("Expected SoF but got EoF") {

host/class/uvc/usb_host_uvc/idf_component.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
## IDF Component Manager Manifest File
2-
version: "2.3.1"
2+
version: "2.4.0"
33
description: USB Host UVC driver
44
url: https://github.com/espressif/esp-usb/tree/master/host/class/uvc/usb_host_uvc
55
dependencies:

host/class/uvc/usb_host_uvc/include/usb/uvc_host.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ typedef struct {
182182
int number_of_urbs; /**< Number of URBs for this stream. Triple buffering scheme is recommended */
183183
size_t urb_size; /**< Size in bytes of 1 URB, 10kB should be enough for start.
184184
Larger value results in less frequent interrupts at the cost of memory consumption */
185+
uint8_t **user_frame_buffers; /**< Optional: User-provided frame buffers. NULL (default) = driver allocates buffers.
186+
Non-NULL = provide 'number_of_frame_buffers' buffers, each 'frame_size' bytes. */
185187
} advanced;
186188
} uvc_host_stream_config_t;
187189

host/class/uvc/usb_host_uvc/private_include/uvc_frame_priv.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ extern "C" {
2020
* @param[in] nb_of_fb Number of frame buffers to allocate
2121
* @param[in] fb_size Size of 1 frame buffer in bytes
2222
* @param[in] fb_caps Memory capabilities of memory for frame buffers
23+
* @param[in] user_frame_buffers Optional user-provided frame buffers. If not NULL, use these instead of allocating
2324
* @return
2425
* - ESP_OK: Success
2526
* - ESP_ERR_NO_MEM: Not enough memory for frame buffers
2627
* - ESP_ERR_INVALID_ARG: Invalid count or size of frame buffers
2728
*/
28-
esp_err_t uvc_frame_allocate(uvc_stream_t *uvc_stream, int nb_of_fb, size_t fb_size, uint32_t fb_caps);
29+
esp_err_t uvc_frame_allocate(uvc_stream_t *uvc_stream, int nb_of_fb, size_t fb_size, uint32_t fb_caps, uint8_t **user_frame_buffers);
2930

3031
/**
3132
* @brief Free allocated frame buffers

host/class/uvc/usb_host_uvc/private_include/uvc_types_priv.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ struct uvc_host_stream_s {
3636
uvc_host_frame_callback_t frame_cb; // User's frame callback
3737
void *cb_arg; // Common argument for user's callbacks
3838
QueueHandle_t empty_fb_queue; // Queue of empty framebuffers
39+
bool user_provided_fb; // Flag indicating if frame buffers are user-provided
3940

4041
// Constant USB descriptor values
4142
uint16_t bcdUVC; // Version of UVC specs this device implements

host/class/uvc/usb_host_uvc/test_app/main/test_uvc_host.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,120 @@ TEST_CASE("Open and change format", "[uvc]")
310310
vTaskDelay(20); // Short delay to allow task to be cleaned up
311311
}
312312

313+
/**
314+
* @brief UVC user-provided frame buffers streaming test
315+
*
316+
* This test verifies zero-copy streaming with user-provided buffers.
317+
*
318+
* -# Allocate user frame buffers
319+
* -# Open UVC stream with user-provided buffers
320+
* -# Start streaming
321+
* -# Receive frames and verify they use user buffers (pointer check)
322+
* -# Stop streaming
323+
* -# Close stream and free buffers
324+
*/
325+
static volatile int frames_received = 0;
326+
static uint8_t **test_user_buffers_ptr = NULL;
327+
static int test_num_buffers = 0;
328+
329+
static bool test_frame_callback(const uvc_host_frame_t *frame, void *user_ctx)
330+
{
331+
frames_received++;
332+
333+
// Verify that frame data pointer is one of our user buffers
334+
bool is_user_buffer = false;
335+
for (int i = 0; i < test_num_buffers; i++) {
336+
if (frame->data == test_user_buffers_ptr[i]) {
337+
is_user_buffer = true;
338+
printf("Frame %d received in user buffer[%d] (%p), size: %zu bytes\n",
339+
frames_received, i, frame->data, frame->data_len);
340+
break;
341+
}
342+
}
343+
344+
TEST_ASSERT_TRUE_MESSAGE(is_user_buffer, "Frame data is not in user-provided buffer!");
345+
return true; // Return frame immediately
346+
}
347+
348+
TEST_CASE("Streaming with user-provided frame buffers", "[uvc]")
349+
{
350+
test_install_uvc_driver();
351+
352+
// Allocate user frame buffers
353+
const int num_buffers = 3;
354+
const size_t buffer_size = 60 * 1024;
355+
uint8_t *user_buffers[num_buffers];
356+
357+
printf("Allocating %d * %d KB buffers for streaming test\n", num_buffers, buffer_size / 1024);
358+
for (int i = 0; i < num_buffers; i++) {
359+
#if CONFIG_SPIRAM
360+
user_buffers[i] = heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM);
361+
#else
362+
user_buffers[i] = heap_caps_malloc(buffer_size, MALLOC_CAP_DEFAULT);
363+
#endif
364+
TEST_ASSERT_NOT_NULL(user_buffers[i]);
365+
printf(" Buffer[%d] at %p\n", i, user_buffers[i]);
366+
}
367+
368+
// Set global pointers for callback verification
369+
test_user_buffers_ptr = user_buffers;
370+
test_num_buffers = num_buffers;
371+
frames_received = 0;
372+
373+
printf("Opening UVC stream\n");
374+
uvc_host_stream_hdl_t stream = NULL;
375+
uvc_host_stream_config_t stream_config = {
376+
.event_cb = NULL,
377+
.frame_cb = test_frame_callback,
378+
.user_ctx = NULL,
379+
.usb = {
380+
.dev_addr = UVC_HOST_ANY_DEV_ADDR,
381+
.vid = UVC_HOST_ANY_VID,
382+
.pid = UVC_HOST_ANY_PID,
383+
.uvc_stream_index = 0,
384+
},
385+
.vs_format = {
386+
.h_res = 1280,
387+
.v_res = 720,
388+
.fps = 15,
389+
.format = UVC_VS_FORMAT_MJPEG,
390+
},
391+
.advanced = {
392+
.number_of_frame_buffers = num_buffers,
393+
.frame_size = buffer_size,
394+
.frame_heap_caps = 0,
395+
.number_of_urbs = 4,
396+
.urb_size = 10 * 1024,
397+
.user_frame_buffers = user_buffers,
398+
},
399+
};
400+
401+
TEST_ASSERT_EQUAL(ESP_OK, uvc_host_stream_open(&stream_config, 1000, &stream));
402+
TEST_ASSERT_NOT_NULL(stream);
403+
404+
printf("Starting stream\n");
405+
TEST_ASSERT_EQUAL(ESP_OK, uvc_host_stream_start(stream));
406+
407+
// Receive frames for 3 seconds
408+
printf("Streaming for 3 seconds...\n");
409+
vTaskDelay(pdMS_TO_TICKS(3000));
410+
411+
printf("Stopping stream\n");
412+
TEST_ASSERT_EQUAL(ESP_OK, uvc_host_stream_stop(stream));
413+
414+
printf("Total frames received: %d\n", frames_received);
415+
TEST_ASSERT_GREATER_THAN(0, frames_received);
416+
417+
// Clean-up
418+
TEST_ASSERT_EQUAL(ESP_OK, uvc_host_stream_close(stream));
419+
420+
printf("Freeing user frame buffers\n");
421+
for (int i = 0; i < num_buffers; i++) {
422+
free(user_buffers[i]);
423+
}
424+
425+
TEST_ASSERT_EQUAL(ESP_OK, uvc_host_uninstall());
426+
vTaskDelay(20);
427+
}
428+
313429
#endif // SOC_USB_OTG_SUPPORTED

host/class/uvc/usb_host_uvc/uvc_bulk.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ void bulk_transfer_callback(usb_transfer_t *transfer)
8686
uvc_stream->single_thread.next_bulk_packet = UVC_STREAM_BULK_PACKET_SOF;
8787

8888
if (payload_header->bmHeaderInfo.end_of_frame) {
89-
assert(payload_header->bmHeaderInfo.frame_id == uvc_stream->single_thread.current_frame_id);
90-
if (payload_header->bmHeaderInfo.error) {
89+
if (payload_header->bmHeaderInfo.error || payload_header->bmHeaderInfo.frame_id != uvc_stream->single_thread.current_frame_id) {
9190
uvc_stream->single_thread.skip_current_frame = true;
9291
}
9392

host/class/uvc/usb_host_uvc/uvc_frame.c

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,46 @@ esp_err_t uvc_host_frame_return(uvc_host_stream_hdl_t stream_hdl, uvc_host_frame
3030
return ESP_OK;
3131
}
3232

33-
esp_err_t uvc_frame_allocate(uvc_stream_t *uvc_stream, int nb_of_fb, size_t fb_size, uint32_t fb_caps)
33+
esp_err_t uvc_frame_allocate(uvc_stream_t *uvc_stream, int nb_of_fb, size_t fb_size, uint32_t fb_caps, uint8_t **user_frame_buffers)
3434
{
3535
UVC_CHECK(uvc_stream, ESP_ERR_INVALID_ARG);
3636
esp_err_t ret;
3737

38+
// Determine if we're using user-provided buffers
39+
const bool using_user_buffers = (user_frame_buffers != NULL);
40+
uvc_stream->constant.user_provided_fb = using_user_buffers;
41+
3842
// We will be passing the frame buffers by reference
3943
uvc_stream->constant.empty_fb_queue = xQueueCreate(nb_of_fb, sizeof(uvc_host_frame_t *));
4044
UVC_CHECK(uvc_stream->constant.empty_fb_queue, ESP_ERR_NO_MEM);
45+
4146
for (int i = 0; i < nb_of_fb; i++) {
4247
// Allocate the frame buffer
4348
uvc_host_frame_t *this_fb = malloc(sizeof(uvc_host_frame_t));
44-
if (fb_caps == 0) {
45-
fb_caps = MALLOC_CAP_DEFAULT; // In case the user did not fill the config, set it to default
46-
}
47-
uint8_t *this_data = heap_caps_malloc(fb_size, fb_caps);
48-
if (this_data == NULL || this_fb == NULL) {
49-
free(this_fb);
50-
free(this_data);
49+
if (this_fb == NULL) {
5150
ret = ESP_ERR_NO_MEM;
52-
ESP_LOGE(TAG, "Not enough memory for frame buffers %zu", fb_size);
51+
ESP_LOGE(TAG, "Not enough memory for frame buffer structure");
5352
goto err;
5453
}
5554

55+
uint8_t *this_data = NULL;
56+
if (using_user_buffers) {
57+
// Use user-provided buffer (already validated in uvc_host_stream_open)
58+
this_data = user_frame_buffers[i];
59+
} else {
60+
// Allocate driver-managed buffer
61+
if (fb_caps == 0) {
62+
fb_caps = MALLOC_CAP_DEFAULT; // In case the user did not fill the config, set it to default
63+
}
64+
this_data = heap_caps_malloc(fb_size, fb_caps);
65+
if (this_data == NULL) {
66+
free(this_fb);
67+
ret = ESP_ERR_NO_MEM;
68+
ESP_LOGE(TAG, "Not enough memory for frame buffers %zu", fb_size);
69+
goto err;
70+
}
71+
}
72+
5673
// Set members to default
5774
this_fb->data = this_data;
5875
this_fb->data_buffer_len = fb_size;
@@ -78,7 +95,10 @@ void uvc_frame_free(uvc_stream_t *uvc_stream)
7895
// Free all Frame Buffers and the Queue itself
7996
uvc_host_frame_t *this_fb;
8097
while (xQueueReceive(uvc_stream->constant.empty_fb_queue, &this_fb, 0) == pdPASS) {
81-
free(this_fb->data);
98+
// Only free the data buffer if it was allocated by the driver (not user-provided)
99+
if (!uvc_stream->constant.user_provided_fb) {
100+
free(this_fb->data);
101+
}
82102
free(this_fb);
83103
}
84104
vQueueDelete(uvc_stream->constant.empty_fb_queue);

host/class/uvc/usb_host_uvc/uvc_host.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,19 @@ esp_err_t uvc_host_stream_open(const uvc_host_stream_config_t *stream_config, in
665665
UVC_CHECK(stream_config, ESP_ERR_INVALID_ARG);
666666
UVC_CHECK(stream_hdl_ret, ESP_ERR_INVALID_ARG);
667667

668+
// Validate user-provided frame buffers configuration
669+
if (stream_config->advanced.user_frame_buffers != NULL) {
670+
UVC_CHECK(stream_config->advanced.number_of_frame_buffers > 0, ESP_ERR_INVALID_ARG);
671+
UVC_CHECK(stream_config->advanced.frame_size > 0, ESP_ERR_INVALID_ARG);
672+
// Verify that all user-provided buffers are not NULL
673+
for (int i = 0; i < stream_config->advanced.number_of_frame_buffers; i++) {
674+
if (stream_config->advanced.user_frame_buffers[i] == NULL) {
675+
ESP_LOGE(TAG, "User-provided frame buffer[%d] is NULL", i);
676+
return ESP_ERR_INVALID_ARG;
677+
}
678+
}
679+
}
680+
668681
uvc_stream_t *uvc_stream;
669682
xSemaphoreTake(p_uvc_host_driver->open_close_mutex, portMAX_DELAY);
670683

@@ -717,7 +730,8 @@ esp_err_t uvc_host_stream_open(const uvc_host_stream_config_t *stream_config, in
717730
uvc_stream,
718731
stream_config->advanced.number_of_frame_buffers,
719732
stream_config->advanced.frame_size ? stream_config->advanced.frame_size : vs_result.dwMaxVideoFrameSize,
720-
stream_config->advanced.frame_heap_caps),
733+
stream_config->advanced.frame_heap_caps,
734+
stream_config->advanced.user_frame_buffers),
721735
err, TAG,);
722736

723737
// Save info

0 commit comments

Comments
 (0)