| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- /*
- * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
- *
- * SPDX-License-Identifier: Apache-2.0
- */
- #include <stdio.h>
- #include <unistd.h>
- #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;
- }
|