安装UVC Class驱动,识别摄像头并获取视频流

app/tasks/task_capture.c
			↓(uvc_port_start)
			↓
ports\uvc\uvc_port.c

uvc_port.c

esp_err_t uvc_port_start(uvc_frame_callback_t frame_cb,
                         uvc_state_callback_t state_cb,
                         void *ctx)
{
    s_callbacks = (callbacks_t){.frame_cb = frame_cb, .state_cb = state_cb, .ctx = ctx};
    s_frames = xQueueCreate(3, sizeof(uvc_host_frame_t *)); // uvc帧缓冲
    if (!s_frames) {
        return ESP_ERR_NO_MEM;
    }
    const uvc_host_driver_config_t config = {
        .driver_task_stack_size = 6144,//UVC驱动后台任务栈大小
        .driver_task_priority = 16, //UVC驱动后台任务的FreeRTOS优先级
        .xCoreID = tskNO_AFFINITY, //表示不固定运行在哪一个CPU核心上,由FreeRTOS调度器安排
        .create_background_task = true, //让官方UVC组件自己创建后台任务。
        .event_cb = driver_connected, //当UVC驱动识别到符合标准的摄像头时调用driver_connected
    };
    esp_err_t err = uvc_host_install(&config); //主要是这里,安装驱动
    if (err != ESP_OK) {
        return err;
    }
    if (xTaskCreate(stream_task, "uvc_stream", 8192, NULL, 12, NULL) != pdPASS) { //持续获取流的任务
        return ESP_ERR_NO_MEM;
    }
    return ESP_OK;
}

uvc_port_start该任务需要处理:

  • UVC设备连接
  • 描述符解析
  • 传输完成事件
  • UVC数据包
  • 帧组装
  • 断开事件
stream_task
static void stream_task(void *arg)
{
    (void)arg;
    const uvc_host_stream_config_t config = {
        .event_cb = driver_event, 
        .frame_cb = driver_frame,
        .user_ctx = &s_frames,
        .usb = {
            .dev_addr = UVC_HOST_ANY_DEV_ADDR,
            .vid = UVC_HOST_ANY_VID,
            .pid = UVC_HOST_ANY_PID,
            .uvc_stream_index = 0,
        },
        .vs_format = {
            .h_res = MEDIA_VIDEO_WIDTH,
            .v_res = MEDIA_VIDEO_HEIGHT,
            .fps = MEDIA_VIDEO_FPS,
            .format = UVC_VS_FORMAT_YUY2,
        },
        .advanced = {
            .number_of_frame_buffers = 3,
            .frame_size = MEDIA_VIDEO_WIDTH * MEDIA_VIDEO_HEIGHT * 2,
            .frame_heap_caps = MALLOC_CAP_SPIRAM,
            .number_of_urbs = 4,
            .urb_size = 16 * 1024,
        },
    };

    for (;;) {
        uvc_host_stream_hdl_t stream = NULL;
        ESP_LOGI(TAG, "Waiting for YUY2 %dx%d@%d UVC camera",
                 MEDIA_VIDEO_WIDTH, MEDIA_VIDEO_HEIGHT, MEDIA_VIDEO_FPS);
        esp_err_t err = uvc_host_stream_open(&config, pdMS_TO_TICKS(5000), &stream);
        if (err != ESP_OK) {
            ESP_LOGW(TAG, "Open failed: %s", esp_err_to_name(err));
            vTaskDelay(pdMS_TO_TICKS(2000));
            continue;
        }
        s_connected = true;
        if (s_callbacks.state_cb) {
            s_callbacks.state_cb(true, s_callbacks.ctx);
        }
        err = uvc_host_stream_start(stream);
        if (err != ESP_OK) {
            (void)uvc_host_stream_close(stream);
            s_connected = false;
            if (s_callbacks.state_cb) {
                s_callbacks.state_cb(false, s_callbacks.ctx);
            }
            continue;
        }

        while (s_connected) {
            uvc_host_frame_t *frame = NULL;
            if (xQueueReceive(s_frames, &frame, pdMS_TO_TICKS(500)) != pdPASS) {
                continue;
            }
            if (s_callbacks.frame_cb && frame->vs_format.format == UVC_VS_FORMAT_YUY2) {
                s_callbacks.frame_cb(frame->data, frame->data_len, s_callbacks.ctx);
            }
            (void)uvc_host_frame_return(stream, frame);
        }

        (void)uvc_host_stream_stop(stream);
        uvc_host_frame_t *frame = NULL;
        while (xQueueReceive(s_frames, &frame, 0) == pdPASS) {
            (void)uvc_host_frame_return(stream, frame);
        }
        (void)uvc_host_stream_close(stream);
        if (s_callbacks.state_cb) {
            s_callbacks.state_cb(false, s_callbacks.ctx);
        }
    }
}