Skip to content

Commit

Permalink
feat(uvc): Latest UVC updates
Browse files Browse the repository at this point in the history
  • Loading branch information
tore-espressif committed Dec 11, 2024
1 parent 6356f77 commit df83b49
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 158 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,98 +3,89 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>

#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

#include <sdkconfig.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"

#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "sd_pwr_ctrl_by_on_chip_ldo.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"

#include "usb/usb_host.h"
#include "usb/uvc_host.h"
#include "esp_private/uvc_stream.h"

#define EXAMPLE_USB_HOST_PRIORITY (15)
#define EXAMPLE_USB_DEVICE_VID (0x2207)
#define EXAMPLE_USB_DEVICE_PID (0x0018) // Customer's dual camera
#define EXAMPLE_USB_DEVICE_VID (UVC_HOST_ANY_VID)
#define EXAMPLE_USB_DEVICE_PID (UVC_HOST_ANY_PID)
#if CONFIG_SPIRAM
#define EXAMPLE_FRAME_COUNT (3)
#else
#define EXAMPLE_FRAME_COUNT (2)
#endif
#define EXAMPLE_STREAM_FPS (15)
#define EXAMPLE_RECORDING_LENGTH_S (5)
#define EXAMPLE_NUMBER_OF_STREAMS (2)

#define MOUNT_POINT "/sdcard"
#define EXAMPLE_RECORDING_LENGTH_S (5) // The stream(s) is enabled, run for EXAMPLE_RECORDING_LENGTH_S and then stopped
#define EXAMPLE_NUMBER_OF_STREAMS (2) // This example shows how to control multiple UVC streams. Set this to 1 if you camera offers only 1 stream
#define EXAMPLE_USE_SDCARD (0) // SD card on P4 evaluation board will be initialized

static const char *TAG = "UVC example";
static SemaphoreHandle_t device_disconnected_sem;
static QueueHandle_t rx_frames_queue[EXAMPLE_NUMBER_OF_STREAMS];
static bool dev_connected = false;

bool frame_callback(const uvc_host_frame_t *frame, void *user_ctx)
{
assert(frame);
assert(user_ctx);
QueueHandle_t frame_q = *((QueueHandle_t *)user_ctx);
// ESP_LOGI(TAG, "Frame callback! data len: %d", frame->data_len);
BaseType_t result = xQueueSend(frame_q, &frame, 0);
ESP_LOGD(TAG, "Frame callback! data len: %d", frame->data_len);
BaseType_t result = xQueueSendToBack(frame_q, &frame, 0);
if (pdPASS != result) {
ESP_LOGW(TAG, "Queue full, losing frame"); // This should never happen. We allocated queue with the same size as EXAMPLE_FRAME_COUNT
return true; // We will not process this frame, return it immediately
}
return false; // We only passed the frame to Queue, so we must return false and call uvc_host_frame_return() later
}

/**
* @brief Device event callback
*
* Apart from handling device disconnection it doesn't do anything useful
*
* @param[in] event Device event type and data
* @param[in] user_ctx Argument we passed to the device open function
*/
static void handle_event(const uvc_host_stream_event_data_t *event, void *user_ctx)
static void stream_callback(const uvc_host_stream_event_data_t *event, void *user_ctx)
{
switch (event->type) {
case UVC_HOST_TRANSFER_ERROR:
ESP_LOGE(TAG, "USB error has occurred, err_no = %i", event->data.error);
ESP_LOGE(TAG, "USB error has occurred, err_no = %i", event->transfer_error.error);
break;
case UVC_HOST_DEVICE_DISCONNECTED:
ESP_LOGI(TAG, "Device suddenly disconnected");
ESP_ERROR_CHECK(uvc_host_stream_close(event->data.stream_hdl));
xSemaphoreGive(device_disconnected_sem);
dev_connected = false;
ESP_ERROR_CHECK(uvc_host_stream_close(event->device_disconnected.stream_hdl));
break;
case UVC_HOST_FRAME_BUFFER_OVERFLOW:
ESP_LOGW(TAG, "Frame buffer overflow");
break;
case UVC_HOST_FRAME_BUFFER_UNDERFLOW:
ESP_LOGW(TAG, "Frame buffer underflow");
break;
default:
ESP_LOGW(TAG, "Unsupported event: %i", event->type);
abort();
break;
}
}

/**
* @brief USB Host library handling task
*
* @param arg Unused
*/
static void usb_lib_task(void *arg)
{
while (1) {
// Start handling system events
uint32_t event_flags;
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
ESP_ERROR_CHECK(usb_host_device_free_all());
usb_host_device_free_all();
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
ESP_LOGI(TAG, "USB: All devices freed");
Expand All @@ -121,20 +112,20 @@ static void frame_handling_task(void *arg)
continue;
}
//uvc_host_desc_print(uvc_stream);
dev_connected = true;
ESP_LOGI(TAG, "Device 0x%04X:0x%04X-%d OPENED!", stream_config->usb.vid, stream_config->usb.pid, uvc_index);
vTaskDelay(pdMS_TO_TICKS(100));
unsigned count = 0;

// This is the main processing loop. It enables the stream for 2 seconds and then closes it
uvc_host_stream_start(uvc_stream);
// This is the main processing loop. It enables the stream for EXAMPLE_RECORDING_LENGTH_S seconds and then closes it
while (true) {
ESP_LOGI(TAG, "Stream %d start. Iteration %u", uvc_index, count);
count++;
uvc_host_stream_start(uvc_stream);
TickType_t timeout_ticks = pdMS_TO_TICKS(EXAMPLE_RECORDING_LENGTH_S * 1000);
TimeOut_t connection_timeout;
vTaskSetTimeOutState(&connection_timeout);
TimeOut_t stream_timeout;
vTaskSetTimeOutState(&stream_timeout);

uvc_host_stream_unpause(uvc_stream);
do {
uvc_host_frame_t *frame;
if (xQueueReceive(frame_q, &frame, pdMS_TO_TICKS(1000)) == pdPASS) {
Expand All @@ -145,57 +136,78 @@ static void frame_handling_task(void *arg)
uvc_host_frame_return(uvc_stream, frame);
} else {
ESP_LOGW(TAG, "Stream %d: Frame not received on time", uvc_index);
break;
}
} while (xTaskCheckForTimeOut(&connection_timeout, &timeout_ticks) == pdFALSE);
ESP_LOGI(TAG, "Stream %d stop", uvc_index);
uvc_host_stream_pause(uvc_stream);

vTaskDelay(pdMS_TO_TICKS(2000));
} while (xTaskCheckForTimeOut(&stream_timeout, &timeout_ticks) == pdFALSE);

if (dev_connected) {
// Stop and wait 2 seconds
ESP_LOGI(TAG, "Stream %d stop", uvc_index);
uvc_host_stream_stop(uvc_stream);
vTaskDelay(pdMS_TO_TICKS(2000));
}
if (!dev_connected) {
ESP_LOGI(TAG, "device disconnected, breaking");
break;
}
}
uvc_host_stream_stop(uvc_stream);
uvc_host_stream_close(uvc_stream);

// We are done. Wait for device disconnection and start over
ESP_LOGI(TAG, "Example finished successfully! You can reconnect the device to run again.");
xSemaphoreTake(device_disconnected_sem, portMAX_DELAY);
}
}

static const uvc_host_stream_config_t stream_mjpeg_config = {
.event_cb = handle_event,
.event_cb = stream_callback,
.frame_cb = frame_callback,
.user_ctx = &rx_frames_queue[0],
.usb.vid = EXAMPLE_USB_DEVICE_VID,
.usb.pid = EXAMPLE_USB_DEVICE_PID,
.usb.uvc_stream_index = 0,
.vs_format.h_res = 720,
.vs_format.v_res = 1280,
.vs_format.fps = 15,
.vs_format.format = UVC_VS_FORMAT_MJPEG,
.advanced.number_of_frame_buffers = EXAMPLE_FRAME_COUNT,
.advanced.frame_size = 0,
.advanced.number_of_urbs = 6,
.advanced.urb_size = 20 * 1024,
.usb = {
.vid = EXAMPLE_USB_DEVICE_VID,
.pid = EXAMPLE_USB_DEVICE_PID,
.uvc_stream_index = 0,
},
.vs_format = {
.h_res = 720,
.v_res = 1280,
.fps = 15,
.format = UVC_VS_FORMAT_MJPEG,
},
.advanced = {
.number_of_frame_buffers = EXAMPLE_FRAME_COUNT,
.frame_size = 0,
.number_of_urbs = 4,
.urb_size = 10 * 1024,
},
};

#if EXAMPLE_NUMBER_OF_STREAMS > 1
static const uvc_host_stream_config_t stream_h265_config = {
.event_cb = handle_event,
.event_cb = stream_callback,
.frame_cb = frame_callback,
.user_ctx = &rx_frames_queue[1],
.usb.vid = EXAMPLE_USB_DEVICE_VID,
.usb.pid = EXAMPLE_USB_DEVICE_PID, // Customer's device
.usb.uvc_stream_index = 1,
.vs_format.h_res = 1280,
.vs_format.v_res = 720,
.vs_format.fps = 15,
.vs_format.format = UVC_VS_FORMAT_H265,
.advanced.number_of_frame_buffers = EXAMPLE_FRAME_COUNT,
.advanced.frame_size = 0,
.advanced.number_of_urbs = 6,
.advanced.urb_size = 20 * 1024,
.usb = {
.vid = EXAMPLE_USB_DEVICE_VID,
.pid = EXAMPLE_USB_DEVICE_PID,
.uvc_stream_index = 1,
},
.vs_format = {
.h_res = 1280,
.v_res = 720,
.fps = 15,
.format = UVC_VS_FORMAT_H265,
},
.advanced = {
.number_of_frame_buffers = EXAMPLE_FRAME_COUNT,
.frame_size = 0,
.number_of_urbs = 4,
.urb_size = 10 * 1024,
},
};
#endif // EXAMPLE_NUMBER_OF_STREAMS > 1

/*
#if EXAMPLE_USE_SDCARD
#define MOUNT_POINT "/sdcard"
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "sd_pwr_ctrl_by_on_chip_ldo.h"
void app_init_sdcard(void)
{
esp_err_t ret;
Expand Down Expand Up @@ -265,23 +277,22 @@ void app_init_sdcard(void)
}
return;
}
//esp_vfs_fat_sdcard_format(mount_point, card);
}
*/
#endif // EXAMPLE_USE_SDCARD

/**
* @brief Main application
*/
void app_main(void)
{
device_disconnected_sem = xSemaphoreCreateBinary();
for (int i = 0; i < EXAMPLE_NUMBER_OF_STREAMS; i++) {
rx_frames_queue[i] = xQueueCreate(EXAMPLE_FRAME_COUNT, sizeof(uvc_host_frame_t *));
assert(rx_frames_queue[i]);
}
assert(device_disconnected_sem);

//app_init_sdcard(); // Uncomment this if you want to init the SD card
#if EXAMPLE_USE_SDCARD
app_init_sdcard();
#endif // EXAMPLE_USE_SDCARD

// Install USB Host driver. Should only be called once in entire application
ESP_LOGI(TAG, "Installing USB Host");
Expand All @@ -306,7 +317,10 @@ void app_main(void)

task_created = xTaskCreatePinnedToCore(frame_handling_task, "mjpeg_handling", 4096, (void *)&stream_mjpeg_config, EXAMPLE_USB_HOST_PRIORITY - 2, NULL, tskNO_AFFINITY);
assert(task_created == pdTRUE);

#if EXAMPLE_NUMBER_OF_STREAMS > 1
vTaskDelay(pdMS_TO_TICKS(1000));
task_created = xTaskCreatePinnedToCore(frame_handling_task, "h265_handling", 4096, (void *)&stream_h265_config, EXAMPLE_USB_HOST_PRIORITY - 3, NULL, tskNO_AFFINITY);
assert(task_created == pdTRUE);
#endif // EXAMPLE_USE_SDCARD
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32p4"
CONFIG_BOOTLOADER_LOG_COLORS=y
CONFIG_ESP32P4_REV_MIN_0=y
CONFIG_RTC_CLK_SRC_EXT_CRYS=y
CONFIG_RTC_CLK_CAL_CYCLES=1024
CONFIG_SPIRAM=y
CONFIG_SPIRAM_SPEED_200M=y
CONFIG_LOG_COLORS=y
CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=2048
CONFIG_USB_HOST_HW_BUFFER_BIAS_IN=y
CONFIG_USB_HOST_HUBS_SUPPORTED=y
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32p4"
CONFIG_BOOTLOADER_LOG_COLORS=y
CONFIG_ESP32P4_REV_MIN_0=y
CONFIG_RTC_CLK_SRC_EXT_CRYS=y
CONFIG_RTC_CLK_CAL_CYCLES=1024
CONFIG_SPIRAM=y
CONFIG_SPIRAM_SPEED_200M=y
CONFIG_LOG_COLORS=y
CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=4096
CONFIG_USB_HOST_HW_BUFFER_BIAS_IN=y
CONFIG_USB_HOST_HUBS_SUPPORTED=y
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ void stream_callback(const uvc_host_stream_event_data_t *event, void *user_ctx)
break;
case UVC_HOST_DEVICE_DISCONNECTED:
ESP_LOGW(TAG, "Device disconnected");
ESP_ERROR_CHECK(uvc_host_stream_close(event->data.stream_hdl));
ESP_ERROR_CHECK(uvc_host_stream_close(event->device_disconnected.stream_hdl));
xSemaphoreGive(device_disconnected_sem);
break;
case UVC_HOST_FRAME_BUFFER_OVERFLOW:
Expand Down Expand Up @@ -155,12 +155,11 @@ static void usb_lib_task(void *arg)
uint32_t event_flags;
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
ESP_ERROR_CHECK(usb_host_device_free_all());
usb_host_device_free_all();
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
ESP_LOGI(TAG, "USB: All devices freed");
// Continue handling USB events to allow device reconnection
// The only way this task can be stopped is by calling bsp_usb_host_stop()
}
}
}
Expand Down
Loading

0 comments on commit df83b49

Please sign in to comment.