Skip to content

Commit 249d12a

Browse files
committed
feat: Add camera functions (lichuang-dev)
1 parent ecfebc4 commit 249d12a

File tree

12 files changed

+472
-72
lines changed

12 files changed

+472
-72
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# CMakeLists in this exact order for cmake to work correctly
55
cmake_minimum_required(VERSION 3.16)
66

7-
set(PROJECT_VER "1.6.3")
7+
set(PROJECT_VER "1.6.4")
88

99
# Add this line to disable the specific warning
1010
add_compile_options(-Wno-missing-field-initializers)

main/boards/common/board.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ Display* Board::GetDisplay() {
5757
return &display;
5858
}
5959

60+
Camera* Board::GetCamera() {
61+
return nullptr;
62+
}
63+
6064
Led* Board::GetLed() {
6165
static NoLed led;
6266
return &led;

main/boards/common/board.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "led/led.h"
1111
#include "backlight.h"
12+
#include "camera.h"
1213

1314
void* create_board();
1415
class AudioCodec;
@@ -39,6 +40,7 @@ class Board {
3940
virtual AudioCodec* GetAudioCodec() = 0;
4041
virtual bool GetTemperature(float& esp32temp);
4142
virtual Display* GetDisplay();
43+
virtual Camera* GetCamera();
4244
virtual Http* CreateHttp() = 0;
4345
virtual WebSocket* CreateWebSocket() = 0;
4446
virtual Mqtt* CreateMqtt() = 0;

main/boards/common/camera.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef CAMERA_H
2+
#define CAMERA_H
3+
4+
#include <string>
5+
6+
class Camera {
7+
public:
8+
virtual void SetExplainUrl(const std::string& url, const std::string& token) = 0;
9+
virtual bool Capture() = 0;
10+
virtual std::string Explain(const std::string& question) = 0;
11+
};
12+
13+
#endif // CAMERA_H

main/boards/common/esp32_camera.cc

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#include "esp32_camera.h"
2+
#include "mcp_server.h"
3+
#include "display.h"
4+
#include "board.h"
5+
6+
#include <esp_log.h>
7+
#include <esp_heap_caps.h>
8+
#include <img_converters.h>
9+
#include <cstring>
10+
11+
#define TAG "Esp32Camera"
12+
13+
Esp32Camera::Esp32Camera(const camera_config_t& config) {
14+
jpeg_queue_ = xQueueCreate(10, sizeof(JpegChunk));
15+
if (jpeg_queue_ == nullptr) {
16+
ESP_LOGE(TAG, "Failed to create JPEG queue");
17+
return;
18+
}
19+
20+
// camera init
21+
esp_err_t err = esp_camera_init(&config); // 配置上面定义的参数
22+
if (err != ESP_OK) {
23+
ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
24+
return;
25+
}
26+
27+
sensor_t *s = esp_camera_sensor_get(); // 获取摄像头型号
28+
if (s->id.PID == GC0308_PID) {
29+
s->set_hmirror(s, 0); // 这里控制摄像头镜像 写1镜像 写0不镜像
30+
}
31+
32+
// 初始化预览图片的内存
33+
memset(&preview_image_, 0, sizeof(preview_image_));
34+
preview_image_.header.magic = LV_IMAGE_HEADER_MAGIC;
35+
preview_image_.header.cf = LV_COLOR_FORMAT_RGB565;
36+
preview_image_.header.flags = LV_IMAGE_FLAGS_ALLOCATED | LV_IMAGE_FLAGS_MODIFIABLE;
37+
38+
if (config.frame_size == FRAMESIZE_VGA) {
39+
preview_image_.header.w = 640;
40+
preview_image_.header.h = 480;
41+
} else if (config.frame_size == FRAMESIZE_QVGA) {
42+
preview_image_.header.w = 320;
43+
preview_image_.header.h = 240;
44+
}
45+
preview_image_.header.stride = preview_image_.header.w * 2;
46+
preview_image_.data_size = preview_image_.header.w * preview_image_.header.h * 2;
47+
preview_image_.data = (uint8_t*)heap_caps_malloc(preview_image_.data_size, MALLOC_CAP_SPIRAM);
48+
if (preview_image_.data == nullptr) {
49+
ESP_LOGE(TAG, "Failed to allocate memory for preview image");
50+
return;
51+
}
52+
}
53+
54+
Esp32Camera::~Esp32Camera() {
55+
if (fb_) {
56+
esp_camera_fb_return(fb_);
57+
fb_ = nullptr;
58+
}
59+
if (preview_image_.data) {
60+
heap_caps_free((void*)preview_image_.data);
61+
preview_image_.data = nullptr;
62+
}
63+
esp_camera_deinit();
64+
65+
if (jpeg_queue_ != nullptr) {
66+
vQueueDelete(jpeg_queue_);
67+
jpeg_queue_ = nullptr;
68+
}
69+
}
70+
71+
void Esp32Camera::SetExplainUrl(const std::string& url, const std::string& token) {
72+
explain_url_ = url;
73+
explain_token_ = token;
74+
}
75+
76+
bool Esp32Camera::Capture() {
77+
if (preview_thread_.joinable()) {
78+
preview_thread_.join();
79+
}
80+
if (encoder_thread_.joinable()) {
81+
encoder_thread_.join();
82+
}
83+
84+
int frames_to_get = 2;
85+
// Try to get a stable frame
86+
for (int i = 0; i < frames_to_get; i++) {
87+
if (fb_ != nullptr) {
88+
esp_camera_fb_return(fb_);
89+
}
90+
fb_ = esp_camera_fb_get();
91+
if (fb_ == nullptr) {
92+
ESP_LOGE(TAG, "Camera capture failed");
93+
return false;
94+
}
95+
}
96+
97+
preview_thread_ = std::thread([this]() {
98+
// 显示预览图片
99+
auto display = Board::GetInstance().GetDisplay();
100+
if (display != nullptr) {
101+
auto src = (uint16_t*)fb_->buf;
102+
auto dst = (uint16_t*)preview_image_.data;
103+
size_t pixel_count = fb_->len / 2;
104+
for (size_t i = 0; i < pixel_count; i++) {
105+
// 交换每个16位字内的字节
106+
dst[i] = __builtin_bswap16(src[i]);
107+
}
108+
display->SetPreviewImage(&preview_image_);
109+
}
110+
});
111+
return true;
112+
}
113+
114+
std::string Esp32Camera::Explain(const std::string& question) {
115+
if (explain_url_.empty() || explain_token_.empty()) {
116+
return "{\"success\": false, \"message\": \"Image explain URL or token is not set\"}";
117+
}
118+
119+
// We spawn a thread to encode the image to JPEG
120+
encoder_thread_ = std::thread([this]() {
121+
frame2jpg_cb(fb_, 80, [](void* arg, size_t index, const void* data, size_t len) -> unsigned int {
122+
auto jpeg_queue = (QueueHandle_t)arg;
123+
JpegChunk chunk = {
124+
.data = (uint8_t*)heap_caps_malloc(len, MALLOC_CAP_SPIRAM),
125+
.len = len
126+
};
127+
memcpy(chunk.data, data, len);
128+
xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
129+
return len;
130+
}, jpeg_queue_);
131+
});
132+
133+
auto http = Board::GetInstance().CreateHttp();
134+
// 构造multipart/form-data请求体
135+
std::string boundary = "----ESP32_CAMERA_BOUNDARY";
136+
137+
// 构造question字段
138+
std::string question_field;
139+
question_field += "--" + boundary + "\r\n";
140+
question_field += "Content-Disposition: form-data; name=\"question\"\r\n";
141+
question_field += "\r\n";
142+
question_field += question + "\r\n";
143+
144+
// 构造文件字段头部
145+
std::string file_header;
146+
file_header += "--" + boundary + "\r\n";
147+
file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n";
148+
file_header += "Content-Type: image/jpeg\r\n";
149+
file_header += "\r\n";
150+
151+
// 构造尾部
152+
std::string multipart_footer;
153+
multipart_footer += "\r\n--" + boundary + "--\r\n";
154+
155+
// 配置HTTP客户端,使用分块传输编码
156+
http->SetHeader("Authorization", "Bearer " + explain_token_);
157+
http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
158+
http->SetHeader("Transfer-Encoding", "chunked");
159+
if (!http->Open("POST", explain_url_)) {
160+
ESP_LOGE(TAG, "Failed to connect to explain URL");
161+
// Clear the queue
162+
encoder_thread_.join();
163+
JpegChunk chunk;
164+
while (xQueueReceive(jpeg_queue_, &chunk, portMAX_DELAY) == pdPASS) {
165+
if (chunk.data != nullptr) {
166+
heap_caps_free(chunk.data);
167+
} else {
168+
break;
169+
}
170+
}
171+
return "{\"success\": false, \"message\": \"Failed to connect to explain URL\"}";
172+
}
173+
174+
// 第一块:question字段
175+
http->Write(question_field.c_str(), question_field.size());
176+
177+
// 第二块:文件字段头部
178+
http->Write(file_header.c_str(), file_header.size());
179+
180+
// 第三块:JPEG数据
181+
size_t total_sent = 0;
182+
while (true) {
183+
JpegChunk chunk;
184+
if (xQueueReceive(jpeg_queue_, &chunk, portMAX_DELAY) != pdPASS) {
185+
ESP_LOGE(TAG, "Failed to receive JPEG chunk");
186+
break;
187+
}
188+
if (chunk.data == nullptr) {
189+
break; // The last chunk
190+
}
191+
http->Write((const char*)chunk.data, chunk.len);
192+
total_sent += chunk.len;
193+
heap_caps_free(chunk.data);
194+
}
195+
196+
// 第四块:multipart尾部
197+
http->Write(multipart_footer.c_str(), multipart_footer.size());
198+
199+
// 结束块
200+
http->Write("", 0);
201+
202+
if (http->GetStatusCode() != 200) {
203+
ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode());
204+
return "{\"success\": false, \"message\": \"Failed to upload photo\"}";
205+
}
206+
207+
std::string result = http->ReadAll();
208+
http->Close();
209+
210+
ESP_LOGI(TAG, "Explain image size=%dx%d, compressed size=%d, question=%s\n%s", fb_->width, fb_->height, total_sent, question.c_str(), result.c_str());
211+
return result;
212+
}

main/boards/common/esp32_camera.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#ifndef ESP32_CAMERA_H
2+
#define ESP32_CAMERA_H
3+
4+
#include <esp_camera.h>
5+
#include <lvgl.h>
6+
#include <thread>
7+
#include <memory>
8+
9+
#include <freertos/FreeRTOS.h>
10+
#include <freertos/queue.h>
11+
12+
#include "camera.h"
13+
14+
struct JpegChunk {
15+
uint8_t* data;
16+
size_t len;
17+
};
18+
19+
class Esp32Camera : public Camera {
20+
private:
21+
camera_fb_t* fb_ = nullptr;
22+
lv_img_dsc_t preview_image_;
23+
std::string explain_url_;
24+
std::string explain_token_;
25+
std::thread preview_thread_;
26+
std::thread encoder_thread_;
27+
28+
QueueHandle_t jpeg_queue_ = nullptr;
29+
public:
30+
Esp32Camera(const camera_config_t& config);
31+
~Esp32Camera();
32+
33+
virtual void SetExplainUrl(const std::string& url, const std::string& token);
34+
virtual bool Capture();
35+
virtual std::string Explain(const std::string& question);
36+
};
37+
38+
#endif // ESP32_CAMERA_H

main/boards/lichuang-dev/config.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,26 @@
3737
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
3838
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
3939

40+
/* Camera pins */
41+
#define CAMERA_PIN_PWDN -1
42+
#define CAMERA_PIN_RESET -1
43+
#define CAMERA_PIN_XCLK 5
44+
#define CAMERA_PIN_SIOD 1
45+
#define CAMERA_PIN_SIOC 2
46+
47+
#define CAMERA_PIN_D7 9
48+
#define CAMERA_PIN_D6 4
49+
#define CAMERA_PIN_D5 6
50+
#define CAMERA_PIN_D4 15
51+
#define CAMERA_PIN_D3 17
52+
#define CAMERA_PIN_D2 8
53+
#define CAMERA_PIN_D1 18
54+
#define CAMERA_PIN_D0 16
55+
#define CAMERA_PIN_VSYNC 3
56+
#define CAMERA_PIN_HREF 46
57+
#define CAMERA_PIN_PCLK 7
58+
59+
#define XCLK_FREQ_HZ 24000000
60+
4061

4162
#endif // _BOARD_CONFIG_H_

0 commit comments

Comments
 (0)