450 lines
16 KiB
C
450 lines
16 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/event_groups.h"
|
|
#include "freertos/task.h"
|
|
#include "bsp/esp-bsp.h"
|
|
#include "bsp/display.h"
|
|
#include "esp_log.h"
|
|
#include "esp_jpeg_dec.h"
|
|
#include "esp_timer.h"
|
|
#include "nvs.h"
|
|
#include "nvs_flash.h"
|
|
#include "usb_stream.h"
|
|
#include "iot_button.h"
|
|
|
|
static const char *TAG = "uvc_camera_lcd_demo";
|
|
/****************** configure the example working mode *******************************/
|
|
|
|
#define DEMO_UVC_XFER_BUFFER_SIZE (35 * 1024) // Double buffer
|
|
#define DEMO_KEY_RESOLUTION "resolution" // The key of the resolution stored in nvs
|
|
#define DEMO_SWITCH_BUTTON_IO 0 // The button to switch resolution
|
|
#define DEMO_MAX_H 680 // The max width of the camera
|
|
#define DEMO_MAX_V 480 // The max height of the camera
|
|
|
|
#define BIT0_FRAME_START (0x01 << 0)
|
|
static EventGroupHandle_t s_evt_handle;
|
|
|
|
typedef struct
|
|
{
|
|
uint16_t width;
|
|
uint16_t height;
|
|
} camera_frame_size_t;
|
|
|
|
typedef struct
|
|
{
|
|
camera_frame_size_t camera_frame_size;
|
|
uvc_frame_size_t *camera_frame_list;
|
|
size_t camera_frame_list_num;
|
|
size_t camera_currect_frame_index;
|
|
} camera_resolution_info_t;
|
|
|
|
static camera_resolution_info_t camera_resolution_info = {0};
|
|
static uint8_t *jpg_frame_buf = NULL;
|
|
static uint8_t *xfer_buffer_a = NULL;
|
|
static uint8_t *xfer_buffer_b = NULL;
|
|
static uint8_t *frame_buffer = NULL;
|
|
static uint16_t current_width = 0;
|
|
static uint16_t current_height = 0;
|
|
static lv_obj_t *camera_canvas = NULL;
|
|
static lv_obj_t *label = NULL;
|
|
|
|
static jpeg_error_t esp_jpeg_decoder_one_picture(uint8_t *input_buf, int len, uint8_t *output_buf)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
// Generate default configuration
|
|
jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG();
|
|
config.output_type = JPEG_RAW_TYPE_RGB565_BE;
|
|
// Empty handle to jpeg_decoder
|
|
jpeg_dec_handle_t jpeg_dec = NULL;
|
|
|
|
// Create jpeg_dec
|
|
jpeg_dec = jpeg_dec_open(&config);
|
|
|
|
// Create io_callback handle
|
|
jpeg_dec_io_t *jpeg_io = calloc(1, sizeof(jpeg_dec_io_t));
|
|
if (jpeg_io == NULL)
|
|
{
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
// Create out_info handle
|
|
jpeg_dec_header_info_t *out_info = calloc(1, sizeof(jpeg_dec_header_info_t));
|
|
if (out_info == NULL)
|
|
{
|
|
return ESP_FAIL;
|
|
}
|
|
// Set input buffer and buffer len to io_callback
|
|
jpeg_io->inbuf = input_buf;
|
|
jpeg_io->inbuf_len = len;
|
|
|
|
// Parse jpeg picture header and get picture for user and decoder
|
|
ret = jpeg_dec_parse_header(jpeg_dec, jpeg_io, out_info);
|
|
if (ret < 0)
|
|
{
|
|
goto _exit;
|
|
}
|
|
|
|
jpeg_io->outbuf = output_buf;
|
|
int inbuf_consumed = jpeg_io->inbuf_len - jpeg_io->inbuf_remain;
|
|
jpeg_io->inbuf = input_buf + inbuf_consumed;
|
|
jpeg_io->inbuf_len = jpeg_io->inbuf_remain;
|
|
|
|
// Start decode jpeg raw data
|
|
ret = jpeg_dec_process(jpeg_dec, jpeg_io);
|
|
if (ret < 0)
|
|
{
|
|
goto _exit;
|
|
}
|
|
|
|
_exit:
|
|
// Decoder deinitialize
|
|
jpeg_dec_close(jpeg_dec);
|
|
free(out_info);
|
|
free(jpeg_io);
|
|
return ret;
|
|
}
|
|
|
|
static void adaptive_jpg_frame_buffer(size_t length)
|
|
{
|
|
if (jpg_frame_buf != NULL)
|
|
{
|
|
free(jpg_frame_buf);
|
|
}
|
|
|
|
jpg_frame_buf = (uint8_t *)heap_caps_aligned_alloc(16, length, MALLOC_CAP_SPIRAM);
|
|
assert(jpg_frame_buf != NULL);
|
|
}
|
|
|
|
static void _camera_display(uint8_t *lcd_buffer)
|
|
{
|
|
bsp_display_lock(0);
|
|
lv_canvas_set_buffer(camera_canvas, lcd_buffer, current_width, current_height, LV_IMG_CF_TRUE_COLOR);
|
|
lv_label_set_text_fmt(label, "#FF0000 %d*%d#", current_width, current_height);
|
|
bsp_display_unlock();
|
|
}
|
|
|
|
static void camera_frame_cb(uvc_frame_t *frame, void *ptr)
|
|
{
|
|
ESP_LOGI(TAG, "uvc callback! frame_format = %d, seq = %" PRIu32 ", width = %" PRIu32 ", height = %" PRIu32 ", length = %u, ptr = %d",
|
|
frame->frame_format, frame->sequence, frame->width, frame->height, frame->data_bytes, (int)ptr);
|
|
if (current_width != frame->width || current_height != frame->height)
|
|
{
|
|
current_width = frame->width;
|
|
current_height = frame->height;
|
|
adaptive_jpg_frame_buffer(current_width * current_height * 2);
|
|
}
|
|
|
|
esp_jpeg_decoder_one_picture((uint8_t *)frame->data, frame->data_bytes, jpg_frame_buf);
|
|
_camera_display(jpg_frame_buf);
|
|
vTaskDelay(pdMS_TO_TICKS(1));
|
|
}
|
|
|
|
|
|
static esp_err_t _display_init(void)
|
|
{
|
|
bsp_display_start();
|
|
bsp_display_backlight_on(); // Set display brightness to 100%
|
|
bsp_display_lock(0);
|
|
camera_canvas = lv_canvas_create(lv_scr_act());
|
|
assert(camera_canvas);
|
|
lv_obj_center(camera_canvas);
|
|
label = lv_label_create(lv_scr_act());
|
|
lv_label_set_recolor(label, true);
|
|
lv_obj_set_pos(label, 0, 0);
|
|
lv_label_set_text(label, "Insert a camera, press boot for resolution.");
|
|
bsp_display_unlock();
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void _get_value_from_nvs(char *key, void *value, size_t *size)
|
|
{
|
|
nvs_handle_t my_handle;
|
|
esp_err_t err = nvs_open("memory", NVS_READWRITE, &my_handle);
|
|
if (err != ESP_OK)
|
|
{
|
|
ESP_LOGE(TAG, "Error (%s) opening NVS handle!\n", esp_err_to_name(err));
|
|
}
|
|
else
|
|
{
|
|
err = nvs_get_blob(my_handle, key, value, size);
|
|
switch (err)
|
|
{
|
|
case ESP_OK:
|
|
break;
|
|
case ESP_ERR_NVS_NOT_FOUND:
|
|
ESP_LOGI(TAG, "%s is not initialized yet!", key);
|
|
break;
|
|
default:
|
|
ESP_LOGE(TAG, "Error (%s) reading!\n", esp_err_to_name(err));
|
|
}
|
|
|
|
nvs_close(my_handle);
|
|
}
|
|
}
|
|
|
|
static esp_err_t _set_value_to_nvs(char *key, void *value, size_t size)
|
|
{
|
|
nvs_handle_t my_handle;
|
|
esp_err_t err = nvs_open("memory", NVS_READWRITE, &my_handle);
|
|
if (err != ESP_OK)
|
|
{
|
|
ESP_LOGE(TAG, "Error (%s) opening NVS handle!\n", esp_err_to_name(err));
|
|
return ESP_FAIL;
|
|
}
|
|
else
|
|
{
|
|
err = nvs_set_blob(my_handle, key, value, size);
|
|
if (err != ESP_OK)
|
|
{
|
|
ESP_LOGE(TAG, "NVS set failed %s", esp_err_to_name(err));
|
|
}
|
|
|
|
err = nvs_commit(my_handle);
|
|
if (err != ESP_OK)
|
|
{
|
|
ESP_LOGE(TAG, "NVS commit failed");
|
|
}
|
|
|
|
nvs_close(my_handle);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static esp_err_t _usb_stream_init(void)
|
|
{
|
|
uvc_config_t uvc_config = {
|
|
.frame_interval = FRAME_INTERVAL_FPS_30,
|
|
.xfer_buffer_size = DEMO_UVC_XFER_BUFFER_SIZE,
|
|
.xfer_buffer_a = xfer_buffer_a,
|
|
.xfer_buffer_b = xfer_buffer_b,
|
|
.frame_buffer_size = DEMO_UVC_XFER_BUFFER_SIZE,
|
|
.frame_buffer = frame_buffer,
|
|
.frame_cb = &camera_frame_cb,
|
|
.frame_cb_arg = NULL,
|
|
.frame_width = FRAME_RESOLUTION_ANY,
|
|
.frame_height = FRAME_RESOLUTION_ANY,
|
|
.flags = FLAG_UVC_SUSPEND_AFTER_START,
|
|
};
|
|
|
|
esp_err_t ret = uvc_streaming_config(&uvc_config);
|
|
if (ret != ESP_OK)
|
|
{
|
|
ESP_LOGE(TAG, "uvc streaming config failed");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static size_t _find_current_resolution(camera_frame_size_t *camera_frame_size)
|
|
{
|
|
if (camera_resolution_info.camera_frame_list == NULL)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
size_t i = 0;
|
|
while (i < camera_resolution_info.camera_frame_list_num)
|
|
{
|
|
if (camera_frame_size->width >= camera_resolution_info.camera_frame_list[i].width && camera_frame_size->height >= camera_resolution_info.camera_frame_list[i].height)
|
|
{
|
|
/* Find next resolution
|
|
If current resolution is the min resolution, switch to the max resolution*/
|
|
camera_frame_size->width = camera_resolution_info.camera_frame_list[i].width;
|
|
camera_frame_size->height = camera_resolution_info.camera_frame_list[i].height;
|
|
break;
|
|
}
|
|
else if (i == camera_resolution_info.camera_frame_list_num - 1)
|
|
{
|
|
camera_frame_size->width = camera_resolution_info.camera_frame_list[i].width;
|
|
camera_frame_size->height = camera_resolution_info.camera_frame_list[i].height;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
ESP_LOGI(TAG, "Current resolution is %dx%d", camera_frame_size->width, camera_frame_size->height);
|
|
return i;
|
|
}
|
|
|
|
static void switch_button_press_down_cb(void *arg, void *data)
|
|
{
|
|
if (camera_resolution_info.camera_frame_list == NULL || xEventGroupWaitBits(s_evt_handle, BIT0_FRAME_START, false, false, pdMS_TO_TICKS(10)) != pdTRUE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "old resolution is %d*%d", camera_resolution_info.camera_frame_size.width, camera_resolution_info.camera_frame_size.height);
|
|
if (++camera_resolution_info.camera_currect_frame_index >= camera_resolution_info.camera_frame_list_num)
|
|
{
|
|
camera_resolution_info.camera_currect_frame_index = 0;
|
|
}
|
|
camera_resolution_info.camera_frame_size.width = camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].width;
|
|
camera_resolution_info.camera_frame_size.height = camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].height;
|
|
ESP_LOGI(TAG, "currect resolution is %d*%d", camera_resolution_info.camera_frame_size.width, camera_resolution_info.camera_frame_size.height);
|
|
|
|
/* Save the new camera resolution to nsv */
|
|
usb_streaming_control(STREAM_UVC, CTRL_SUSPEND, NULL);
|
|
ESP_ERROR_CHECK(uvc_frame_size_reset(camera_resolution_info.camera_frame_size.width,
|
|
camera_resolution_info.camera_frame_size.height,
|
|
FPS2INTERVAL(30)));
|
|
ESP_ERROR_CHECK(_set_value_to_nvs(DEMO_KEY_RESOLUTION, &camera_resolution_info.camera_frame_size, sizeof(camera_frame_size_t)));
|
|
usb_streaming_control(STREAM_UVC, CTRL_RESUME, NULL);
|
|
}
|
|
|
|
static esp_err_t _switch_button_init(void)
|
|
{
|
|
button_config_t button_config = {
|
|
.type = BUTTON_TYPE_GPIO,
|
|
.gpio_button_config = {
|
|
.gpio_num = DEMO_SWITCH_BUTTON_IO,
|
|
.active_level = 0,
|
|
},
|
|
};
|
|
|
|
button_handle_t button_handle = iot_button_create(&button_config);
|
|
assert(button_handle != NULL);
|
|
esp_err_t ret = iot_button_register_cb(button_handle, BUTTON_PRESS_DOWN, switch_button_press_down_cb, NULL);
|
|
if (ret != ESP_OK)
|
|
{
|
|
ESP_LOGE(TAG, "button register callback fail");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void _stream_state_changed_cb(usb_stream_state_t event, void *arg)
|
|
{
|
|
switch (event)
|
|
{
|
|
case STREAM_CONNECTED:
|
|
{
|
|
/* Get camera resolution in nvs*/
|
|
size_t size = sizeof(camera_frame_size_t);
|
|
_get_value_from_nvs(DEMO_KEY_RESOLUTION, &camera_resolution_info.camera_frame_size, &size);
|
|
size_t frame_index = 0;
|
|
uvc_frame_size_list_get(NULL, &camera_resolution_info.camera_frame_list_num, NULL);
|
|
if (camera_resolution_info.camera_frame_list_num)
|
|
{
|
|
ESP_LOGI(TAG, "UVC: get frame list size = %u, current = %u", camera_resolution_info.camera_frame_list_num, frame_index);
|
|
uvc_frame_size_t *_frame_list = (uvc_frame_size_t *)malloc(camera_resolution_info.camera_frame_list_num * sizeof(uvc_frame_size_t));
|
|
|
|
camera_resolution_info.camera_frame_list = (uvc_frame_size_t *)realloc(camera_resolution_info.camera_frame_list, camera_resolution_info.camera_frame_list_num * sizeof(uvc_frame_size_t));
|
|
if (NULL == camera_resolution_info.camera_frame_list)
|
|
{
|
|
ESP_LOGE(TAG, "camera_resolution_info.camera_frame_list");
|
|
}
|
|
uvc_frame_size_list_get(_frame_list, NULL, NULL);
|
|
for (size_t i = 0; i < camera_resolution_info.camera_frame_list_num; i++)
|
|
{
|
|
if (_frame_list[i].width <= DEMO_MAX_H && _frame_list[i].height <= DEMO_MAX_V)
|
|
{
|
|
camera_resolution_info.camera_frame_list[frame_index++] = _frame_list[i];
|
|
ESP_LOGI(TAG, "\tpick frame[%u] = %ux%u", i, _frame_list[i].width, _frame_list[i].height);
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGI(TAG, "\tdrop frame[%u] = %ux%u", i, _frame_list[i].width, _frame_list[i].height);
|
|
}
|
|
}
|
|
camera_resolution_info.camera_frame_list_num = frame_index;
|
|
if (camera_resolution_info.camera_frame_size.width != 0 && camera_resolution_info.camera_frame_size.height != 0)
|
|
{
|
|
camera_resolution_info.camera_currect_frame_index = _find_current_resolution(&camera_resolution_info.camera_frame_size);
|
|
}
|
|
else
|
|
{
|
|
camera_resolution_info.camera_currect_frame_index = 0;
|
|
}
|
|
|
|
if (-1 == camera_resolution_info.camera_currect_frame_index)
|
|
{
|
|
ESP_LOGE(TAG, "fine current resolution fail");
|
|
break;
|
|
}
|
|
ESP_ERROR_CHECK(uvc_frame_size_reset(camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].width,
|
|
camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].height, FPS2INTERVAL(30)));
|
|
camera_frame_size_t camera_frame_size = {
|
|
.width = camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].width,
|
|
.height = camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].height,
|
|
};
|
|
ESP_ERROR_CHECK(_set_value_to_nvs(DEMO_KEY_RESOLUTION, &camera_frame_size, sizeof(camera_frame_size_t)));
|
|
|
|
if (_frame_list != NULL)
|
|
{
|
|
free(_frame_list);
|
|
}
|
|
/* Wait USB Camera connected */
|
|
usb_streaming_control(STREAM_UVC, CTRL_RESUME, NULL);
|
|
xEventGroupSetBits(s_evt_handle, BIT0_FRAME_START);
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGW(TAG, "UVC: get frame list size = %u", camera_resolution_info.camera_frame_list_num);
|
|
}
|
|
ESP_LOGI(TAG, "Device connected");
|
|
break;
|
|
}
|
|
case STREAM_DISCONNECTED:
|
|
xEventGroupClearBits(s_evt_handle, BIT0_FRAME_START);
|
|
ESP_LOGI(TAG, "Device disconnected");
|
|
break;
|
|
default:
|
|
ESP_LOGE(TAG, "Unknown event");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void app_main(void)
|
|
{
|
|
esp_log_level_set("*", ESP_LOG_INFO);
|
|
/* Initialize NVS */
|
|
esp_err_t ret = nvs_flash_init();
|
|
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
|
|
{
|
|
/* NVS partition was truncated and needs to be erased */
|
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
|
/* Retry nvs_flash_init */
|
|
ret = nvs_flash_init();
|
|
}
|
|
ESP_ERROR_CHECK(ret);
|
|
|
|
/* Create event group */
|
|
s_evt_handle = xEventGroupCreate();
|
|
if (s_evt_handle == NULL)
|
|
{
|
|
ESP_LOGE(TAG, "line-%u event group create failed", __LINE__);
|
|
assert(0);
|
|
}
|
|
|
|
/* malloc double buffer for usb payload, xfer_buffer_size >= frame_buffer_size*/
|
|
// xfer_buffer_a = (uint8_t *)heap_caps_calloc(1, DEMO_UVC_XFER_BUFFER_SIZE, MALLOC_CAP_SPIRAM);
|
|
xfer_buffer_a = (uint8_t *)heap_caps_malloc(DEMO_UVC_XFER_BUFFER_SIZE, MALLOC_CAP_SPIRAM);
|
|
assert(xfer_buffer_a != NULL);
|
|
xfer_buffer_b = (uint8_t *)heap_caps_malloc(DEMO_UVC_XFER_BUFFER_SIZE, MALLOC_CAP_SPIRAM);
|
|
assert(xfer_buffer_b != NULL);
|
|
|
|
/* malloc frame buffer for a jpeg frame*/
|
|
frame_buffer = (uint8_t *)heap_caps_malloc(DEMO_UVC_XFER_BUFFER_SIZE, MALLOC_CAP_SPIRAM);
|
|
assert(frame_buffer != NULL);
|
|
|
|
/* Initialize the screen */
|
|
ESP_ERROR_CHECK(_display_init());
|
|
|
|
/* Initialize the button to switch resolution */
|
|
ESP_ERROR_CHECK(_switch_button_init());
|
|
|
|
/* Initialize the screen according to the resolution stored in nvs */
|
|
ESP_ERROR_CHECK(_usb_stream_init());
|
|
|
|
/* Register a callback to control uvc frame size */
|
|
ESP_ERROR_CHECK(usb_streaming_state_register(&_stream_state_changed_cb, NULL));
|
|
|
|
/* Start stream with pre-configs, usb stream driver will create multi-tasks internal
|
|
to handle usb data from different pipes, and user's callback will be called after new frame ready. */
|
|
ESP_ERROR_CHECK(usb_streaming_start());
|
|
ESP_ERROR_CHECK(usb_streaming_connect_wait(portMAX_DELAY));
|
|
}
|