forked from xiaozhi/xiaozhi-esp32
fix: avoid SRAM overflow in software JPEG encode (#1441)
This commit is contained in:
@@ -1,19 +1,21 @@
|
||||
#include "esp32_camera.h"
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/param.h>
|
||||
#include <unistd.h>
|
||||
#include "board.h"
|
||||
#include "display.h"
|
||||
|
||||
#include "esp_imgfx_color_convert.h"
|
||||
#include "esp_video_device.h"
|
||||
#include "esp_video_init.h"
|
||||
#include "jpg/image_to_jpeg.h"
|
||||
#include "linux/videodev2.h"
|
||||
|
||||
#include "esp32_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"
|
||||
|
||||
#ifdef CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
|
||||
#undef LOG_LOCAL_LEVEL
|
||||
@@ -193,12 +195,9 @@ Esp32Camera::Esp32Camera(const esp_video_init_config_t& config) {
|
||||
return 0;
|
||||
case V4L2_PIX_FMT_RGB565:
|
||||
return 1;
|
||||
case V4L2_PIX_FMT_YUV420:
|
||||
#ifdef CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
|
||||
case V4L2_PIX_FMT_YUV420: // 软件 JPEG 编码器不支持 YUV420 格式
|
||||
return 2;
|
||||
#else
|
||||
// 软件 JPEG 编码器不支持 YUV420 格式
|
||||
[[fallthrough]];
|
||||
#endif // CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
|
||||
case V4L2_PIX_FMT_GREY:
|
||||
case V4L2_PIX_FMT_YUV422P:
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
#include <esp_log.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <utility>
|
||||
|
||||
#include "esp_jpeg_common.h"
|
||||
#include "esp_jpeg_enc.h"
|
||||
#include "esp_imgfx_color_convert.h"
|
||||
|
||||
#if CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
|
||||
#include "driver/jpeg_encode.h"
|
||||
#endif
|
||||
@@ -34,7 +37,7 @@ static __always_inline uint8_t expand_6_to_8(uint8_t v) {
|
||||
|
||||
static uint8_t* convert_input_to_encoder_buf(const uint8_t* src, uint16_t width, uint16_t height, v4l2_pix_fmt_t format,
|
||||
jpeg_pixel_format_t* out_fmt, int* out_size) {
|
||||
// 直接支持的格式:GRAY、RGB888、YCbYCr(YUYV)
|
||||
// GRAY 直接作为 JPEG_PIXEL_FORMAT_GRAY 输入
|
||||
if (format == V4L2_PIX_FMT_GREY) {
|
||||
int sz = (int)width * (int)height;
|
||||
uint8_t* buf = (uint8_t*)jpeg_calloc_align(sz, 16);
|
||||
@@ -63,7 +66,8 @@ static uint8_t* convert_input_to_encoder_buf(const uint8_t* src, uint16_t width,
|
||||
}
|
||||
|
||||
// V4L2 UYVY (Cb Y Cr Y) -> 重排为 YUYV 再作为 YCbYCr 输入
|
||||
if (format == V4L2_PIX_FMT_UYVY) {
|
||||
// 当前版本暂时不会出现 UYVY 格式
|
||||
if (format == V4L2_PIX_FMT_UYVY) [[unlikely]] {
|
||||
int sz = (int)width * (int)height * 2;
|
||||
const uint8_t* s = src;
|
||||
uint8_t* buf = (uint8_t*)jpeg_calloc_align(sz, 16);
|
||||
@@ -87,7 +91,8 @@ static uint8_t* convert_input_to_encoder_buf(const uint8_t* src, uint16_t width,
|
||||
}
|
||||
|
||||
// V4L2 YUV422P (YUV422 Planar) -> 重排为 YUYV (YCbYCr)
|
||||
if (format == V4L2_PIX_FMT_YUV422P) {
|
||||
// 当前版本暂时不会出现 YUV422P 格式
|
||||
if (format == V4L2_PIX_FMT_YUV422P) [[unlikely]] {
|
||||
int sz = (int)width * (int)height * 2;
|
||||
const uint8_t* y_plane = src;
|
||||
const uint8_t* u_plane = y_plane + (int)width * (int)height;
|
||||
@@ -119,44 +124,72 @@ static uint8_t* convert_input_to_encoder_buf(const uint8_t* src, uint16_t width,
|
||||
return buf;
|
||||
}
|
||||
|
||||
// 其余格式转换为 RGB888
|
||||
int rgb_size = (int)width * (int)height * 3;
|
||||
uint8_t* rgb = (uint8_t*)jpeg_calloc_align(rgb_size, 16);
|
||||
if (!rgb)
|
||||
return NULL;
|
||||
|
||||
if (format == V4L2_PIX_FMT_RGB24) {
|
||||
// V4L2_RGB24 即 RGB888
|
||||
memcpy(rgb, src, rgb_size);
|
||||
} else if (format == V4L2_PIX_FMT_RGB565) {
|
||||
// RGB565 小端,需要转换为 RGB888
|
||||
const uint8_t* p = src;
|
||||
uint8_t* d = rgb;
|
||||
int pixels = (int)width * (int)height;
|
||||
for (int i = 0; i < pixels; i++) {
|
||||
uint8_t lo = p[0]; // 低字节(LSB)
|
||||
uint8_t hi = p[1]; // 高字节(MSB)
|
||||
p += 2;
|
||||
|
||||
uint8_t r5 = (hi >> 3) & 0x1F;
|
||||
uint8_t g6 = ((hi & 0x07) << 3) | ((lo & 0xE0) >> 5);
|
||||
uint8_t b5 = lo & 0x1F;
|
||||
|
||||
d[0] = expand_5_to_8(r5);
|
||||
d[1] = expand_6_to_8(g6);
|
||||
d[2] = expand_5_to_8(b5);
|
||||
d += 3;
|
||||
// RGB 转换为 YUV422 (YCbYCr) 再输入
|
||||
// 见 https://github.com/78/xiaozhi-esp32/issues/1380#issuecomment-3497156378
|
||||
else if (format == V4L2_PIX_FMT_RGB24 || format == V4L2_PIX_FMT_RGB565 || format == V4L2_PIX_FMT_RGB565X) {
|
||||
esp_imgfx_pixel_fmt_t in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB888;
|
||||
uint32_t src_len = 0;
|
||||
switch (format) {
|
||||
case V4L2_PIX_FMT_RGB24:
|
||||
in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB888;
|
||||
src_len = static_cast<uint32_t>(width * height * 3);
|
||||
break;
|
||||
case V4L2_PIX_FMT_RGB565:
|
||||
in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB565_LE;
|
||||
src_len = static_cast<uint32_t>(width * height * 2);
|
||||
break;
|
||||
[[unlikely]] case V4L2_PIX_FMT_RGB565X: // 当前版本暂时不会出现 RGB565X
|
||||
in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB565_BE;
|
||||
src_len = static_cast<uint32_t>(width * height * 2);
|
||||
break;
|
||||
[[unlikely]] default:
|
||||
ESP_LOGE(TAG, "[Unreachable Case] unsupported format: 0x%08x", format);
|
||||
std::unreachable();
|
||||
}
|
||||
} else {
|
||||
// 其他未覆盖格式,清零
|
||||
memset(rgb, 0, rgb_size);
|
||||
int sz = (int)width * (int)height * 2;
|
||||
uint8_t* buf = (uint8_t*)jpeg_calloc_align(sz, 16);
|
||||
if (!buf)
|
||||
return nullptr;
|
||||
esp_imgfx_color_convert_cfg_t convert_cfg = {
|
||||
.in_res = {.width = static_cast<int16_t>(width),
|
||||
.height = static_cast<int16_t>(height)},
|
||||
.in_pixel_fmt = in_pixel_fmt,
|
||||
.out_pixel_fmt = ESP_IMGFX_PIXEL_FMT_YUYV,
|
||||
.color_space_std = ESP_IMGFX_COLOR_SPACE_STD_BT601,
|
||||
};
|
||||
esp_imgfx_color_convert_handle_t convert_handle = nullptr;
|
||||
esp_imgfx_err_t err = esp_imgfx_color_convert_open(&convert_cfg, &convert_handle);
|
||||
if (err != ESP_IMGFX_ERR_OK || convert_handle == nullptr) {
|
||||
ESP_LOGE(TAG, "esp_imgfx_color_convert_open failed");
|
||||
jpeg_free_align(buf);
|
||||
return nullptr;
|
||||
}
|
||||
esp_imgfx_data_t convert_input_data = {
|
||||
.data = const_cast<uint8_t*>(src),
|
||||
.data_len = static_cast<uint32_t>(src_len),
|
||||
};
|
||||
esp_imgfx_data_t convert_output_data = {
|
||||
.data = buf,
|
||||
.data_len = static_cast<uint32_t>(sz),
|
||||
};
|
||||
err = esp_imgfx_color_convert_process(convert_handle, &convert_input_data, &convert_output_data);
|
||||
if (err != ESP_IMGFX_ERR_OK) {
|
||||
ESP_LOGE(TAG, "esp_imgfx_color_convert_process failed");
|
||||
jpeg_free_align(buf);
|
||||
return nullptr;
|
||||
}
|
||||
esp_imgfx_color_convert_close(convert_handle);
|
||||
convert_handle = nullptr;
|
||||
if (out_fmt)
|
||||
*out_fmt = JPEG_PIXEL_FORMAT_YCbYCr;
|
||||
if (out_size)
|
||||
*out_size = sz;
|
||||
return buf;
|
||||
}
|
||||
|
||||
if (out_fmt)
|
||||
*out_fmt = JPEG_PIXEL_FORMAT_RGB888;
|
||||
ESP_LOGE(TAG, "unsupported format: 0x%08x", format);
|
||||
if (out_size)
|
||||
*out_size = rgb_size;
|
||||
return rgb;
|
||||
*out_size = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
|
||||
|
||||
Reference in New Issue
Block a user