Bladeren bron

USB: Add usb uvc example

Closes https://github.com/espressif/esp-idf/issues/6493
Martin Valik 3 jaren geleden
bovenliggende
commit
3bcd9ceefe

+ 8 - 0
examples/peripherals/usb/host/uvc/CMakeLists.txt

@@ -0,0 +1,8 @@
+# The following lines of boilerplate have to be in your project's
+# CMakeLists in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(usb-uvc)

+ 199 - 0
examples/peripherals/usb/host/uvc/README.md

@@ -0,0 +1,199 @@
+| Supported Targets | ESP32-S2 | ESP32-S3 |
+| ----------------- | -------- | -------- |
+
+# USB Camera Example
+
+## Overview
+
+This example demonstrates how to:
+
+- Capture video from a USB camera using the `libuvc` library.
+- Stream the video over WiFi by hosting a TCP server.
+
+The example enumerates connected camera, negotiates selected resolution together with `FPS` and starts capturing video.
+`frame_callback` function is then invoked after receiving each frame. User can process received frame according to his needs.
+
+Optionally, captured video can be visualized on computer with help of `player.py` script located in this example. 
+After setting `Enable streaming` menuconfig option, example will create TCP server upon start, and wait until `player.py` connects to server.
+Once connection is established, example streams each received frame to computer for visualization. 
+
+**Notice** that `libuvc` selects highest possible `dwMaxPayloadTransferSize` by default, so example has to manually overwrite this value to 512 bytes (maximum transfer size supported by ESP32-S2/S3).
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+## How to Use Example
+
+### Hardware Required
+
+This example requires any ESP32-S2 or ESP32-S3 with external PSRAM and exposed USB connector attached to USB camera.
+*ESP module without external PSRAM will fail to initialize*
+
+### Configure the project
+
+Following configuration is needed for streaming video:
+
+Open the project configuration menu (`idf.py menuconfig`). 
+
+In the `Example Connection Configuration` menu:
+
+* Set the Wi-Fi configuration.
+    * Set `WiFi SSID`.
+    * Set `WiFi Password`.
+
+In the `Example Configuration` menu:
+
+* Set the Example configuration
+    * `Enable streaming`
+
+Optional: If you need, change the other options according to your requirements.
+
+Additionally, `player.py` python script makes use of `opencv-python` and `numpy` packages,
+not included in `idf-env` environment. Run following commands to install:
+* `pip install opencv-python`
+* `pip install numpy`
+
+### Build and Flash
+
+Build the project and flash it to the board, then run the monitor tool to view the serial output:
+
+Run `idf.py set-target esp32s2` to set target chip. 
+
+Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the Getting Started Guide for all the steps to configure and use the ESP-IDF to build projects.
+
+## Known limitations
+
+Having only Full Speed USB peripheral and hardware limited MPS (maximum packet size) to 512 bytes, ESP32-S2/S3 is capable of reading about 0.5 MB of data per second. When connected to Full Speed USB host, cameras normally provide resolution no larger than 640x480 pixels. 
+Following two supported formats are the most common (both encoded in MJPEG):
+ * 320x240  30 FPS
+ * 640x480  15 FPS
+
+## Tested cameras
+ * Logitech C980
+ * CANYON CNE-CWC2
+
+## Example Output
+
+```
+...
+Waiting for device
+Device found
+DEVICE CONFIGURATION (0c45:6340/        S) ---
+Status: idle
+VideoControl:
+        bcdUVC: 0x0100
+VideoStreaming(1):
+        bEndpointAddress: 129
+        Formats:
+        MJPEGFormat(1)
+                  bits per pixel: 0
+                  GUID: 4d4a5047000000000000000000000000 (MJPG)
+                  default frame: 1
+                  aspect ratio: 0x0
+                  interlace flags: 00
+                  copy protect: 00
+                        FrameDescriptor(1)
+                          capabilities: 00
+                          size: 640x480
+                          bit rate: 24576000-147456000
+                          max frame size: 614400
+                          default interval: 1/30
+                          interval[0]: 1/30
+                          interval[1]: 1/25
+                          interval[2]: 1/20
+                          interval[3]: 1/15
+                          interval[4]: 1/10
+                          interval[5]: 1/5
+                        FrameDescriptor(2)
+                          capabilities: 00
+                          size: 352x288
+                          bit rate: 8110080-48660480
+                          max frame size: 202752
+                          default interval: 1/30
+                          interval[0]: 1/30
+                          interval[1]: 1/25
+                          interval[2]: 1/20
+                          interval[3]: 1/15
+                          interval[4]: 1/10
+                          interval[5]: 1/5
+                        FrameDescriptor(3)
+                          capabilities: 00
+                          size: 320x240
+                          bit rate: 6144000-36864000
+                          max frame size: 153600
+                          default interval: 1/30
+                          interval[0]: 1/30
+                          interval[1]: 1/25
+                          interval[2]: 1/20
+                          interval[3]: 1/15
+                          interval[4]: 1/10
+                          interval[5]: 1/5
+                        FrameDescriptor(4)
+                          capabilities: 00
+                          size: 176x144
+                          bit rate: 2027520-12165120
+                          max frame size: 50688
+                          default interval: 1/30
+                          interval[0]: 1/30
+                          interval[1]: 1/25
+                          interval[2]: 1/20
+                          interval[3]: 1/15
+                          interval[4]: 1/10
+                          interval[5]: 1/5
+                        FrameDescriptor(5)
+                          capabilities: 00
+                          size: 160x120
+                          bit rate: 1536000-9216000
+                          max frame size: 38400
+                          default interval: 1/30
+                          interval[0]: 1/30
+                          interval[1]: 1/25
+                          interval[2]: 1/20
+                          interval[3]: 1/15
+                          interval[4]: 1/10
+                          interval[5]: 1/5
+                        StillFrameDescriptor
+                          bEndPointAddress: 00
+                          wWidth(1) = 640
+                          wHeight(1) = 480
+                          wWidth(2) = 352
+                          wHeight(2) = 288
+                          wWidth(3) = 320
+                          wHeight(3) = 240
+                          wWidth(4) = 176
+                          wHeight(4) = 144
+                          wWidth(5) = 160
+                          wHeight(5) = 120
+END DEVICE CONFIGURATION
+bmHint: 0001
+bFormatIndex: 1
+bFrameIndex: 3
+dwFrameInterval: 333333
+wKeyFrameRate: 0
+wPFrameRate: 0
+wCompQuality: 0
+wCompWindowSize: 0
+wDelay: 20905
+dwMaxVideoFrameSize: 153600
+dwMaxPayloadTransferSize: 512
+bInterfaceNumber: 1
+Streaming...
+I (4801) example: fps: 35, bytes per second: 170480
+I (5821) example: fps: 34, bytes per second: 172448
+I (6841) example: fps: 34, bytes per second: 172448
+I (7871) example: fps: 34, bytes per second: 172448
+I (8891) example: fps: 34, bytes per second: 172448
+I (9921) example: fps: 35, bytes per second: 177520
+I (10941) example: fps: 34, bytes per second: 172448
+I (11961) example: fps: 34, bytes per second: 172448
+I (12991) example: fps: 34, bytes per second: 172448
+I (14011) example: fps: 34, bytes per second: 172448
+I (15041) example: fps: 34, bytes per second: 172448
+I (16061) example: fps: 34, bytes per second: 172448
+I (17081) example: fps: 34, bytes per second: 172448
+Done streaming.
+UVC exited
+```

+ 3 - 0
examples/peripherals/usb/host/uvc/main/CMakeLists.txt

@@ -0,0 +1,3 @@
+idf_component_register(SRCS "main.c" "tcp_server.c"
+                    INCLUDE_DIRS ""
+                    REQUIRES protocol_examples_common nvs_flash usb mdns esp_ringbuf esp_timer esp_wifi driver)

+ 7 - 0
examples/peripherals/usb/host/uvc/main/Kconfig.projbuild

@@ -0,0 +1,7 @@
+menu "Example Configuration"
+    config EXAMPLE_ENABLE_STREAMING
+        bool "Enable streaming"
+        default n
+        help
+            Enables streaming of captured video
+endmenu

+ 7 - 0
examples/peripherals/usb/host/uvc/main/idf_component.yml

@@ -0,0 +1,7 @@
+## IDF Component Manager Manifest File
+dependencies:
+  idf: ">=4.4"
+  usb_host_uvc: "1.0.0"
+  mdns:
+    rules:
+      - if: "idf_version >= 5.0"

+ 236 - 0
examples/peripherals/usb/host/uvc/main/main.c

@@ -0,0 +1,236 @@
+/*
+ * SPDX-FileCopyrightText: 2022 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
+
+#define FPS 30
+#define WIDTH 320
+#define HEIGHT 240
+#define FORMAT UVC_COLOR_FORMAT_MJPEG // UVC_COLOR_FORMAT_YUYV
+
+// 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;
+}
+
+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;
+    uvc_error_t res;
+
+    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 {
+
+        printf("Waiting for device\n");
+        wait_for_event(UVC_DEVICE_CONNECTED);
+
+        UVC_CHECK( uvc_find_device(ctx, &dev, PID, VID, SERIAL_NUMBER) );
+        puts("Device found");
+
+        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
+        res = uvc_get_stream_ctrl_format_size(devh, &ctrl, FORMAT, WIDTH, HEIGHT, FPS );
+        while (res != UVC_SUCCESS) {
+            printf("Negotiating streaming format failed, trying again...\n");
+            res = uvc_get_stream_ctrl_format_size(devh, &ctrl, FORMAT, WIDTH, HEIGHT, FPS );
+            sleep(1);
+        }
+
+        // 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) );
+        puts("Streaming...");
+
+        wait_for_event(UVC_DEVICE_DISCONNECTED);
+
+        uvc_stop_streaming(devh);
+        puts("Done streaming.");
+
+        uvc_close(devh);
+
+    } while (gpio_get_level(USB_DISCONNECT_PIN) != 0);
+
+    tcp_server_close_when_done();
+
+    uvc_exit(ctx);
+    puts("UVC exited");
+
+    uninitialize_usb_host_lib();
+
+    return 0;
+}

+ 227 - 0
examples/peripherals/usb/host/uvc/main/tcp_server.c

@@ -0,0 +1,227 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+#include <string.h>
+#include <sys/param.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/ringbuf.h"
+#include "freertos/event_groups.h"
+#include "esp_system.h"
+#include "esp_wifi.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "nvs_flash.h"
+#include "esp_netif.h"
+#include "protocol_examples_common.h"
+#include "addr_from_stdin.h"
+#include "lwip/err.h"
+#include "lwip/sockets.h"
+#include "tcp_server.h"
+#include "mdns.h"
+
+#define TAG "tcp_server"
+#define PORT 2222
+
+typedef struct {
+    int sock;
+    int listen_sock;
+    RingbufHandle_t buffer;
+    volatile bool close_request;
+    bool is_active;
+} tcp_server_t;
+
+#ifdef CONFIG_EXAMPLE_ENABLE_STREAMING
+
+static tcp_server_t *s_server;
+
+void socket_close(tcp_server_t *server)
+{
+    ESP_LOGI(TAG, "Closing socket");
+    shutdown(server->sock, 0);
+    close(server->sock);
+    close(server->listen_sock);
+}
+
+static void sender_task(void *arg)
+{
+    tcp_server_t *server = (tcp_server_t *)arg;
+    server->is_active = true;
+
+    while (1) {
+        size_t bytes_received = 0;
+        char *payload = (char *)xRingbufferReceiveUpTo(
+            server->buffer, &bytes_received, pdMS_TO_TICKS(2500), 20000);
+
+        if (payload != NULL && server->is_active) {
+            int sent = send(server->sock, payload, bytes_received, 0);
+            if (sent < 0) {
+                ESP_LOGE(TAG, "Error occurred during sending: errno %d, \
+                               Shutting down tcp server...", errno);
+                server->is_active = false;
+            }
+            vRingbufferReturnItem(server->buffer, (void *)payload);
+        }
+
+        if (server->close_request) {
+            socket_close(server);
+            vRingbufferDelete(server->buffer);
+            vTaskDelete(NULL);
+            s_server = NULL;
+            free(server);
+            return;
+        }
+    }
+}
+
+esp_err_t tcp_server_send(uint8_t *payload, size_t size)
+{
+    if (!s_server || !s_server->is_active) {
+        return ESP_OK;
+    }
+
+    if ( xRingbufferSend(s_server->buffer, payload, size, pdMS_TO_TICKS(1)) != pdTRUE ) {
+        ESP_LOGW(TAG, "Failed to send frame to ring buffer.");
+        return ESP_FAIL;
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t start_mdns_service(void)
+{
+    esp_err_t err = mdns_init();
+    if (err) {
+        printf("MDNS Init failed: %d\n", err);
+        return ESP_FAIL;
+    }
+
+    mdns_hostname_set("esp-cam");
+
+    return ESP_OK;
+}
+
+static esp_err_t create_server(tcp_server_t *server)
+{
+    char addr_str[128];
+    int ip_protocol = 0;
+    int addr_family = AF_INET;
+    struct sockaddr_storage dest_addr;
+
+    struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
+    dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
+    dest_addr_ip4->sin_family = addr_family;
+    dest_addr_ip4->sin_port = htons(PORT);
+    ip_protocol = IPPROTO_IP;
+
+    server->listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
+    if (server->listen_sock < 0) {
+        ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
+        return ESP_FAIL;
+    }
+    int opt = 1;
+    setsockopt(server->listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+    ESP_LOGI(TAG, "Socket created");
+
+    int err = bind(server->listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
+    if (err != 0) {
+        ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
+        ESP_LOGE(TAG, "IPPROTO: %d", addr_family);
+        close(server->listen_sock);
+        return ESP_FAIL;
+    }
+    ESP_LOGI(TAG, "Socket bound, port %d", PORT);
+
+    err = listen(server->listen_sock, 1);
+    if (err != 0) {
+        ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
+        close(server->listen_sock);
+        return ESP_FAIL;
+    }
+
+    ESP_LOGI(TAG, "Socket listening...");
+    ESP_LOGI(TAG, "Execute player.py script");
+
+    struct sockaddr_storage source_addr;
+    socklen_t addr_len = sizeof(source_addr);
+    server->sock = accept(server->listen_sock, (struct sockaddr *)&source_addr, &addr_len);
+    if (server->sock < 0) {
+        ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
+        close(server->listen_sock);
+        return ESP_FAIL;
+    }
+
+    // Convert ip address to string
+    if (source_addr.ss_family == PF_INET) {
+        inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
+    }
+    ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);
+
+    return ESP_OK;
+}
+
+esp_err_t tcp_server_wait_for_connection(void)
+{
+    TaskHandle_t task_handle = NULL;
+
+    ESP_ERROR_CHECK(nvs_flash_init());
+    ESP_ERROR_CHECK(esp_netif_init());
+    ESP_ERROR_CHECK(esp_event_loop_create_default());
+    ESP_ERROR_CHECK(start_mdns_service());
+    ESP_ERROR_CHECK(example_connect());
+
+    tcp_server_t *server = calloc(1, sizeof(tcp_server_t));
+    if (server == NULL) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    server->buffer = xRingbufferCreate(100000, RINGBUF_TYPE_BYTEBUF);
+    if ( server->buffer == NULL) {
+        free(server);
+        return ESP_ERR_NO_MEM;;
+    }
+
+    if ( create_server(server) != ESP_OK) {
+        vRingbufferDelete(server->buffer);
+        free(server);
+        return ESP_FAIL;
+    }
+
+
+    BaseType_t task_created = xTaskCreate(sender_task, "sender_task", 4096, server, 10, &task_handle);
+    if (!task_created) {
+        socket_close(server);
+        vRingbufferDelete(server->buffer);
+        free(server);
+        return ESP_ERR_NO_MEM;
+    }
+
+    s_server = server;
+    return ESP_OK;
+}
+
+void tcp_server_close_when_done(void)
+{
+    if (s_server) {
+        s_server->close_request = true;
+    }
+}
+
+#else
+
+esp_err_t tcp_server_wait_for_connection(void)
+{
+    return ESP_OK;
+}
+
+esp_err_t tcp_server_send(uint8_t *payload, size_t size)
+{
+    return ESP_OK;
+}
+
+void tcp_server_close_when_done(void) { }
+
+#endif

+ 22 - 0
examples/peripherals/usb/host/uvc/main/tcp_server.h

@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+#pragma once
+
+#include "esp_err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+esp_err_t tcp_server_wait_for_connection(void);
+
+esp_err_t tcp_server_send(uint8_t *payload, size_t size);
+
+void tcp_server_close_when_done(void);
+
+#ifdef __cplusplus
+}
+#endif

+ 37 - 0
examples/peripherals/usb/host/uvc/player.py

@@ -0,0 +1,37 @@
+# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Unlicense OR CC0-1.0
+import socket
+
+import cv2
+import numpy as np
+
+frame_count = 0
+stream = bytearray()
+
+print('Connecting to server...')
+
+with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+    sock.connect(('esp-cam.local', 2222))
+
+    print('Receiving data ')
+    while True:
+        data = sock.recv(4096)
+        if not data:
+            break
+        stream += data
+        print('.', end='', flush=True)
+
+        a = stream.find(b'\xff\xd8')
+        b = stream.find(b'\xff\xd9', a)
+
+        if a != -1 and b != -1:
+            jpg = stream[a:b + 2]
+            stream = stream[b + 2:]
+            buffer = np.frombuffer(jpg, dtype=np.uint8)
+            image = cv2.imdecode(buffer, cv2.IMREAD_COLOR)
+            cv2.imshow('Stream', image)
+            if cv2.waitKey(10) == 27:
+                exit(0)
+            frame_count += 1
+
+print('\nFrames received ', frame_count)

+ 26 - 0
examples/peripherals/usb/host/uvc/sdkconfig.defaults

@@ -0,0 +1,26 @@
+#
+# SPIRAM
+#
+CONFIG_SPIRAM=y
+CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=150000
+CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
+
+#
+# USB
+#
+CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=1024
+CONFIG_USB_HOST_HW_BUFFER_BIAS_IN=y
+
+#
+# WIFI
+#
+CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=8
+CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=8
+CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=8
+CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=16
+CONFIG_ESP32_WIFI_RX_BA_WIN=8
+
+#
+# LWIP
+#
+CONFIG_LWIP_TCP_SND_BUF_DEFAULT=15000