From d5ec8f7081b7f1e5b5577344192d3344d1e3d95f Mon Sep 17 00:00:00 2001 From: Kevincoooool <33611679+Kevincoooool@users.noreply.github.com> Date: Tue, 20 Jan 2026 19:56:36 +0800 Subject: [PATCH] Add ESP32-S3 camera component selection and support (#1670) Introduces a new esp32s3_camera implementation for ESP32-S3 boards using the esp_camera component, with conditional compilation and Kconfig options to select between esp_camera and esp_video. Updates board initialization code and config files to use the new camera class where appropriate, and adjusts build system and dependencies to support both camera components on ESP32-S3 and ESP32-P4 targets. --- main/CMakeLists.txt | 21 + main/Kconfig.projbuild | 25 ++ main/boards/common/esp32_camera.cc | 5 + main/boards/common/esp32_camera.h | 5 +- main/boards/common/esp32s3_camera.cc | 413 ++++++++++++++++++ main/boards/common/esp32s3_camera.h | 53 +++ main/boards/esp32s3-korvo2-v3/config.json | 1 + .../esp32s3_korvo2_v3_board.cc | 74 ++-- .../kevin-sp-v3-dev/kevin-sp-v3_board.cc | 74 ++-- main/boards/kevin-sp-v4-dev/config.json | 1 + .../kevin-sp-v4-dev/kevin-sp-v4_board.cc | 70 ++- main/display/lvgl_display/jpg/image_to_jpeg.h | 118 ++--- main/idf_component.yml | 9 +- 13 files changed, 699 insertions(+), 170 deletions(-) create mode 100644 main/boards/common/esp32s3_camera.cc create mode 100644 main/boards/common/esp32s3_camera.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index ba9db4d..a885970 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -53,6 +53,7 @@ list(APPEND SOURCES "boards/common/backlight.cc" "boards/common/button.cc" "boards/common/esp32_camera.cc" + "boards/common/esp32s3_camera.cc" "boards/common/i2c_device.cc" "boards/common/knob.cc" "boards/common/power_save_timer.cc" @@ -758,10 +759,30 @@ if(CONFIG_IDF_TARGET_ESP32) "display/lvgl_display/jpg/image_to_jpeg.cpp" "display/lvgl_display/jpg/jpeg_to_image.c" "boards/common/esp32_camera.cc" + "boards/common/esp32s3_camera.cc" "boards/common/nt26_board.cc" ) endif() +# ESP32-S3: 根据 Kconfig 选择使用哪个摄像头组件 +if(CONFIG_IDF_TARGET_ESP32S3) + if(CONFIG_XIAOZHI_USE_ESP_CAMERA) + # 使用 esp_camera 组件,排除 esp32_camera.cc + list(REMOVE_ITEM SOURCES "boards/common/esp32_camera.cc") + elseif(CONFIG_XIAOZHI_USE_ESP_VIDEO) + # 使用 esp_video 组件,排除 esp32s3_camera.cc + list(REMOVE_ITEM SOURCES "boards/common/esp32s3_camera.cc") + else() + # 默认使用 esp_camera 组件 + list(REMOVE_ITEM SOURCES "boards/common/esp32_camera.cc") + endif() +endif() + +# ESP32-P4: 只能使用 esp_video 组件,排除 esp32s3_camera.cc +if(CONFIG_IDF_TARGET_ESP32P4) + list(REMOVE_ITEM SOURCES "boards/common/esp32s3_camera.cc") +endif() + idf_component_register(SRCS ${SOURCES} EMBED_FILES ${LANG_SOUNDS} ${COMMON_SOUNDS} INCLUDE_DIRS ${INCLUDE_DIRS} diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 4189da6..f8463c6 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -737,6 +737,31 @@ menu "Camera Configuration" comment "Warning: Please read the help text before modifying these settings." + choice XIAOZHI_CAMERA_COMPONENT + prompt "Camera Component Selection" + default XIAOZHI_USE_ESP_VIDEO if IDF_TARGET_ESP32S3 + default XIAOZHI_USE_ESP_VIDEO if IDF_TARGET_ESP32P4 + help + Select the camera component to use. + ESP32-S3 can choose between esp_camera (legacy) or esp_video (new). + ESP32-P4 only supports esp_video. + + config XIAOZHI_USE_ESP_CAMERA + bool "Use esp_camera (legacy component)" + depends on IDF_TARGET_ESP32S3 + help + Use the legacy esp32-camera component. + This is the traditional camera driver for ESP32-S3. + + config XIAOZHI_USE_ESP_VIDEO + bool "Use esp_video (new component)" + depends on IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4 + help + Use the new esp_video component. + This component provides V4L2-like interface and is required for ESP32-P4. + On ESP32-S3, it provides additional features but may require different camera configuration. + endchoice + config XIAOZHI_CAMERA_ALLOW_JPEG_INPUT bool "Allow JPEG Input" default n diff --git a/main/boards/common/esp32_camera.cc b/main/boards/common/esp32_camera.cc index e51190e..15cdad0 100644 --- a/main/boards/common/esp32_camera.cc +++ b/main/boards/common/esp32_camera.cc @@ -1,3 +1,7 @@ +#include "sdkconfig.h" + +// esp32_camera (使用 esp_video 组件) 用于 ESP32-P4,或 ESP32-S3 选择使用 esp_video 时 +#if defined(CONFIG_IDF_TARGET_ESP32P4) || (defined(CONFIG_IDF_TARGET_ESP32S3) && defined(CONFIG_XIAOZHI_USE_ESP_VIDEO)) #include #include #include @@ -1037,3 +1041,4 @@ std::string Esp32Camera::Explain(const std::string& question) { (int)frame_.len, (int)total_sent, (int)remain_stack_size, question.c_str(), result.c_str()); return result; } +#endif // CONFIG_IDF_TARGET_ESP32P4 || (CONFIG_IDF_TARGET_ESP32S3 && CONFIG_XIAOZHI_USE_ESP_VIDEO) diff --git a/main/boards/common/esp32_camera.h b/main/boards/common/esp32_camera.h index 84366ac..d5ca096 100644 --- a/main/boards/common/esp32_camera.h +++ b/main/boards/common/esp32_camera.h @@ -1,7 +1,8 @@ #pragma once #include "sdkconfig.h" +// esp32_camera (使用 esp_video 组件) 用于 ESP32-P4,或 ESP32-S3 选择使用 esp_video 时 +#if defined(CONFIG_IDF_TARGET_ESP32P4) || (defined(CONFIG_IDF_TARGET_ESP32S3) && defined(CONFIG_XIAOZHI_USE_ESP_VIDEO)) -#ifndef CONFIG_IDF_TARGET_ESP32 #include #include #include @@ -53,4 +54,4 @@ public: virtual std::string Explain(const std::string& question); }; -#endif // ndef CONFIG_IDF_TARGET_ESP32 \ No newline at end of file +#endif // ndef CONFIG_IDF_TARGET_ESP32 diff --git a/main/boards/common/esp32s3_camera.cc b/main/boards/common/esp32s3_camera.cc new file mode 100644 index 0000000..02e8ac3 --- /dev/null +++ b/main/boards/common/esp32s3_camera.cc @@ -0,0 +1,413 @@ +#include "sdkconfig.h" + +// esp32s3_camera (使用 esp_camera 组件) 仅用于 ESP32-S3 且选择使用 esp_camera 时 +#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(CONFIG_XIAOZHI_USE_ESP_CAMERA) + +#include +#include +#include +#include + +#include "esp32s3_camera.h" +#include "board.h" +#include "display.h" +#include "lvgl_display.h" +#include "mcp_server.h" +#include "system_info.h" +#include "jpg/image_to_jpeg.h" + +#define TAG "Esp32S3Camera" + +// V4L2 兼容的格式定义 +#define V4L2_PIX_FMT_RGB565 0x50424752 // 'RGBP' +#define V4L2_PIX_FMT_YUYV 0x56595559 // 'YUYV' +#define V4L2_PIX_FMT_JPEG 0x4745504A // 'JPEG' +#define V4L2_PIX_FMT_RGB24 0x33424752 // 'RGB3' +#define V4L2_PIX_FMT_GREY 0x59455247 // 'GREY' + +static uint32_t pixformat_to_v4l2(pixformat_t fmt) +{ + switch (fmt) + { + case PIXFORMAT_RGB565: + return V4L2_PIX_FMT_RGB565; + case PIXFORMAT_YUV422: + return V4L2_PIX_FMT_YUYV; + case PIXFORMAT_JPEG: + return V4L2_PIX_FMT_JPEG; + case PIXFORMAT_RGB888: + return V4L2_PIX_FMT_RGB24; + case PIXFORMAT_GRAYSCALE: + return V4L2_PIX_FMT_GREY; + default: + return 0; + } +} + +Esp32S3Camera::Esp32S3Camera(const camera_config_t &config) +{ + esp_err_t err = esp_camera_init(&config); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_camera_init failed with error 0x%x", err); + return; + } + + sensor_t *s = esp_camera_sensor_get(); + if (s) + { + frame_.width = config.frame_size == FRAMESIZE_QVGA ? 320 : config.frame_size == FRAMESIZE_VGA ? 640 + : config.frame_size == FRAMESIZE_SVGA ? 800 + : config.frame_size == FRAMESIZE_XGA ? 1024 + : config.frame_size == FRAMESIZE_HD ? 1280 + : config.frame_size == FRAMESIZE_SXGA ? 1280 + : config.frame_size == FRAMESIZE_UXGA ? 1600 + : 320; + frame_.height = config.frame_size == FRAMESIZE_QVGA ? 240 : config.frame_size == FRAMESIZE_VGA ? 480 + : config.frame_size == FRAMESIZE_SVGA ? 600 + : config.frame_size == FRAMESIZE_XGA ? 768 + : config.frame_size == FRAMESIZE_HD ? 720 + : config.frame_size == FRAMESIZE_SXGA ? 1024 + : config.frame_size == FRAMESIZE_UXGA ? 1200 + : 240; + frame_.format = config.pixel_format; + ESP_LOGI(TAG, "Camera initialized: %dx%d, format=%d", frame_.width, frame_.height, config.pixel_format); + } + + streaming_on_ = true; + ESP_LOGI(TAG, "ESP32-S3 Camera init success"); +} + +Esp32S3Camera::~Esp32S3Camera() +{ + if (streaming_on_) + { + if (current_fb_) + { + esp_camera_fb_return(current_fb_); + current_fb_ = nullptr; + } + esp_camera_deinit(); + streaming_on_ = false; + } + if (frame_.data) + { + heap_caps_free(frame_.data); + frame_.data = nullptr; + } +} + +void Esp32S3Camera::SetExplainUrl(const std::string &url, const std::string &token) +{ + explain_url_ = url; + explain_token_ = token; +} + +bool Esp32S3Camera::Capture() +{ + if (encoder_thread_.joinable()) + { + encoder_thread_.join(); + } + + if (!streaming_on_) + { + return false; + } + + // 释放之前的帧 + if (current_fb_) + { + esp_camera_fb_return(current_fb_); + current_fb_ = nullptr; + } + + // 丢弃前两帧,获取最新帧 + for (int i = 0; i < 3; i++) + { + camera_fb_t *fb = esp_camera_fb_get(); + if (!fb) + { + ESP_LOGE(TAG, "Camera capture failed"); + return false; + } + if (i < 2) + { + esp_camera_fb_return(fb); + } + else + { + current_fb_ = fb; + } + } + + if (!current_fb_) + { + ESP_LOGE(TAG, "Failed to get frame buffer"); + return false; + } + + // 保存帧副本到 PSRAM + if (frame_.data) + { + heap_caps_free(frame_.data); + frame_.data = nullptr; + } + + frame_.len = current_fb_->len; + frame_.width = current_fb_->width; + frame_.height = current_fb_->height; + frame_.format = current_fb_->format; + + frame_.data = (uint8_t *)heap_caps_malloc(frame_.len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (!frame_.data) + { + ESP_LOGE(TAG, "Failed to allocate %zu bytes for frame copy", frame_.len); + esp_camera_fb_return(current_fb_); + current_fb_ = nullptr; + return false; + } + memcpy(frame_.data, current_fb_->buf, frame_.len); + + // 释放原始帧 + esp_camera_fb_return(current_fb_); + current_fb_ = nullptr; + + // 对 RGB565 格式进行字节交换 (Big Endian <-> Little Endian) + // 这样 frame_.data 就是已交换的数据,显示和上传都使用相同的数据 + if (frame_.format == PIXFORMAT_RGB565) + { + uint8_t *data = frame_.data; + size_t pixel_count = frame_.width * frame_.height; + for (size_t i = 0; i < pixel_count; i++) + { + uint8_t temp = data[2 * i]; + data[2 * i] = data[2 * i + 1]; + data[2 * i + 1] = temp; + } + } + + ESP_LOGD(TAG, "Captured frame: %dx%d, len=%zu, format=%d", + frame_.width, frame_.height, frame_.len, frame_.format); + + // 显示预览图片 + auto display = dynamic_cast(Board::GetInstance().GetDisplay()); + if (display != nullptr) + { + if (!frame_.data) + { + ESP_LOGE(TAG, "frame.data is null"); + return false; + } + + uint16_t w = frame_.width; + uint16_t h = frame_.height; + size_t lvgl_image_size = frame_.len; + size_t stride = ((w * 2) + 3) & ~3; // 4字节对齐 + lv_color_format_t color_format = LV_COLOR_FORMAT_RGB565; + uint8_t *data = nullptr; + + switch (frame_.format) + { + case PIXFORMAT_RGB565: + // frame_.data 已经在捕获阶段完成了字节交换,直接复制即可 + data = (uint8_t *)heap_caps_malloc(w * h * 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (data == nullptr) + { + ESP_LOGE(TAG, "Failed to allocate memory for preview image"); + return false; + } + memcpy(data, frame_.data, frame_.len); + lvgl_image_size = frame_.len; + break; + + case PIXFORMAT_JPEG: + // JPEG 格式需要解码 - 跳过预览显示 + ESP_LOGD(TAG, "JPEG format preview not supported, skipping display"); + return true; + + default: + ESP_LOGE(TAG, "Unsupported frame format for preview: %d", frame_.format); + return true; // 仍然返回 true,因为捕获成功 + } + + if (data) + { + auto image = std::make_unique(data, lvgl_image_size, w, h, stride, color_format); + display->SetPreviewImage(std::move(image)); + } + } + return true; +} + +bool Esp32S3Camera::SetHMirror(bool enabled) +{ + sensor_t *s = esp_camera_sensor_get(); + if (!s) + { + return false; + } + s->set_hmirror(s, enabled ? 1 : 0); + return true; +} + +bool Esp32S3Camera::SetVFlip(bool enabled) +{ + sensor_t *s = esp_camera_sensor_get(); + if (!s) + { + return false; + } + s->set_vflip(s, enabled ? 1 : 0); + return true; +} + +std::string Esp32S3Camera::Explain(const std::string &question) +{ + if (explain_url_.empty()) + { + throw std::runtime_error("Image explain URL or token is not set"); + } + + // 创建局部的 JPEG 队列 + QueueHandle_t jpeg_queue = xQueueCreate(40, sizeof(JpegChunk)); + if (jpeg_queue == nullptr) + { + ESP_LOGE(TAG, "Failed to create JPEG queue"); + throw std::runtime_error("Failed to create JPEG queue"); + } + + // 转换格式为 v4l2 兼容格式 + uint32_t v4l2_format = pixformat_to_v4l2(frame_.format); + + // 启动编码线程 + encoder_thread_ = std::thread([this, jpeg_queue, v4l2_format]() + { + uint16_t w = frame_.width ? frame_.width : 320; + uint16_t h = frame_.height ? frame_.height : 240; + bool ok = image_to_jpeg_cb( + frame_.data, frame_.len, w, h, static_cast(v4l2_format), 80, + [](void* arg, size_t index, const void* data, size_t len) -> size_t { + auto jpeg_queue = static_cast(arg); + JpegChunk chunk = {.data = nullptr, .len = len}; + if (index == 0 && data != nullptr && len > 0) { + chunk.data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (chunk.data == nullptr) { + ESP_LOGE(TAG, "Failed to allocate %zu bytes for JPEG chunk", len); + chunk.len = 0; + } else { + memcpy(chunk.data, data, len); + } + } else { + chunk.len = 0; + } + xQueueSend(jpeg_queue, &chunk, portMAX_DELAY); + return len; + }, + jpeg_queue); + + if (!ok) { + JpegChunk chunk = {.data = nullptr, .len = 0}; + xQueueSend(jpeg_queue, &chunk, portMAX_DELAY); + } }); + + auto network = Board::GetInstance().GetNetwork(); + auto http = network->CreateHttp(3); + std::string boundary = "----ESP32_CAMERA_BOUNDARY"; + + http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); + http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str()); + if (!explain_token_.empty()) + { + http->SetHeader("Authorization", "Bearer " + explain_token_); + } + http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary); + http->SetHeader("Transfer-Encoding", "chunked"); + if (!http->Open("POST", explain_url_)) + { + ESP_LOGE(TAG, "Failed to connect to explain URL"); + encoder_thread_.join(); + JpegChunk chunk; + while (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) == pdPASS) + { + if (chunk.data != nullptr) + { + heap_caps_free(chunk.data); + } + else + { + break; + } + } + vQueueDelete(jpeg_queue); + throw std::runtime_error("Failed to connect to explain URL"); + } + + { + std::string question_field; + question_field += "--" + boundary + "\r\n"; + question_field += "Content-Disposition: form-data; name=\"question\"\r\n"; + question_field += "\r\n"; + question_field += question + "\r\n"; + http->Write(question_field.c_str(), question_field.size()); + } + { + std::string file_header; + file_header += "--" + boundary + "\r\n"; + file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n"; + file_header += "Content-Type: image/jpeg\r\n"; + file_header += "\r\n"; + http->Write(file_header.c_str(), file_header.size()); + } + + size_t total_sent = 0; + bool saw_terminator = false; + while (true) + { + JpegChunk chunk; + if (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) != pdPASS) + { + ESP_LOGE(TAG, "Failed to receive JPEG chunk"); + break; + } + if (chunk.data == nullptr) + { + saw_terminator = true; + break; + } + http->Write((const char *)chunk.data, chunk.len); + total_sent += chunk.len; + heap_caps_free(chunk.data); + } + encoder_thread_.join(); + vQueueDelete(jpeg_queue); + + if (!saw_terminator || total_sent == 0) + { + ESP_LOGE(TAG, "JPEG encoder failed or produced empty output"); + throw std::runtime_error("Failed to encode image to JPEG"); + } + + { + std::string multipart_footer; + multipart_footer += "\r\n--" + boundary + "--\r\n"; + http->Write(multipart_footer.c_str(), multipart_footer.size()); + } + http->Write("", 0); + + if (http->GetStatusCode() != 200) + { + ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode()); + throw std::runtime_error("Failed to upload photo"); + } + + std::string result = http->ReadAll(); + http->Close(); + + size_t remain_stack_size = uxTaskGetStackHighWaterMark(nullptr); + ESP_LOGI(TAG, "Explain image size=%d bytes, compressed size=%d, remain stack size=%d, question=%s\n%s", + (int)frame_.len, (int)total_sent, (int)remain_stack_size, question.c_str(), result.c_str()); + return result; +} + +#endif // CONFIG_IDF_TARGET_ESP32S3 && CONFIG_XIAOZHI_USE_ESP_CAMERA diff --git a/main/boards/common/esp32s3_camera.h b/main/boards/common/esp32s3_camera.h new file mode 100644 index 0000000..0974942 --- /dev/null +++ b/main/boards/common/esp32s3_camera.h @@ -0,0 +1,53 @@ +#pragma once +#include "sdkconfig.h" + +// esp32s3_camera (使用 esp_camera 组件) 仅用于 ESP32-S3 且选择使用 esp_camera 时 +#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(CONFIG_XIAOZHI_USE_ESP_CAMERA) + +#include +#include +#include +#include + +#include +#include + +#include "camera.h" +#include "esp_camera.h" + +struct JpegChunk +{ + uint8_t *data; + size_t len; +}; + +class Esp32S3Camera : public Camera +{ +private: + struct FrameBuffer + { + uint8_t *data = nullptr; + size_t len = 0; + uint16_t width = 0; + uint16_t height = 0; + pixformat_t format = PIXFORMAT_RGB565; + } frame_; + + bool streaming_on_ = false; + std::string explain_url_; + std::string explain_token_; + std::thread encoder_thread_; + camera_fb_t *current_fb_ = nullptr; + +public: + Esp32S3Camera(const camera_config_t &config); + ~Esp32S3Camera(); + + virtual void SetExplainUrl(const std::string &url, const std::string &token) override; + virtual bool Capture() override; + virtual bool SetHMirror(bool enabled) override; + virtual bool SetVFlip(bool enabled) override; + virtual std::string Explain(const std::string &question) override; +}; + +#endif // CONFIG_IDF_TARGET_ESP32S3 && CONFIG_XIAOZHI_USE_ESP_CAMERA diff --git a/main/boards/esp32s3-korvo2-v3/config.json b/main/boards/esp32s3-korvo2-v3/config.json index 8b967ff..e5b4095 100644 --- a/main/boards/esp32s3-korvo2-v3/config.json +++ b/main/boards/esp32s3-korvo2-v3/config.json @@ -4,6 +4,7 @@ { "name": "esp32s3-korvo2-v3", "sdkconfig_append": [ + "CONFIG_XIAOZHI_USE_ESP_CAMERA=y", "CONFIG_CAMERA_OV2640=y", "CONFIG_CAMERA_OV3660=y", "CONFIG_CAMERA_OV3660_AUTO_DETECT_DVP_INTERFACE_SENSOR=y", diff --git a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc index 801c5dd..92fc972 100644 --- a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc +++ b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc @@ -13,7 +13,7 @@ #include #include #include -#include "esp32_camera.h" +#include "esp32s3_camera.h" #include "power_manager.h" #include "power_save_timer.h" @@ -60,7 +60,7 @@ private: i2c_master_bus_handle_t i2c_bus_; LcdDisplay* display_; esp_io_expander_handle_t io_expander_ = NULL; - Esp32Camera* camera_; + Esp32S3Camera* camera_; PowerSaveTimer* power_save_timer_; PowerManager* power_manager_; void InitializePowerManager() { @@ -353,43 +353,43 @@ private: } void InitializeCamera() { - static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = { - .data_width = CAM_CTLR_DATA_WIDTH_8, - .data_io = { - [0] = CAMERA_PIN_D0, - [1] = CAMERA_PIN_D1, - [2] = CAMERA_PIN_D2, - [3] = CAMERA_PIN_D3, - [4] = CAMERA_PIN_D4, - [5] = CAMERA_PIN_D5, - [6] = CAMERA_PIN_D6, - [7] = CAMERA_PIN_D7, - }, - .vsync_io = CAMERA_PIN_VSYNC, - .de_io = CAMERA_PIN_HREF, - .pclk_io = CAMERA_PIN_PCLK, - .xclk_io = CAMERA_PIN_XCLK, + // ESP32-S3 使用 esp_camera 组件 + camera_config_t camera_config = { + .pin_pwdn = CAMERA_PIN_PWDN, + .pin_reset = CAMERA_PIN_RESET, + .pin_xclk = CAMERA_PIN_XCLK, + .pin_sccb_sda = -1, // 使用已初始化的 I2C + .pin_sccb_scl = -1, + .pin_d7 = CAMERA_PIN_D7, + .pin_d6 = CAMERA_PIN_D6, + .pin_d5 = CAMERA_PIN_D5, + .pin_d4 = CAMERA_PIN_D4, + .pin_d3 = CAMERA_PIN_D3, + .pin_d2 = CAMERA_PIN_D2, + .pin_d1 = CAMERA_PIN_D1, + .pin_d0 = CAMERA_PIN_D0, + .pin_vsync = CAMERA_PIN_VSYNC, + .pin_href = CAMERA_PIN_HREF, + .pin_pclk = CAMERA_PIN_PCLK, + + .xclk_freq_hz = XCLK_FREQ_HZ, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + + .pixel_format = PIXFORMAT_RGB565, + .frame_size = FRAMESIZE_QVGA, + .jpeg_quality = 12, + .fb_count = 2, + .fb_location = CAMERA_FB_IN_PSRAM, + .grab_mode = CAMERA_GRAB_WHEN_EMPTY, + .sccb_i2c_port = (i2c_port_t)1, }; - esp_video_init_sccb_config_t sccb_config = { - .init_sccb = false, - .i2c_handle = i2c_bus_, - .freq = 100000, - }; - - esp_video_init_dvp_config_t dvp_config = { - .sccb_config = sccb_config, - .reset_pin = CAMERA_PIN_RESET, - .pwdn_pin = CAMERA_PIN_PWDN, - .dvp_pin = dvp_pin_config, - .xclk_freq = XCLK_FREQ_HZ, - }; - - esp_video_init_config_t video_config = { - .dvp = &dvp_config, - }; - - camera_ = new Esp32Camera(video_config); + camera_ = new Esp32S3Camera(camera_config); + if(camera_ != nullptr) + { + camera_->SetVFlip(true); + } } public: diff --git a/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc b/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc index 782504e..95f2ef8 100644 --- a/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc +++ b/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc @@ -12,7 +12,7 @@ #include #include #include -#include "esp32_camera.h" +#include "esp32s3_camera.h" #define TAG "kevin-sp-v3" @@ -22,7 +22,7 @@ private: i2c_master_bus_handle_t display_i2c_bus_; Button boot_button_; LcdDisplay* display_; - Esp32Camera* camera_; + Esp32S3Camera* camera_; void InitializeSpi() { spi_bus_config_t buscfg = {}; @@ -84,47 +84,39 @@ private: } void InitializeCamera() { - static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = { - .data_width = CAM_CTLR_DATA_WIDTH_8, - .data_io = { - [0] = CAMERA_PIN_D0, - [1] = CAMERA_PIN_D1, - [2] = CAMERA_PIN_D2, - [3] = CAMERA_PIN_D3, - [4] = CAMERA_PIN_D4, - [5] = CAMERA_PIN_D5, - [6] = CAMERA_PIN_D6, - [7] = CAMERA_PIN_D7, - }, - .vsync_io = CAMERA_PIN_VSYNC, - .de_io = CAMERA_PIN_HREF, - .pclk_io = CAMERA_PIN_PCLK, - .xclk_io = CAMERA_PIN_XCLK, + // ESP32-S3 使用 esp_camera 组件 + camera_config_t camera_config = { + .pin_pwdn = CAMERA_PIN_PWDN, + .pin_reset = CAMERA_PIN_RESET, + .pin_xclk = CAMERA_PIN_XCLK, + .pin_sccb_sda = -1, // 使用已初始化的 I2C + .pin_sccb_scl = -1, + .pin_d7 = CAMERA_PIN_D7, + .pin_d6 = CAMERA_PIN_D6, + .pin_d5 = CAMERA_PIN_D5, + .pin_d4 = CAMERA_PIN_D4, + .pin_d3 = CAMERA_PIN_D3, + .pin_d2 = CAMERA_PIN_D2, + .pin_d1 = CAMERA_PIN_D1, + .pin_d0 = CAMERA_PIN_D0, + .pin_vsync = CAMERA_PIN_VSYNC, + .pin_href = CAMERA_PIN_HREF, + .pin_pclk = CAMERA_PIN_PCLK, + + .xclk_freq_hz = XCLK_FREQ_HZ, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + + .pixel_format = PIXFORMAT_RGB565, + .frame_size = FRAMESIZE_QVGA, + .jpeg_quality = 12, + .fb_count = 2, + .fb_location = CAMERA_FB_IN_PSRAM, + .grab_mode = CAMERA_GRAB_WHEN_EMPTY, + .sccb_i2c_port = (i2c_port_t)1, }; - esp_video_init_sccb_config_t sccb_config = { - .init_sccb = true, - .i2c_config = { - .port = 1, - .scl_pin = CAMERA_PIN_SIOC, - .sda_pin = CAMERA_PIN_SIOD, - }, - .freq = 100000, - }; - - esp_video_init_dvp_config_t dvp_config = { - .sccb_config = sccb_config, - .reset_pin = CAMERA_PIN_RESET, - .pwdn_pin = CAMERA_PIN_PWDN, - .dvp_pin = dvp_pin_config, - .xclk_freq = XCLK_FREQ_HZ, - }; - - esp_video_init_config_t video_config = { - .dvp = &dvp_config, - }; - - camera_ = new Esp32Camera(video_config); + camera_ = new Esp32S3Camera(camera_config); } public: diff --git a/main/boards/kevin-sp-v4-dev/config.json b/main/boards/kevin-sp-v4-dev/config.json index b01291e..82f8001 100644 --- a/main/boards/kevin-sp-v4-dev/config.json +++ b/main/boards/kevin-sp-v4-dev/config.json @@ -4,6 +4,7 @@ { "name": "kevin-sp-v4-dev", "sdkconfig_append": [ + "CONFIG_XIAOZHI_USE_ESP_CAMERA=y", "CONFIG_CAMERA_OV2640=y", "CONFIG_CAMERA_OV3660=y", "CONFIG_CAMERA_OV3660_AUTO_DETECT_DVP_INTERFACE_SENSOR=y", diff --git a/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc b/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc index 7887940..ca76838 100644 --- a/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc +++ b/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc @@ -10,7 +10,7 @@ #include #include #include -#include "esp32_camera.h" +#include "esp32s3_camera.h" #define TAG "kevin-sp-v4" @@ -19,7 +19,7 @@ private: Button boot_button_; LcdDisplay* display_; i2c_master_bus_handle_t i2c_bus_; - Esp32Camera* camera_; + Esp32S3Camera* camera_; void InitializeCodecI2c() { // Initialize I2C peripheral @@ -98,43 +98,39 @@ private: } void InitializeCamera() { - static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = { - .data_width = CAM_CTLR_DATA_WIDTH_8, - .data_io = { - [0] = CAMERA_PIN_D0, - [1] = CAMERA_PIN_D1, - [2] = CAMERA_PIN_D2, - [3] = CAMERA_PIN_D3, - [4] = CAMERA_PIN_D4, - [5] = CAMERA_PIN_D5, - [6] = CAMERA_PIN_D6, - [7] = CAMERA_PIN_D7, - }, - .vsync_io = CAMERA_PIN_VSYNC, - .de_io = CAMERA_PIN_HREF, - .pclk_io = CAMERA_PIN_PCLK, - .xclk_io = CAMERA_PIN_XCLK, + // ESP32-S3 使用 esp_camera 组件 + camera_config_t camera_config = { + .pin_pwdn = CAMERA_PIN_PWDN, + .pin_reset = CAMERA_PIN_RESET, + .pin_xclk = CAMERA_PIN_XCLK, + .pin_sccb_sda = -1, // 使用已初始化的 I2C + .pin_sccb_scl = -1, + .pin_d7 = CAMERA_PIN_D7, + .pin_d6 = CAMERA_PIN_D6, + .pin_d5 = CAMERA_PIN_D5, + .pin_d4 = CAMERA_PIN_D4, + .pin_d3 = CAMERA_PIN_D3, + .pin_d2 = CAMERA_PIN_D2, + .pin_d1 = CAMERA_PIN_D1, + .pin_d0 = CAMERA_PIN_D0, + .pin_vsync = CAMERA_PIN_VSYNC, + .pin_href = CAMERA_PIN_HREF, + .pin_pclk = CAMERA_PIN_PCLK, + + .xclk_freq_hz = XCLK_FREQ_HZ, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + + .pixel_format = PIXFORMAT_RGB565, + .frame_size = FRAMESIZE_QVGA, + .jpeg_quality = 12, + .fb_count = 2, + .fb_location = CAMERA_FB_IN_PSRAM, + .grab_mode = CAMERA_GRAB_WHEN_EMPTY, + .sccb_i2c_port = (i2c_port_t)1, }; - esp_video_init_sccb_config_t sccb_config = { - .init_sccb = false, - .i2c_handle = i2c_bus_, - .freq = 100000, - }; - - esp_video_init_dvp_config_t dvp_config = { - .sccb_config = sccb_config, - .reset_pin = CAMERA_PIN_RESET, - .pwdn_pin = CAMERA_PIN_PWDN, - .dvp_pin = dvp_pin_config, - .xclk_freq = XCLK_FREQ_HZ, - }; - - esp_video_init_config_t video_config = { - .dvp = &dvp_config, - }; - - camera_ = new Esp32Camera(video_config); + camera_ = new Esp32S3Camera(camera_config); } public: diff --git a/main/display/lvgl_display/jpg/image_to_jpeg.h b/main/display/lvgl_display/jpg/image_to_jpeg.h index 61898c2..2791469 100644 --- a/main/display/lvgl_display/jpg/image_to_jpeg.h +++ b/main/display/lvgl_display/jpg/image_to_jpeg.h @@ -6,62 +6,78 @@ #include #include + +#if defined(CONFIG_IDF_TARGET_ESP32P4) +// ESP32-P4 使用 esp_video 组件提供的 V4L2 头文件 #include - -typedef uint32_t v4l2_pix_fmt_t; // see linux/videodev2.h for details - -#ifdef __cplusplus -extern "C" { +#else +// ESP32-S3 等其他芯片:定义常用的 V4L2 像素格式 +#define V4L2_PIX_FMT_RGB565 0x50424752 // 'RGBP' +#define V4L2_PIX_FMT_RGB565X 0x52474250 // 'PRGB' +#define V4L2_PIX_FMT_RGB24 0x33424752 // 'RGB3' +#define V4L2_PIX_FMT_YUYV 0x56595559 // 'YUYV' +#define V4L2_PIX_FMT_YUV422P 0x36315559 // 'YU16' +#define V4L2_PIX_FMT_YUV420 0x32315559 // 'YU12' +#define V4L2_PIX_FMT_GREY 0x59455247 // 'GREY' +#define V4L2_PIX_FMT_UYVY 0x59565955 // 'UYVY' +#define V4L2_PIX_FMT_JPEG 0x4745504A // 'JPEG' #endif -// JPEG输出回调函数类型 -// arg: 用户自定义参数, index: 当前数据索引, data: JPEG数据块, len: 数据块长度 -// 返回: 实际处理的字节数 -typedef size_t (*jpg_out_cb)(void *arg, size_t index, const void *data, size_t len); +typedef uint32_t v4l2_pix_fmt_t; -/** - * @brief 将图像格式高效转换为JPEG - * - * 这个函数使用优化的JPEG编码器进行编码,主要特点: - * - 节省约8KB的SRAM使用(静态变量改为堆分配) - * - 支持多种图像格式输入 - * - 高质量JPEG输出 - * - * @param src 源图像数据 - * @param src_len 源图像数据长度 - * @param width 图像宽度 - * @param height 图像高度 - * @param format 图像格式 (PIXFORMAT_RGB565, PIXFORMAT_RGB888, 等) - * @param quality JPEG质量 (1-100) - * @param out 输出JPEG数据指针 (需要调用者释放) - * @param out_len 输出JPEG数据长度 - * - * @return true 成功, false 失败 - */ -bool image_to_jpeg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, - v4l2_pix_fmt_t format, uint8_t quality, uint8_t **out, size_t *out_len); +#ifdef __cplusplus +extern "C" +{ +#endif -/** - * @brief 将图像格式转换为JPEG(回调版本) - * - * 使用回调函数处理JPEG输出数据,适合流式传输或分块处理: - * - 节省约8KB的SRAM使用(静态变量改为堆分配) - * - 支持流式输出,无需预分配大缓冲区 - * - 通过回调函数逐块处理JPEG数据 - * - * @param src 源图像数据 - * @param src_len 源图像数据长度 - * @param width 图像宽度 - * @param height 图像高度 - * @param format 图像格式 - * @param quality JPEG质量 (1-100) - * @param cb 输出回调函数 - * @param arg 传递给回调函数的用户参数 - * - * @return true 成功, false 失败 - */ -bool image_to_jpeg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, - v4l2_pix_fmt_t format, uint8_t quality, jpg_out_cb cb, void *arg); + // JPEG输出回调函数类型 + // arg: 用户自定义参数, index: 当前数据索引, data: JPEG数据块, len: 数据块长度 + // 返回: 实际处理的字节数 + typedef size_t (*jpg_out_cb)(void *arg, size_t index, const void *data, size_t len); + + /** + * @brief 将图像格式高效转换为JPEG + * + * 这个函数使用优化的JPEG编码器进行编码,主要特点: + * - 节省约8KB的SRAM使用(静态变量改为堆分配) + * - 支持多种图像格式输入 + * - 高质量JPEG输出 + * + * @param src 源图像数据 + * @param src_len 源图像数据长度 + * @param width 图像宽度 + * @param height 图像高度 + * @param format 图像格式 (PIXFORMAT_RGB565, PIXFORMAT_RGB888, 等) + * @param quality JPEG质量 (1-100) + * @param out 输出JPEG数据指针 (需要调用者释放) + * @param out_len 输出JPEG数据长度 + * + * @return true 成功, false 失败 + */ + bool image_to_jpeg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, + v4l2_pix_fmt_t format, uint8_t quality, uint8_t **out, size_t *out_len); + + /** + * @brief 将图像格式转换为JPEG(回调版本) + * + * 使用回调函数处理JPEG输出数据,适合流式传输或分块处理: + * - 节省约8KB的SRAM使用(静态变量改为堆分配) + * - 支持流式输出,无需预分配大缓冲区 + * - 通过回调函数逐块处理JPEG数据 + * + * @param src 源图像数据 + * @param src_len 源图像数据长度 + * @param width 图像宽度 + * @param height 图像高度 + * @param format 图像格式 + * @param quality JPEG质量 (1-100) + * @param cb 输出回调函数 + * @param arg 传递给回调函数的用户参数 + * + * @return true 成功, false 失败 + */ + bool image_to_jpeg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, + v4l2_pix_fmt_t format, uint8_t quality, jpg_out_cb cb, void *arg); #ifdef __cplusplus } diff --git a/main/idf_component.yml b/main/idf_component.yml index 24d7f16..4a5a2aa 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -32,10 +32,15 @@ dependencies: espressif/esp-sr: ~2.2.0 espressif/button: ~4.1.3 espressif/knob: ^1.0.0 + espressif/esp32-camera: + version: ^2.0.15 + rules: + - if: target in [esp32s3] espressif/esp_video: version: ==1.3.1 # for compatibility. update version may need to modify this project code. rules: - - if: target not in [esp32] + - if: target in [esp32p4, esp32s3] + espressif/esp_image_effects: version: ^1.0.1 rules: @@ -111,5 +116,5 @@ dependencies: ## Required IDF version idf: - version: '>=5.5.2' + version: '>=5.5.1' espressif/esp_lcd_touch_st7123: ^1.0.0