Files
OpenIris-ESPIDF/components/usb_device_uvc/usb_device_uvc.c

346 lines
10 KiB
C

/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_check.h"
#if CONFIG_TINYUSB_RHPORT_HS
#include "soc/hp_sys_clkrst_reg.h"
#include "soc/hp_system_reg.h"
#else
#include "esp_private/usb_phy.h"
#endif
#include "tusb.h"
#include "usb_device_uvc.h"
static const char *TAG = "usbd_uvc";
#if CONFIG_UVC_SUPPORT_TWO_CAM
#define UVC_CAM_NUM 2
#else
#define UVC_CAM_NUM 1
#endif
typedef struct
{
#if !CONFIG_TINYUSB_RHPORT_HS
usb_phy_handle_t phy_hdl;
#endif
bool uvc_init[UVC_CAM_NUM];
uvc_format_t format[UVC_CAM_NUM];
uvc_device_config_t user_config[UVC_CAM_NUM];
TaskHandle_t uvc_task_hdl[UVC_CAM_NUM];
uint32_t interval_ms[UVC_CAM_NUM];
} uvc_device_t;
static uvc_device_t s_uvc_device;
static void usb_phy_init(void)
{
#if !CONFIG_TINYUSB_RHPORT_HS
// Configure USB PHY
usb_phy_config_t phy_conf = {
.controller = USB_PHY_CTRL_OTG,
.otg_mode = USB_OTG_MODE_DEVICE,
.target = USB_PHY_TARGET_INT,
};
usb_new_phy(&phy_conf, &s_uvc_device.phy_hdl);
#endif
}
static inline uint32_t get_time_millis(void)
{
return (uint32_t)(esp_timer_get_time() / 1000);
}
static void tusb_device_task(void *arg)
{
while (1)
{
tud_task();
}
}
void tud_mount_cb(void)
{
ESP_LOGI(TAG, "Mount");
}
// Invoked when device is unmounted
void tud_umount_cb(void)
{
ESP_LOGI(TAG, "UN-Mount");
}
void tud_suspend_cb(bool remote_wakeup_en)
{
(void)remote_wakeup_en;
if (s_uvc_device.user_config[0].stop_cb)
{
s_uvc_device.user_config[0].stop_cb(s_uvc_device.user_config[0].cb_ctx);
}
#if CONFIG_UVC_SUPPORT_TWO_CAM
if (s_uvc_device.user_config[1].stop_cb)
{
s_uvc_device.user_config[1].stop_cb(s_uvc_device.user_config[1].cb_ctx);
}
#endif
ESP_LOGI(TAG, "Suspend");
}
// Invoked when usb bus is resumed
void tud_resume_cb(void)
{
ESP_LOGI(TAG, "Resume");
}
#if (CFG_TUD_VIDEO)
//--------------------------------------------------------------------+
// USB Video
//--------------------------------------------------------------------+
static void video_task(void *arg)
{
uint32_t start_ms = 0;
uint32_t frame_num = 0;
uint32_t frame_len = 0;
uint32_t already_start = 0;
uint32_t tx_busy = 0;
uint8_t *uvc_buffer = s_uvc_device.user_config[0].uvc_buffer;
uint32_t uvc_buffer_size = s_uvc_device.user_config[0].uvc_buffer_size;
uvc_fb_t *pic = NULL;
while (1)
{
if (!tud_video_n_streaming(0, 0))
{
already_start = 0;
frame_num = 0;
tx_busy = 0;
vTaskDelay(1);
continue;
}
if (!already_start)
{
already_start = 1;
start_ms = get_time_millis();
}
uint32_t cur = get_time_millis();
if (cur - start_ms < s_uvc_device.interval_ms[0])
{
vTaskDelay(1);
continue;
}
if (tx_busy)
{
uint32_t xfer_done = ulTaskNotifyTake(pdTRUE, 1);
if (xfer_done == 0)
{
continue;
}
++frame_num;
tx_busy = 0;
}
start_ms += s_uvc_device.interval_ms[0];
ESP_LOGD(TAG, "frame %" PRIu32 " taking picture...", frame_num);
pic = s_uvc_device.user_config[0].fb_get_cb(s_uvc_device.user_config[0].cb_ctx);
if (pic)
{
ESP_LOGD(TAG, "Picture taken! Its size was: %zu bytes", pic->len);
}
else
{
ESP_LOGE(TAG, "Failed to capture picture");
continue;
}
if (pic->len > uvc_buffer_size)
{
ESP_LOGW(TAG, "frame size is too big, dropping frame");
s_uvc_device.user_config[0].fb_return_cb(pic, s_uvc_device.user_config[0].cb_ctx);
continue;
}
frame_len = pic->len;
memcpy(uvc_buffer, pic->buf, frame_len);
s_uvc_device.user_config[0].fb_return_cb(pic, s_uvc_device.user_config[0].cb_ctx);
tx_busy = 1;
tud_video_n_frame_xfer(0, 0, (void *)uvc_buffer, frame_len);
ESP_LOGD(TAG, "frame %" PRIu32 " transfer start, size %" PRIu32, frame_num, frame_len);
}
}
#if CONFIG_UVC_SUPPORT_TWO_CAM
static void video_task2(void *arg)
{
uint32_t start_ms = 0;
uint32_t frame_num = 0;
uint32_t frame_len = 0;
uint32_t already_start = 0;
uint32_t tx_busy = 0;
uint8_t *uvc_buffer = s_uvc_device.user_config[1].uvc_buffer;
uint32_t uvc_buffer_size = s_uvc_device.user_config[1].uvc_buffer_size;
uvc_fb_t *pic = NULL;
while (1)
{
if (!tud_video_n_streaming(1, 0))
{
already_start = 0;
frame_num = 0;
tx_busy = 0;
vTaskDelay(1);
continue;
}
if (!already_start)
{
already_start = 1;
start_ms = get_time_millis();
}
uint32_t cur = get_time_millis();
if (cur - start_ms < s_uvc_device.interval_ms[1])
{
vTaskDelay(1);
continue;
}
if (tx_busy)
{
uint32_t xfer_done = ulTaskNotifyTake(pdTRUE, 1);
if (xfer_done == 0)
{
continue;
}
++frame_num;
tx_busy = 0;
}
start_ms += s_uvc_device.interval_ms[1];
ESP_LOGD(TAG, "frame %" PRIu32 " taking picture...", frame_num);
pic = s_uvc_device.user_config[1].fb_get_cb(s_uvc_device.user_config[1].cb_ctx);
if (pic)
{
ESP_LOGD(TAG, "Picture taken! Its size was: %zu bytes", pic->len);
}
else
{
ESP_LOGE(TAG, "Failed to capture picture");
continue;
}
if (pic->len > uvc_buffer_size)
{
ESP_LOGW(TAG, "frame size is too big, dropping frame");
s_uvc_device.user_config[1].fb_return_cb(pic, s_uvc_device.user_config[1].cb_ctx);
continue;
}
frame_len = pic->len;
memcpy(uvc_buffer, pic->buf, frame_len);
s_uvc_device.user_config[1].fb_return_cb(pic, s_uvc_device.user_config[1].cb_ctx);
tx_busy = 1;
tud_video_n_frame_xfer(1, 0, (void *)uvc_buffer, frame_len);
ESP_LOGD(TAG, "frame %" PRIu32 " transfer start, size %" PRIu32, frame_num, frame_len);
}
}
#endif
void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx)
{
(void)ctl_idx;
(void)stm_idx;
xTaskNotifyGive(s_uvc_device.uvc_task_hdl[ctl_idx]);
}
int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx,
video_probe_and_commit_control_t const *parameters)
{
(void)ctl_idx;
(void)stm_idx;
/* convert unit to ms from 100 ns */
ESP_LOGI(TAG, "bFrameIndex: %u", parameters->bFrameIndex);
ESP_LOGI(TAG, "dwFrameInterval: %" PRIu32 "", parameters->dwFrameInterval);
if (parameters->bFrameIndex > UVC_FRAME_NUM)
{
return VIDEO_ERROR_OUT_OF_RANGE;
}
s_uvc_device.interval_ms[ctl_idx] = parameters->dwFrameInterval / 10000;
int frame_index = parameters->bFrameIndex - 1;
esp_err_t ret = s_uvc_device.user_config[ctl_idx].start_cb(s_uvc_device.format[ctl_idx], UVC_FRAMES_INFO[ctl_idx][frame_index].width,
UVC_FRAMES_INFO[ctl_idx][frame_index].height, UVC_FRAMES_INFO[ctl_idx][frame_index].rate, s_uvc_device.user_config[ctl_idx].cb_ctx);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "camera init failed");
return VIDEO_ERROR_OUT_OF_RANGE;
}
return VIDEO_ERROR_NONE;
}
#endif
esp_err_t uvc_device_config(int index, uvc_device_config_t *config)
{
ESP_RETURN_ON_FALSE(index < UVC_CAM_NUM, ESP_ERR_INVALID_ARG, TAG, "index is invalid");
ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "config is NULL");
ESP_RETURN_ON_FALSE(config->start_cb != NULL, ESP_ERR_INVALID_ARG, TAG, "start_cb is NULL");
ESP_RETURN_ON_FALSE(config->fb_get_cb != NULL, ESP_ERR_INVALID_ARG, TAG, "fb_get_cb is NULL");
ESP_RETURN_ON_FALSE(config->fb_return_cb != NULL, ESP_ERR_INVALID_ARG, TAG, "fb_return_cb is NULL");
ESP_RETURN_ON_FALSE(config->stop_cb != NULL, ESP_ERR_INVALID_ARG, TAG, "stop_cb is NULL");
ESP_RETURN_ON_FALSE(config->uvc_buffer != NULL, ESP_ERR_INVALID_ARG, TAG, "uvc_buffer is NULL");
ESP_RETURN_ON_FALSE(config->uvc_buffer_size > 0, ESP_ERR_INVALID_ARG, TAG, "uvc_buffer_size is 0");
s_uvc_device.user_config[index] = *config;
s_uvc_device.interval_ms[index] = 1000 / (index == 0 ? UVC_CAM1_FRAME_RATE : UVC_CAM2_FRAME_RATE);
s_uvc_device.uvc_init[index] = true;
return ESP_OK;
}
esp_err_t uvc_device_init(void)
{
ESP_RETURN_ON_FALSE(s_uvc_device.uvc_init[0], ESP_ERR_INVALID_STATE, TAG, "uvc device 0 not init");
#if CONFIG_UVC_SUPPORT_TWO_CAM
ESP_RETURN_ON_FALSE(s_uvc_device.uvc_init[1], ESP_ERR_INVALID_STATE, TAG, "uvc device 1 not init, if not use, please disable CONFIG_UVC_SUPPORT_TWO_CAM");
#endif
#ifdef CONFIG_FORMAT_MJPEG_CAM1
s_uvc_device.format[0] = UVC_FORMAT_JPEG;
#endif
#if CONFIG_UVC_SUPPORT_TWO_CAM
#ifdef CONFIG_FORMAT_MJPEG_CAM2
s_uvc_device.format[1] = UVC_FORMAT_JPEG;
#endif
#endif
// init device stack on configured roothub port
usb_phy_init();
bool usb_init = tusb_init();
if (!usb_init)
{
ESP_LOGE(TAG, "USB Device Stack Init Fail");
return ESP_FAIL;
}
BaseType_t core_id = (CONFIG_UVC_TINYUSB_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_TINYUSB_TASK_CORE;
xTaskCreatePinnedToCore(tusb_device_task, "TinyUSB", 4096, NULL, CONFIG_UVC_TINYUSB_TASK_PRIORITY, NULL, core_id);
#if (CFG_TUD_VIDEO)
core_id = (CONFIG_UVC_CAM1_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_CAM1_TASK_CORE;
xTaskCreatePinnedToCore(video_task, "UVC", 4096, NULL, CONFIG_UVC_CAM1_TASK_PRIORITY, &s_uvc_device.uvc_task_hdl[0], core_id);
#if CONFIG_UVC_SUPPORT_TWO_CAM
core_id = (CONFIG_UVC_CAM2_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_CAM2_TASK_CORE;
xTaskCreatePinnedToCore(video_task2, "UVC2", 4096, NULL, CONFIG_UVC_CAM2_TASK_PRIORITY, &s_uvc_device.uvc_task_hdl[1], core_id);
#endif
#endif
ESP_LOGI(TAG, "UVC Device Start, Version: %d.%d.%d", USB_DEVICE_UVC_VER_MAJOR, USB_DEVICE_UVC_VER_MINOR, USB_DEVICE_UVC_VER_PATCH);
return ESP_OK;
}