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.
This commit is contained in:
@ -53,6 +53,7 @@ list(APPEND SOURCES
|
|||||||
"boards/common/backlight.cc"
|
"boards/common/backlight.cc"
|
||||||
"boards/common/button.cc"
|
"boards/common/button.cc"
|
||||||
"boards/common/esp32_camera.cc"
|
"boards/common/esp32_camera.cc"
|
||||||
|
"boards/common/esp32s3_camera.cc"
|
||||||
"boards/common/i2c_device.cc"
|
"boards/common/i2c_device.cc"
|
||||||
"boards/common/knob.cc"
|
"boards/common/knob.cc"
|
||||||
"boards/common/power_save_timer.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/image_to_jpeg.cpp"
|
||||||
"display/lvgl_display/jpg/jpeg_to_image.c"
|
"display/lvgl_display/jpg/jpeg_to_image.c"
|
||||||
"boards/common/esp32_camera.cc"
|
"boards/common/esp32_camera.cc"
|
||||||
|
"boards/common/esp32s3_camera.cc"
|
||||||
"boards/common/nt26_board.cc"
|
"boards/common/nt26_board.cc"
|
||||||
)
|
)
|
||||||
endif()
|
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}
|
idf_component_register(SRCS ${SOURCES}
|
||||||
EMBED_FILES ${LANG_SOUNDS} ${COMMON_SOUNDS}
|
EMBED_FILES ${LANG_SOUNDS} ${COMMON_SOUNDS}
|
||||||
INCLUDE_DIRS ${INCLUDE_DIRS}
|
INCLUDE_DIRS ${INCLUDE_DIRS}
|
||||||
|
|||||||
@ -737,6 +737,31 @@ menu "Camera Configuration"
|
|||||||
|
|
||||||
comment "Warning: Please read the help text before modifying these settings."
|
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
|
config XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
|
||||||
bool "Allow JPEG Input"
|
bool "Allow JPEG Input"
|
||||||
default n
|
default n
|
||||||
|
|||||||
@ -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 <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
@ -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());
|
(int)frame_.len, (int)total_sent, (int)remain_stack_size, question.c_str(), result.c_str());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
#endif // CONFIG_IDF_TARGET_ESP32P4 || (CONFIG_IDF_TARGET_ESP32S3 && CONFIG_XIAOZHI_USE_ESP_VIDEO)
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "sdkconfig.h"
|
#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 <lvgl.h>
|
#include <lvgl.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|||||||
413
main/boards/common/esp32s3_camera.cc
Normal file
413
main/boards/common/esp32s3_camera.cc
Normal file
@ -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 <esp_heap_caps.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
#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<LvglDisplay *>(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<LvglAllocatedImage>(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_pix_fmt_t>(v4l2_format), 80,
|
||||||
|
[](void* arg, size_t index, const void* data, size_t len) -> size_t {
|
||||||
|
auto jpeg_queue = static_cast<QueueHandle_t>(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
|
||||||
53
main/boards/common/esp32s3_camera.h
Normal file
53
main/boards/common/esp32s3_camera.h
Normal file
@ -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 <lvgl.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/queue.h>
|
||||||
|
|
||||||
|
#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
|
||||||
@ -4,6 +4,7 @@
|
|||||||
{
|
{
|
||||||
"name": "esp32s3-korvo2-v3",
|
"name": "esp32s3-korvo2-v3",
|
||||||
"sdkconfig_append": [
|
"sdkconfig_append": [
|
||||||
|
"CONFIG_XIAOZHI_USE_ESP_CAMERA=y",
|
||||||
"CONFIG_CAMERA_OV2640=y",
|
"CONFIG_CAMERA_OV2640=y",
|
||||||
"CONFIG_CAMERA_OV3660=y",
|
"CONFIG_CAMERA_OV3660=y",
|
||||||
"CONFIG_CAMERA_OV3660_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
|
"CONFIG_CAMERA_OV3660_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
#include <esp_lcd_ili9341.h>
|
#include <esp_lcd_ili9341.h>
|
||||||
#include <driver/i2c_master.h>
|
#include <driver/i2c_master.h>
|
||||||
#include <driver/spi_common.h>
|
#include <driver/spi_common.h>
|
||||||
#include "esp32_camera.h"
|
#include "esp32s3_camera.h"
|
||||||
#include "power_manager.h"
|
#include "power_manager.h"
|
||||||
#include "power_save_timer.h"
|
#include "power_save_timer.h"
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ private:
|
|||||||
i2c_master_bus_handle_t i2c_bus_;
|
i2c_master_bus_handle_t i2c_bus_;
|
||||||
LcdDisplay* display_;
|
LcdDisplay* display_;
|
||||||
esp_io_expander_handle_t io_expander_ = NULL;
|
esp_io_expander_handle_t io_expander_ = NULL;
|
||||||
Esp32Camera* camera_;
|
Esp32S3Camera* camera_;
|
||||||
PowerSaveTimer* power_save_timer_;
|
PowerSaveTimer* power_save_timer_;
|
||||||
PowerManager* power_manager_;
|
PowerManager* power_manager_;
|
||||||
void InitializePowerManager() {
|
void InitializePowerManager() {
|
||||||
@ -353,43 +353,43 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InitializeCamera() {
|
void InitializeCamera() {
|
||||||
static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
|
// ESP32-S3 使用 esp_camera 组件
|
||||||
.data_width = CAM_CTLR_DATA_WIDTH_8,
|
camera_config_t camera_config = {
|
||||||
.data_io = {
|
.pin_pwdn = CAMERA_PIN_PWDN,
|
||||||
[0] = CAMERA_PIN_D0,
|
.pin_reset = CAMERA_PIN_RESET,
|
||||||
[1] = CAMERA_PIN_D1,
|
.pin_xclk = CAMERA_PIN_XCLK,
|
||||||
[2] = CAMERA_PIN_D2,
|
.pin_sccb_sda = -1, // 使用已初始化的 I2C
|
||||||
[3] = CAMERA_PIN_D3,
|
.pin_sccb_scl = -1,
|
||||||
[4] = CAMERA_PIN_D4,
|
.pin_d7 = CAMERA_PIN_D7,
|
||||||
[5] = CAMERA_PIN_D5,
|
.pin_d6 = CAMERA_PIN_D6,
|
||||||
[6] = CAMERA_PIN_D6,
|
.pin_d5 = CAMERA_PIN_D5,
|
||||||
[7] = CAMERA_PIN_D7,
|
.pin_d4 = CAMERA_PIN_D4,
|
||||||
},
|
.pin_d3 = CAMERA_PIN_D3,
|
||||||
.vsync_io = CAMERA_PIN_VSYNC,
|
.pin_d2 = CAMERA_PIN_D2,
|
||||||
.de_io = CAMERA_PIN_HREF,
|
.pin_d1 = CAMERA_PIN_D1,
|
||||||
.pclk_io = CAMERA_PIN_PCLK,
|
.pin_d0 = CAMERA_PIN_D0,
|
||||||
.xclk_io = CAMERA_PIN_XCLK,
|
.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 = {
|
camera_ = new Esp32S3Camera(camera_config);
|
||||||
.init_sccb = false,
|
if(camera_ != nullptr)
|
||||||
.i2c_handle = i2c_bus_,
|
{
|
||||||
.freq = 100000,
|
camera_->SetVFlip(true);
|
||||||
};
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#include <esp_lcd_panel_vendor.h>
|
#include <esp_lcd_panel_vendor.h>
|
||||||
#include <driver/i2c_master.h>
|
#include <driver/i2c_master.h>
|
||||||
#include "esp32_camera.h"
|
#include "esp32s3_camera.h"
|
||||||
|
|
||||||
#define TAG "kevin-sp-v3"
|
#define TAG "kevin-sp-v3"
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ private:
|
|||||||
i2c_master_bus_handle_t display_i2c_bus_;
|
i2c_master_bus_handle_t display_i2c_bus_;
|
||||||
Button boot_button_;
|
Button boot_button_;
|
||||||
LcdDisplay* display_;
|
LcdDisplay* display_;
|
||||||
Esp32Camera* camera_;
|
Esp32S3Camera* camera_;
|
||||||
|
|
||||||
void InitializeSpi() {
|
void InitializeSpi() {
|
||||||
spi_bus_config_t buscfg = {};
|
spi_bus_config_t buscfg = {};
|
||||||
@ -84,47 +84,39 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InitializeCamera() {
|
void InitializeCamera() {
|
||||||
static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
|
// ESP32-S3 使用 esp_camera 组件
|
||||||
.data_width = CAM_CTLR_DATA_WIDTH_8,
|
camera_config_t camera_config = {
|
||||||
.data_io = {
|
.pin_pwdn = CAMERA_PIN_PWDN,
|
||||||
[0] = CAMERA_PIN_D0,
|
.pin_reset = CAMERA_PIN_RESET,
|
||||||
[1] = CAMERA_PIN_D1,
|
.pin_xclk = CAMERA_PIN_XCLK,
|
||||||
[2] = CAMERA_PIN_D2,
|
.pin_sccb_sda = -1, // 使用已初始化的 I2C
|
||||||
[3] = CAMERA_PIN_D3,
|
.pin_sccb_scl = -1,
|
||||||
[4] = CAMERA_PIN_D4,
|
.pin_d7 = CAMERA_PIN_D7,
|
||||||
[5] = CAMERA_PIN_D5,
|
.pin_d6 = CAMERA_PIN_D6,
|
||||||
[6] = CAMERA_PIN_D6,
|
.pin_d5 = CAMERA_PIN_D5,
|
||||||
[7] = CAMERA_PIN_D7,
|
.pin_d4 = CAMERA_PIN_D4,
|
||||||
},
|
.pin_d3 = CAMERA_PIN_D3,
|
||||||
.vsync_io = CAMERA_PIN_VSYNC,
|
.pin_d2 = CAMERA_PIN_D2,
|
||||||
.de_io = CAMERA_PIN_HREF,
|
.pin_d1 = CAMERA_PIN_D1,
|
||||||
.pclk_io = CAMERA_PIN_PCLK,
|
.pin_d0 = CAMERA_PIN_D0,
|
||||||
.xclk_io = CAMERA_PIN_XCLK,
|
.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 = {
|
camera_ = new Esp32S3Camera(camera_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kevin-sp-v4-dev",
|
"name": "kevin-sp-v4-dev",
|
||||||
"sdkconfig_append": [
|
"sdkconfig_append": [
|
||||||
|
"CONFIG_XIAOZHI_USE_ESP_CAMERA=y",
|
||||||
"CONFIG_CAMERA_OV2640=y",
|
"CONFIG_CAMERA_OV2640=y",
|
||||||
"CONFIG_CAMERA_OV3660=y",
|
"CONFIG_CAMERA_OV3660=y",
|
||||||
"CONFIG_CAMERA_OV3660_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
|
"CONFIG_CAMERA_OV3660_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#include <esp_lcd_panel_vendor.h>
|
#include <esp_lcd_panel_vendor.h>
|
||||||
#include <driver/i2c_master.h>
|
#include <driver/i2c_master.h>
|
||||||
#include "esp32_camera.h"
|
#include "esp32s3_camera.h"
|
||||||
|
|
||||||
#define TAG "kevin-sp-v4"
|
#define TAG "kevin-sp-v4"
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ private:
|
|||||||
Button boot_button_;
|
Button boot_button_;
|
||||||
LcdDisplay* display_;
|
LcdDisplay* display_;
|
||||||
i2c_master_bus_handle_t i2c_bus_;
|
i2c_master_bus_handle_t i2c_bus_;
|
||||||
Esp32Camera* camera_;
|
Esp32S3Camera* camera_;
|
||||||
|
|
||||||
void InitializeCodecI2c() {
|
void InitializeCodecI2c() {
|
||||||
// Initialize I2C peripheral
|
// Initialize I2C peripheral
|
||||||
@ -98,43 +98,39 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InitializeCamera() {
|
void InitializeCamera() {
|
||||||
static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
|
// ESP32-S3 使用 esp_camera 组件
|
||||||
.data_width = CAM_CTLR_DATA_WIDTH_8,
|
camera_config_t camera_config = {
|
||||||
.data_io = {
|
.pin_pwdn = CAMERA_PIN_PWDN,
|
||||||
[0] = CAMERA_PIN_D0,
|
.pin_reset = CAMERA_PIN_RESET,
|
||||||
[1] = CAMERA_PIN_D1,
|
.pin_xclk = CAMERA_PIN_XCLK,
|
||||||
[2] = CAMERA_PIN_D2,
|
.pin_sccb_sda = -1, // 使用已初始化的 I2C
|
||||||
[3] = CAMERA_PIN_D3,
|
.pin_sccb_scl = -1,
|
||||||
[4] = CAMERA_PIN_D4,
|
.pin_d7 = CAMERA_PIN_D7,
|
||||||
[5] = CAMERA_PIN_D5,
|
.pin_d6 = CAMERA_PIN_D6,
|
||||||
[6] = CAMERA_PIN_D6,
|
.pin_d5 = CAMERA_PIN_D5,
|
||||||
[7] = CAMERA_PIN_D7,
|
.pin_d4 = CAMERA_PIN_D4,
|
||||||
},
|
.pin_d3 = CAMERA_PIN_D3,
|
||||||
.vsync_io = CAMERA_PIN_VSYNC,
|
.pin_d2 = CAMERA_PIN_D2,
|
||||||
.de_io = CAMERA_PIN_HREF,
|
.pin_d1 = CAMERA_PIN_D1,
|
||||||
.pclk_io = CAMERA_PIN_PCLK,
|
.pin_d0 = CAMERA_PIN_D0,
|
||||||
.xclk_io = CAMERA_PIN_XCLK,
|
.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 = {
|
camera_ = new Esp32S3Camera(camera_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -6,20 +6,36 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||||
|
// ESP32-P4 使用 esp_video 组件提供的 V4L2 头文件
|
||||||
#include <linux/videodev2.h>
|
#include <linux/videodev2.h>
|
||||||
|
#else
|
||||||
typedef uint32_t v4l2_pix_fmt_t; // see linux/videodev2.h for details
|
// ESP32-S3 等其他芯片:定义常用的 V4L2 像素格式
|
||||||
|
#define V4L2_PIX_FMT_RGB565 0x50424752 // 'RGBP'
|
||||||
#ifdef __cplusplus
|
#define V4L2_PIX_FMT_RGB565X 0x52474250 // 'PRGB'
|
||||||
extern "C" {
|
#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
|
#endif
|
||||||
|
|
||||||
// JPEG输出回调函数类型
|
typedef uint32_t v4l2_pix_fmt_t;
|
||||||
// arg: 用户自定义参数, index: 当前数据索引, data: JPEG数据块, len: 数据块长度
|
|
||||||
// 返回: 实际处理的字节数
|
|
||||||
typedef size_t (*jpg_out_cb)(void *arg, size_t index, const void *data, size_t len);
|
|
||||||
|
|
||||||
/**
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 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
|
* @brief 将图像格式高效转换为JPEG
|
||||||
*
|
*
|
||||||
* 这个函数使用优化的JPEG编码器进行编码,主要特点:
|
* 这个函数使用优化的JPEG编码器进行编码,主要特点:
|
||||||
@ -38,10 +54,10 @@ typedef size_t (*jpg_out_cb)(void *arg, size_t index, const void *data, size_t l
|
|||||||
*
|
*
|
||||||
* @return true 成功, false 失败
|
* @return true 成功, false 失败
|
||||||
*/
|
*/
|
||||||
bool image_to_jpeg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height,
|
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);
|
v4l2_pix_fmt_t format, uint8_t quality, uint8_t **out, size_t *out_len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 将图像格式转换为JPEG(回调版本)
|
* @brief 将图像格式转换为JPEG(回调版本)
|
||||||
*
|
*
|
||||||
* 使用回调函数处理JPEG输出数据,适合流式传输或分块处理:
|
* 使用回调函数处理JPEG输出数据,适合流式传输或分块处理:
|
||||||
@ -60,7 +76,7 @@ bool image_to_jpeg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height
|
|||||||
*
|
*
|
||||||
* @return true 成功, false 失败
|
* @return true 成功, false 失败
|
||||||
*/
|
*/
|
||||||
bool image_to_jpeg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height,
|
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);
|
v4l2_pix_fmt_t format, uint8_t quality, jpg_out_cb cb, void *arg);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@ -32,10 +32,15 @@ dependencies:
|
|||||||
espressif/esp-sr: ~2.2.0
|
espressif/esp-sr: ~2.2.0
|
||||||
espressif/button: ~4.1.3
|
espressif/button: ~4.1.3
|
||||||
espressif/knob: ^1.0.0
|
espressif/knob: ^1.0.0
|
||||||
|
espressif/esp32-camera:
|
||||||
|
version: ^2.0.15
|
||||||
|
rules:
|
||||||
|
- if: target in [esp32s3]
|
||||||
espressif/esp_video:
|
espressif/esp_video:
|
||||||
version: ==1.3.1 # for compatibility. update version may need to modify this project code.
|
version: ==1.3.1 # for compatibility. update version may need to modify this project code.
|
||||||
rules:
|
rules:
|
||||||
- if: target not in [esp32]
|
- if: target in [esp32p4, esp32s3]
|
||||||
|
|
||||||
espressif/esp_image_effects:
|
espressif/esp_image_effects:
|
||||||
version: ^1.0.1
|
version: ^1.0.1
|
||||||
rules:
|
rules:
|
||||||
@ -111,5 +116,5 @@ dependencies:
|
|||||||
|
|
||||||
## Required IDF version
|
## Required IDF version
|
||||||
idf:
|
idf:
|
||||||
version: '>=5.5.2'
|
version: '>=5.5.1'
|
||||||
espressif/esp_lcd_touch_st7123: ^1.0.0
|
espressif/esp_lcd_touch_st7123: ^1.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user