forked from xiaozhi/xiaozhi-esp32
camera 优化:在原有的RGB565处理下,容易超时改为JPEG格式 (#1029)
* camera 优化 feat(camera): 修改摄像头配置为JPEG格式并优化图像处理逻辑 将摄像头输出格式从RGB565改为JPEG以提高传输效率,同时调整JPEG质量为10 重构预览图像处理逻辑,支持直接处理JPEG格式并自动转换为RGB565 优化Explain方法中的JPEG队列处理,减少内存分配和拷贝操作 * 修复代码缩进 调整代码缩进格式以提升可读性 * fix(esp32_camera): 修复RGB565格式预览图像的字节序问题 添加字节交换处理,将大端序转换为小端序,确保预览图像显示正确 * 使用旧的处理方式 * refactor(esp32_camera): 移除preview_buffer_直接使用preview_image_.data
This commit is contained in:
@@ -65,6 +65,7 @@ Esp32Camera::Esp32Camera(const camera_config_t& config) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for preview image");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Esp32Camera::~Esp32Camera() {
|
||||
@@ -115,14 +116,27 @@ bool Esp32Camera::Capture() {
|
||||
// 显示预览图片
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
if (display != nullptr) {
|
||||
auto src = (uint16_t*)fb_->buf;
|
||||
auto dst = (uint16_t*)preview_image_.data;
|
||||
size_t pixel_count = fb_->len / 2;
|
||||
for (size_t i = 0; i < pixel_count; i++) {
|
||||
// 交换每个16位字内的字节
|
||||
dst[i] = __builtin_bswap16(src[i]);
|
||||
// 检查摄像头输出格式
|
||||
if (fb_->format == PIXFORMAT_JPEG) {
|
||||
// JPEG格式需要先解码为RGB565
|
||||
// 分配足够的缓冲区用于RGB565数据
|
||||
bool converted = jpg2rgb565(fb_->buf, fb_->len, preview_image_.data, JPG_SCALE_NONE);
|
||||
if (converted) {
|
||||
display->SetPreviewImage(&preview_image_);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to convert JPEG to RGB565 for preview");
|
||||
}
|
||||
} else {
|
||||
// RGB565等格式需要进行字节交换处理
|
||||
auto src = (uint16_t*)fb_->buf;
|
||||
auto dst = (uint16_t*)preview_image_.data;
|
||||
size_t pixel_count = fb_->len / 2;
|
||||
for (size_t i = 0; i < pixel_count; i++) {
|
||||
// 交换每个16位字内的字节
|
||||
dst[i] = __builtin_bswap16(src[i]);
|
||||
}
|
||||
display->SetPreviewImage(&preview_image_);
|
||||
}
|
||||
display->SetPreviewImage(&preview_image_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -188,26 +202,56 @@ std::string Esp32Camera::Explain(const std::string& question) {
|
||||
return "{\"success\": false, \"message\": \"Image explain URL or token is not set\"}";
|
||||
}
|
||||
|
||||
// 创建局部的 JPEG 队列, 40 entries is about to store 512 * 40 = 20480 bytes of JPEG data
|
||||
QueueHandle_t jpeg_queue = xQueueCreate(40, sizeof(JpegChunk));
|
||||
if (jpeg_queue == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create JPEG queue");
|
||||
return "{\"success\": false, \"message\": \"Failed to create JPEG queue\"}";
|
||||
}
|
||||
QueueHandle_t jpeg_queue = nullptr;
|
||||
// If the format is not JPEG, we need to convert it to JPEG first
|
||||
if (fb_->format != PIXFORMAT_JPEG) {
|
||||
// 创建局部的 JPEG 队列, 40 entries is about to store 512 * 40 = 20480 bytes of JPEG data
|
||||
jpeg_queue = xQueueCreate(40, sizeof(JpegChunk));
|
||||
if (jpeg_queue == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create JPEG queue");
|
||||
return "{\"success\": false, \"message\": \"Failed to create JPEG queue\"}";
|
||||
}
|
||||
|
||||
// We spawn a thread to encode the image to JPEG
|
||||
encoder_thread_ = std::thread([this, jpeg_queue]() {
|
||||
frame2jpg_cb(fb_, 80, [](void* arg, size_t index, const void* data, size_t len) -> unsigned int {
|
||||
auto jpeg_queue = (QueueHandle_t)arg;
|
||||
JpegChunk chunk = {
|
||||
.data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM),
|
||||
.len = len
|
||||
};
|
||||
memcpy(chunk.data, data, len);
|
||||
// We spawn a thread to encode the image to JPEG
|
||||
encoder_thread_ = std::thread([this, jpeg_queue]() {
|
||||
frame2jpg_cb(fb_, 80, [](void* arg, size_t index, const void* data, size_t len) -> unsigned int {
|
||||
auto jpeg_queue = (QueueHandle_t)arg;
|
||||
JpegChunk chunk = {
|
||||
.data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM),
|
||||
.len = len
|
||||
};
|
||||
if (chunk.data != nullptr) {
|
||||
memcpy(chunk.data, data, len);
|
||||
xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
|
||||
}
|
||||
return len;
|
||||
}, jpeg_queue);
|
||||
// Add a null chunk to indicate the end of the queue
|
||||
JpegChunk null_chunk = { .data = nullptr, .len = 0 };
|
||||
xQueueSend(jpeg_queue, &null_chunk, portMAX_DELAY);
|
||||
});
|
||||
} else {
|
||||
// JPEG format, we can send it directly
|
||||
// To keep the logic consistent, we still use a queue to send the data
|
||||
jpeg_queue = xQueueCreate(2, sizeof(JpegChunk));
|
||||
if (jpeg_queue == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create JPEG queue");
|
||||
return "{\"success\": false, \"message\": \"Failed to create JPEG queue\"}";
|
||||
}
|
||||
JpegChunk chunk = {
|
||||
.data = (uint8_t*)heap_caps_aligned_alloc(16, fb_->len, MALLOC_CAP_SPIRAM),
|
||||
.len = fb_->len
|
||||
};
|
||||
if (chunk.data != nullptr) {
|
||||
memcpy(chunk.data, fb_->buf, fb_->len);
|
||||
xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
|
||||
return len;
|
||||
}, jpeg_queue);
|
||||
});
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for JPEG chunk");
|
||||
}
|
||||
// Add a null chunk to indicate the end of the queue
|
||||
JpegChunk null_chunk = { .data = nullptr, .len = 0 };
|
||||
xQueueSend(jpeg_queue, &null_chunk, portMAX_DELAY);
|
||||
}
|
||||
|
||||
auto network = Board::GetInstance().GetNetwork();
|
||||
auto http = network->CreateHttp(3);
|
||||
@@ -225,7 +269,9 @@ std::string Esp32Camera::Explain(const std::string& question) {
|
||||
if (!http->Open("POST", explain_url_)) {
|
||||
ESP_LOGE(TAG, "Failed to connect to explain URL");
|
||||
// Clear the queue
|
||||
encoder_thread_.join();
|
||||
if (encoder_thread_.joinable()) {
|
||||
encoder_thread_.join();
|
||||
}
|
||||
JpegChunk chunk;
|
||||
while (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) == pdPASS) {
|
||||
if (chunk.data != nullptr) {
|
||||
@@ -273,7 +319,9 @@ std::string Esp32Camera::Explain(const std::string& question) {
|
||||
heap_caps_free(chunk.data);
|
||||
}
|
||||
// Wait for the encoder thread to finish
|
||||
encoder_thread_.join();
|
||||
if (encoder_thread_.joinable()) {
|
||||
encoder_thread_.join();
|
||||
}
|
||||
// 清理队列
|
||||
vQueueDelete(jpeg_queue);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user