mirror of
https://github.com/espressif/esp-idf.git
synced 2024-09-20 00:36:01 -04:00
Merge branch 'feature/usb_host_uvc_component' into 'master'
USB host UVC example See merge request espressif/esp-idf!19397
This commit is contained in:
commit
f1611841a3
8
examples/peripherals/usb/host/uvc/CMakeLists.txt
Normal file
8
examples/peripherals/usb/host/uvc/CMakeLists.txt
Normal file
@ -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
examples/peripherals/usb/host/uvc/README.md
Normal file
199
examples/peripherals/usb/host/uvc/README.md
Normal file
@ -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
examples/peripherals/usb/host/uvc/main/CMakeLists.txt
Normal file
3
examples/peripherals/usb/host/uvc/main/CMakeLists.txt
Normal file
@ -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
examples/peripherals/usb/host/uvc/main/Kconfig.projbuild
Normal file
7
examples/peripherals/usb/host/uvc/main/Kconfig.projbuild
Normal file
@ -0,0 +1,7 @@
|
||||
menu "Example Configuration"
|
||||
config EXAMPLE_ENABLE_STREAMING
|
||||
bool "Enable streaming"
|
||||
default n
|
||||
help
|
||||
Enables streaming of captured video
|
||||
endmenu
|
7
examples/peripherals/usb/host/uvc/main/idf_component.yml
Normal file
7
examples/peripherals/usb/host/uvc/main/idf_component.yml
Normal file
@ -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
examples/peripherals/usb/host/uvc/main/main.c
Normal file
236
examples/peripherals/usb/host/uvc/main/main.c
Normal file
@ -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
examples/peripherals/usb/host/uvc/main/tcp_server.c
Normal file
227
examples/peripherals/usb/host/uvc/main/tcp_server.c
Normal file
@ -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
examples/peripherals/usb/host/uvc/main/tcp_server.h
Normal file
22
examples/peripherals/usb/host/uvc/main/tcp_server.h
Normal file
@ -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
examples/peripherals/usb/host/uvc/player.py
Normal file
37
examples/peripherals/usb/host/uvc/player.py
Normal file
@ -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
examples/peripherals/usb/host/uvc/sdkconfig.defaults
Normal file
26
examples/peripherals/usb/host/uvc/sdkconfig.defaults
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user