mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
62091b4fa0
Some cameras would refuse first stream format and would only accept on second retry.
298 lines
9.1 KiB
C
298 lines
9.1 KiB
C
/*
|
|
* 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;
|
|
int attempt = CONFIG_EXAMPLE_NEGOTIATION_ATTEMPTS;
|
|
#if (CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_AUTO)
|
|
for (int idx = 0; idx < EXAMPLE_UVC_PROTOCOL_AUTO_COUNT; idx++) {
|
|
do {
|
|
/*
|
|
The uvc_get_stream_ctrl_format_size() function will attempt to set the desired format size.
|
|
On first attempt, some cameras would reject the format, even if they support it.
|
|
So we ask 3x by default. The second attempt is usually successful.
|
|
*/
|
|
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);
|
|
} while (--attempt && !(UVC_SUCCESS == res));
|
|
if (UVC_SUCCESS == res) {
|
|
break;
|
|
}
|
|
}
|
|
#endif // CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_AUTO
|
|
|
|
#if (CONFIG_EXAMPLE_UVC_PROTOCOL_MODE_CUSTOM)
|
|
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;
|
|
}
|
|
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;
|
|
}
|