/* * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_log.h" #include "tcp_server.h" #include "libuvc/libuvc.h" #include "libuvc_helper.h" #include "libuvc_adapter.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "freertos/event_groups.h" #include "driver/gpio.h" #include "usb/usb_host.h" #include "esp_err.h" #include "esp_log.h" #include "esp_timer.h" static const char *TAG = "example"; #define USB_DISCONNECT_PIN GPIO_NUM_0 #if (CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_AUTO) #define EXAMPLE_UVC_PROTOCOL_AUTO_COUNT 3 typedef struct { enum uvc_frame_format format; int width; int height; int fps; const char* name; } uvc_stream_profile_t; uvc_stream_profile_t uvc_stream_profiles[EXAMPLE_UVC_PROTOCOL_AUTO_COUNT] = { {UVC_FRAME_FORMAT_MJPEG, 640, 480, 15, "640x480, fps 15"}, {UVC_FRAME_FORMAT_MJPEG, 320, 240, 30, "320x240, fps 30"}, {UVC_FRAME_FORMAT_MJPEG, 320, 240, 0, "320x240, any fps"} }; #endif // CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_AUTO #if (CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_CUSTOM) #define FPS CONFIG_EXAMPLE_FPS_PARAM #define WIDTH CONFIG_EXAMPLE_WIDTH_PARAM #define HEIGHT CONFIG_EXAMPLE_HEIGHT_PARAM #define FORMAT CONFIG_EXAMPLE_FORMAT_PARAM #endif // CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_CUSTOM // Attached camera can be filtered out based on (non-zero value of) PID, VID, SERIAL_NUMBER #define PID 0 #define VID 0 #define SERIAL_NUMBER NULL #define UVC_CHECK(exp) do { \ uvc_error_t _err_ = (exp); \ if(_err_ < 0) { \ ESP_LOGE(TAG, "UVC error: %s", \ uvc_error_string(_err_)); \ assert(0); \ } \ } while(0) static SemaphoreHandle_t ready_to_uninstall_usb; static EventGroupHandle_t app_flags; // Handles common USB host library events static void usb_lib_handler_task(void *args) { while (1) { uint32_t event_flags; usb_host_lib_handle_events(portMAX_DELAY, &event_flags); // Release devices once all clients has deregistered if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { usb_host_device_free_all(); } // Give ready_to_uninstall_usb semaphore to indicate that USB Host library // can be deinitialized, and terminate this task. if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { xSemaphoreGive(ready_to_uninstall_usb); } } vTaskDelete(NULL); } static esp_err_t initialize_usb_host_lib(void) { TaskHandle_t task_handle = NULL; const usb_host_config_t host_config = { .intr_flags = ESP_INTR_FLAG_LEVEL1 }; esp_err_t err = usb_host_install(&host_config); if (err != ESP_OK) { return err; } ready_to_uninstall_usb = xSemaphoreCreateBinary(); if (ready_to_uninstall_usb == NULL) { usb_host_uninstall(); return ESP_ERR_NO_MEM; } if (xTaskCreate(usb_lib_handler_task, "usb_events", 4096, NULL, 2, &task_handle) != pdPASS) { vSemaphoreDelete(ready_to_uninstall_usb); usb_host_uninstall(); return ESP_ERR_NO_MEM; } return ESP_OK; } static void uninitialize_usb_host_lib(void) { xSemaphoreTake(ready_to_uninstall_usb, portMAX_DELAY); vSemaphoreDelete(ready_to_uninstall_usb); if (usb_host_uninstall() != ESP_OK) { ESP_LOGE(TAG, "Failed to uninstall usb_host"); } } /* This callback function runs once per frame. Use it to perform any * quick processing you need, or have it put the frame into your application's * input queue. If this function takes too long, you'll start losing frames. */ void frame_callback(uvc_frame_t *frame, void *ptr) { static size_t fps; static size_t bytes_per_second; static int64_t start_time; int64_t current_time = esp_timer_get_time(); bytes_per_second += frame->data_bytes; fps++; if (!start_time) { start_time = current_time; } if (current_time > start_time + 1000000) { ESP_LOGI(TAG, "fps: %u, bytes per second: %u", fps, bytes_per_second); start_time = current_time; bytes_per_second = 0; fps = 0; } // Stream received frame to client, if enabled tcp_server_send(frame->data, frame->data_bytes); } void button_callback(int button, int state, void *user_ptr) { printf("button %d state %d\n", button, state); } static void libuvc_adapter_cb(libuvc_adapter_event_t event) { xEventGroupSetBits(app_flags, event); } static EventBits_t wait_for_event(EventBits_t event) { return xEventGroupWaitBits(app_flags, event, pdTRUE, pdFALSE, portMAX_DELAY) & event; } static uvc_error_t uvc_negotiate_stream_profile(uvc_device_handle_t *devh, uvc_stream_ctrl_t *ctrl) { uvc_error_t res; #if (CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_AUTO) for (int idx = 0; idx < EXAMPLE_UVC_PROTOCOL_AUTO_COUNT; idx++) { ESP_LOGI(TAG, "Negotiate streaming profile %s ...", uvc_stream_profiles[idx].name); res = uvc_get_stream_ctrl_format_size(devh, ctrl, uvc_stream_profiles[idx].format, uvc_stream_profiles[idx].width, uvc_stream_profiles[idx].height, uvc_stream_profiles[idx].fps); if (UVC_SUCCESS == res) { break; // stream profile negotiated } sleep(1); ESP_LOGE(TAG, "Negotiation failed with error %d.", res); } #endif // CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_AUTO #if (CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_CUSTOM) int attempt = CONFIG_EXAMPLE_NEGOTIATION_ATTEMPTS; while (attempt--) { ESP_LOGI(TAG, "Negotiate streaming profile %dx%d, %d fps ...", WIDTH, HEIGHT, FPS); res = uvc_get_stream_ctrl_format_size(devh, ctrl, FORMAT, WIDTH, HEIGHT, FPS); if (UVC_SUCCESS == res) { break; } sleep(1); ESP_LOGE(TAG, "Negotiation failed. Try again (%d) ...", attempt); } #endif // CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_CUSTOM if (UVC_SUCCESS == res) { ESP_LOGI(TAG, "Negotiation complete."); } else { ESP_LOGE(TAG, "Try another UVC USB device of change negotiation parameters."); } return res; } int app_main(int argc, char **argv) { uvc_context_t *ctx; uvc_device_t *dev; uvc_device_handle_t *devh; uvc_stream_ctrl_t ctrl; app_flags = xEventGroupCreate(); assert(app_flags); const gpio_config_t input_pin = { .pin_bit_mask = BIT64(USB_DISCONNECT_PIN), .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, }; ESP_ERROR_CHECK(gpio_config(&input_pin)); ESP_ERROR_CHECK(initialize_usb_host_lib()); libuvc_adapter_config_t config = { .create_background_task = true, .task_priority = 5, .stack_size = 4096, .callback = libuvc_adapter_cb }; libuvc_adapter_set_config(&config); UVC_CHECK(uvc_init(&ctx, NULL)); // Streaming takes place only when enabled in menuconfig ESP_ERROR_CHECK(tcp_server_wait_for_connection()); do { ESP_LOGI(TAG, "Waiting for USB UVC device connection ..."); wait_for_event(UVC_DEVICE_CONNECTED); UVC_CHECK(uvc_find_device(ctx, &dev, PID, VID, SERIAL_NUMBER)); ESP_LOGI(TAG, "Device found"); // UVC Device open UVC_CHECK(uvc_open(dev, &devh)); // Uncomment to print configuration descriptor // libuvc_adapter_print_descriptors(devh); uvc_set_button_callback(devh, button_callback, NULL); // Print known device information uvc_print_diag(devh, stderr); // Negotiate stream profile if (UVC_SUCCESS == uvc_negotiate_stream_profile(devh, &ctrl)) { // dwMaxPayloadTransferSize has to be overwritten to MPS (maximum packet size) // supported by ESP32-S2(S3), as libuvc selects the highest possible MPS by default. ctrl.dwMaxPayloadTransferSize = 512; uvc_print_stream_ctrl(&ctrl, stderr); UVC_CHECK(uvc_start_streaming(devh, &ctrl, frame_callback, NULL, 0)); ESP_LOGI(TAG, "Streaming..."); wait_for_event(UVC_DEVICE_DISCONNECTED); uvc_stop_streaming(devh); ESP_LOGI(TAG, "Done streaming."); } else { wait_for_event(UVC_DEVICE_DISCONNECTED); } // UVC Device close uvc_close(devh); } while (gpio_get_level(USB_DISCONNECT_PIN) != 0); tcp_server_close_when_done(); uvc_exit(ctx); ESP_LOGI(TAG, "UVC exited"); uninitialize_usb_host_lib(); return 0; }