@@ -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
0 commit comments