sscma_camera.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. #include "sscma_camera.h"
  2. #include "mcp_server.h"
  3. #include "display.h"
  4. #include "board.h"
  5. #include "system_info.h"
  6. #include "config.h"
  7. #include <esp_log.h>
  8. #include <esp_heap_caps.h>
  9. #include <img_converters.h>
  10. #include <cstring>
  11. #define TAG "SscmaCamera"
  12. #define IMG_JPEG_BUF_SIZE 48 * 1024
  13. SscmaCamera::SscmaCamera(esp_io_expander_handle_t io_exp_handle) {
  14. sscma_client_io_spi_config_t spi_io_config = {0};
  15. spi_io_config.sync_gpio_num = BSP_SSCMA_CLIENT_SPI_SYNC;
  16. spi_io_config.cs_gpio_num = BSP_SSCMA_CLIENT_SPI_CS;
  17. spi_io_config.pclk_hz = BSP_SSCMA_CLIENT_SPI_CLK;
  18. spi_io_config.spi_mode = 0;
  19. spi_io_config.wait_delay = 10; //两个transfer之间至少延时4ms,但当前 FREERTOS_HZ=100, 延时精度只能达到10ms,
  20. spi_io_config.user_ctx = NULL;
  21. spi_io_config.io_expander = io_exp_handle;
  22. spi_io_config.flags.sync_use_expander = BSP_SSCMA_CLIENT_RST_USE_EXPANDER;
  23. sscma_client_new_io_spi_bus((sscma_client_spi_bus_handle_t)BSP_SSCMA_CLIENT_SPI_NUM, &spi_io_config, &sscma_client_io_handle_);
  24. sscma_client_config_t sscma_client_config = SSCMA_CLIENT_CONFIG_DEFAULT();
  25. sscma_client_config.event_queue_size = CONFIG_SSCMA_EVENT_QUEUE_SIZE;
  26. sscma_client_config.tx_buffer_size = CONFIG_SSCMA_TX_BUFFER_SIZE;
  27. sscma_client_config.rx_buffer_size = CONFIG_SSCMA_RX_BUFFER_SIZE;
  28. sscma_client_config.process_task_stack = CONFIG_SSCMA_PROCESS_TASK_STACK_SIZE;
  29. sscma_client_config.process_task_affinity = CONFIG_SSCMA_PROCESS_TASK_AFFINITY;
  30. sscma_client_config.process_task_priority = CONFIG_SSCMA_PROCESS_TASK_PRIORITY;
  31. sscma_client_config.monitor_task_stack = CONFIG_SSCMA_MONITOR_TASK_STACK_SIZE;
  32. sscma_client_config.monitor_task_affinity = CONFIG_SSCMA_MONITOR_TASK_AFFINITY;
  33. sscma_client_config.monitor_task_priority = CONFIG_SSCMA_MONITOR_TASK_PRIORITY;
  34. sscma_client_config.reset_gpio_num = BSP_SSCMA_CLIENT_RST;
  35. sscma_client_config.io_expander = io_exp_handle;
  36. sscma_client_config.flags.reset_use_expander = BSP_SSCMA_CLIENT_RST_USE_EXPANDER;
  37. sscma_client_new(sscma_client_io_handle_, &sscma_client_config, &sscma_client_handle_);
  38. sscma_data_queue_ = xQueueCreate(1, sizeof(SscmaData));
  39. sscma_client_callback_t callback = {0};
  40. callback.on_event = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) {
  41. SscmaCamera* self = static_cast<SscmaCamera*>(user_ctx);
  42. if (!self) return;
  43. char *img = NULL;
  44. int img_size = 0;
  45. if (sscma_utils_fetch_image_from_reply(reply, &img, &img_size) == ESP_OK)
  46. {
  47. ESP_LOGI(TAG, "image_size: %d\n", img_size);
  48. // 将数据通过队列发送出去
  49. SscmaData data;
  50. data.img = (uint8_t*)img;
  51. data.len = img_size;
  52. // 清空队列,保证只保存最新的数据
  53. SscmaData dummy;
  54. while (xQueueReceive(self->sscma_data_queue_, &dummy, 0) == pdPASS) {
  55. if (dummy.img) {
  56. heap_caps_free(dummy.img);
  57. }
  58. }
  59. xQueueSend(self->sscma_data_queue_, &data, 0);
  60. // 注意:img 的释放由接收方负责
  61. }
  62. };
  63. callback.on_connect = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) {
  64. ESP_LOGI(TAG, "SSCMA client connected");
  65. };
  66. callback.on_log = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) {
  67. ESP_LOGI(TAG, "log: %s\n", reply->data);
  68. };
  69. sscma_client_register_callback(sscma_client_handle_, &callback, this);
  70. sscma_client_init(sscma_client_handle_);
  71. ESP_LOGI(TAG, "SSCMA client initialized");
  72. // 设置分辨率
  73. // 3 = 640x480
  74. if (sscma_client_set_sensor(sscma_client_handle_, 1, 3, true)) {
  75. ESP_LOGE(TAG, "Failed to set sensor");
  76. sscma_client_del(sscma_client_handle_);
  77. sscma_client_handle_ = NULL;
  78. return;
  79. }
  80. // 获取设备信息
  81. sscma_client_info_t *info;
  82. if (sscma_client_get_info(sscma_client_handle_, &info, true) == ESP_OK) {
  83. ESP_LOGI(TAG, "Device Info - ID: %s, Name: %s",
  84. info->id ? info->id : "NULL",
  85. info->name ? info->name : "NULL");
  86. }
  87. // 初始化JPEG数据的内存
  88. jpeg_data_.len = 0;
  89. jpeg_data_.buf = (uint8_t*)heap_caps_malloc(IMG_JPEG_BUF_SIZE, MALLOC_CAP_SPIRAM);;
  90. if ( jpeg_data_.buf == nullptr ) {
  91. ESP_LOGE(TAG, "Failed to allocate memory for JPEG buffer");
  92. return;
  93. }
  94. //初始化JPEG解码
  95. jpeg_dec_config_t config = { .output_type = JPEG_RAW_TYPE_RGB565_LE, .rotate = JPEG_ROTATE_0D };
  96. jpeg_dec_ = jpeg_dec_open(&config);
  97. if (!jpeg_dec_) {
  98. ESP_LOGE(TAG, "Failed to open JPEG decoder");
  99. return;
  100. }
  101. jpeg_io_ = (jpeg_dec_io_t*)heap_caps_malloc(sizeof(jpeg_dec_io_t), MALLOC_CAP_SPIRAM);
  102. if (!jpeg_io_) {
  103. ESP_LOGE(TAG, "Failed to allocate memory for JPEG IO");
  104. jpeg_dec_close(jpeg_dec_);
  105. return;
  106. }
  107. memset(jpeg_io_, 0, sizeof(jpeg_dec_io_t));
  108. jpeg_out_ = (jpeg_dec_header_info_t*)heap_caps_aligned_alloc(16, sizeof(jpeg_dec_header_info_t), MALLOC_CAP_SPIRAM);
  109. if (!jpeg_out_) {
  110. ESP_LOGE(TAG, "Failed to allocate memory for JPEG output header");
  111. heap_caps_free(jpeg_io_);
  112. jpeg_dec_close(jpeg_dec_);
  113. return;
  114. }
  115. memset(jpeg_out_, 0, sizeof(jpeg_dec_header_info_t));
  116. // 初始化预览图片的内存
  117. memset(&preview_image_, 0, sizeof(preview_image_));
  118. preview_image_.header.magic = LV_IMAGE_HEADER_MAGIC;
  119. preview_image_.header.cf = LV_COLOR_FORMAT_RGB565;
  120. preview_image_.header.flags = LV_IMAGE_FLAGS_ALLOCATED | LV_IMAGE_FLAGS_MODIFIABLE;
  121. preview_image_.header.w = 640;
  122. preview_image_.header.h = 480;
  123. preview_image_.header.stride = preview_image_.header.w * 2;
  124. preview_image_.data_size = preview_image_.header.w * preview_image_.header.h * 2;
  125. preview_image_.data = (uint8_t*)heap_caps_malloc(preview_image_.data_size, MALLOC_CAP_SPIRAM);
  126. if (preview_image_.data == nullptr) {
  127. ESP_LOGE(TAG, "Failed to allocate memory for preview image");
  128. return;
  129. }
  130. }
  131. SscmaCamera::~SscmaCamera() {
  132. if (preview_image_.data) {
  133. heap_caps_free((void*)preview_image_.data);
  134. preview_image_.data = nullptr;
  135. }
  136. if (sscma_client_handle_) {
  137. sscma_client_del(sscma_client_handle_);
  138. }
  139. if (sscma_data_queue_) {
  140. vQueueDelete(sscma_data_queue_);
  141. }
  142. if (jpeg_data_.buf) {
  143. heap_caps_free(jpeg_data_.buf);
  144. jpeg_data_.buf = nullptr;
  145. }
  146. if (jpeg_dec_) {
  147. jpeg_dec_close(jpeg_dec_);
  148. jpeg_dec_ = nullptr;
  149. }
  150. if (jpeg_io_) {
  151. heap_caps_free(jpeg_io_);
  152. jpeg_io_ = nullptr;
  153. }
  154. if (jpeg_out_) {
  155. heap_caps_free(jpeg_out_);
  156. jpeg_out_ = nullptr;
  157. }
  158. }
  159. void SscmaCamera::SetExplainUrl(const std::string& url, const std::string& token) {
  160. explain_url_ = url;
  161. explain_token_ = token;
  162. }
  163. bool SscmaCamera::Capture() {
  164. SscmaData data;
  165. int ret = 0;
  166. if (sscma_client_handle_ == nullptr) {
  167. ESP_LOGE(TAG, "SSCMA client handle is not initialized");
  168. return false;
  169. }
  170. ESP_LOGI(TAG, "Capturing image...");
  171. // himax 有缓存数据,需要拍两张照片, 只获取最新的照片即可.
  172. if (sscma_client_sample(sscma_client_handle_, 2) ) {
  173. ESP_LOGE(TAG, "Failed to capture image from SSCMA client");
  174. return false;
  175. }
  176. vTaskDelay(pdMS_TO_TICKS(500)); // 等待SSCMA客户端处理数据
  177. if (xQueueReceive(sscma_data_queue_, &data, pdMS_TO_TICKS(1000)) != pdPASS) {
  178. ESP_LOGE(TAG, "Failed to receive JPEG data from SSCMA client");
  179. return false;
  180. }
  181. if (jpeg_data_.buf == nullptr) {
  182. heap_caps_free(data.img);
  183. return false;
  184. }
  185. ret = mbedtls_base64_decode(jpeg_data_.buf, IMG_JPEG_BUF_SIZE, &jpeg_data_.len, data.img, data.len);
  186. if (ret != 0 || jpeg_data_.len == 0) {
  187. ESP_LOGE(TAG, "Failed to decode base64 image data, ret: %d, output_len: %zu", ret, jpeg_data_.len);
  188. heap_caps_free(data.img);
  189. return false;
  190. }
  191. heap_caps_free(data.img);
  192. //DECODE JPEG
  193. if (!jpeg_dec_ || !jpeg_io_ || !jpeg_out_ || !preview_image_.data) {
  194. return true;
  195. }
  196. jpeg_io_->inbuf = jpeg_data_.buf;
  197. jpeg_io_->inbuf_len = jpeg_data_.len;
  198. ret = jpeg_dec_parse_header(jpeg_dec_, jpeg_io_, jpeg_out_);
  199. if (ret < 0) {
  200. ESP_LOGE(TAG, "Failed to parse JPEG header, ret: %d", ret);
  201. return true;
  202. }
  203. jpeg_io_->outbuf = (unsigned char*)preview_image_.data;
  204. int inbuf_consumed = jpeg_io_->inbuf_len - jpeg_io_->inbuf_remain;
  205. jpeg_io_->inbuf = jpeg_data_.buf + inbuf_consumed;
  206. jpeg_io_->inbuf_len = jpeg_io_->inbuf_remain;
  207. ret = jpeg_dec_process(jpeg_dec_, jpeg_io_);
  208. if (ret != ESP_OK) {
  209. ESP_LOGE(TAG, "Failed to decode JPEG image, ret: %d", ret);
  210. return true;
  211. }
  212. // 显示预览图片
  213. auto display = Board::GetInstance().GetDisplay();
  214. if (display != nullptr) {
  215. display->SetPreviewImage(&preview_image_);
  216. }
  217. return true;
  218. }
  219. bool SscmaCamera::SetHMirror(bool enabled) {
  220. return false;
  221. }
  222. bool SscmaCamera::SetVFlip(bool enabled) {
  223. return false;
  224. }
  225. /**
  226. * @brief 将摄像头捕获的图像发送到远程服务器进行AI分析和解释
  227. *
  228. * 该函数将当前摄像头缓冲区中的图像编码为JPEG格式,并通过HTTP POST请求
  229. * 以multipart/form-data的形式发送到指定的解释服务器。服务器将根据提供的
  230. * 问题对图像进行AI分析并返回结果。
  231. *
  232. * @param question 要向AI提出的关于图像的问题,将作为表单字段发送
  233. * @return std::string 服务器返回的JSON格式响应字符串
  234. * 成功时包含AI分析结果,失败时包含错误信息
  235. * 格式示例:{"success": true, "result": "分析结果"}
  236. * {"success": false, "message": "错误信息"}
  237. *
  238. * @note 调用此函数前必须先调用SetExplainUrl()设置服务器URL
  239. * @note 函数会等待之前的编码线程完成后再开始新的处理
  240. * @warning 如果摄像头缓冲区为空或网络连接失败,将返回错误信息
  241. */
  242. std::string SscmaCamera::Explain(const std::string& question) {
  243. if (explain_url_.empty()) {
  244. return "{\"success\": false, \"message\": \"Image explain URL or token is not set\"}";
  245. }
  246. auto network = Board::GetInstance().GetNetwork();
  247. auto http = network->CreateHttp(3);
  248. // 构造multipart/form-data请求体
  249. std::string boundary = "----ESP32_CAMERA_BOUNDARY";
  250. // 构造question字段
  251. std::string question_field;
  252. question_field += "--" + boundary + "\r\n";
  253. question_field += "Content-Disposition: form-data; name=\"question\"\r\n";
  254. question_field += "\r\n";
  255. question_field += question + "\r\n";
  256. // 构造文件字段头部
  257. std::string file_header;
  258. file_header += "--" + boundary + "\r\n";
  259. file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n";
  260. file_header += "Content-Type: image/jpeg\r\n";
  261. file_header += "\r\n";
  262. // 构造尾部
  263. std::string multipart_footer;
  264. multipart_footer += "\r\n--" + boundary + "--\r\n";
  265. // 配置HTTP客户端,使用分块传输编码
  266. http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
  267. http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str());
  268. if (!explain_token_.empty()) {
  269. http->SetHeader("Authorization", "Bearer " + explain_token_);
  270. }
  271. http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
  272. http->SetHeader("Transfer-Encoding", "chunked");
  273. if (!http->Open("POST", explain_url_)) {
  274. ESP_LOGE(TAG, "Failed to connect to explain URL");
  275. return "{\"success\": false, \"message\": \"Failed to connect to explain URL\"}";
  276. }
  277. // 第一块:question字段
  278. http->Write(question_field.c_str(), question_field.size());
  279. // 第二块:文件字段头部
  280. http->Write(file_header.c_str(), file_header.size());
  281. // 第三块:JPEG数据
  282. http->Write((const char*)jpeg_data_.buf, jpeg_data_.len);
  283. // 第四块:multipart尾部
  284. http->Write(multipart_footer.c_str(), multipart_footer.size());
  285. // 结束块
  286. http->Write("", 0);
  287. if (http->GetStatusCode() != 200) {
  288. ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode());
  289. return "{\"success\": false, \"message\": \"Failed to upload photo\"}";
  290. }
  291. std::string result = http->ReadAll();
  292. http->Close();
  293. ESP_LOGI(TAG, "Explain image size=%d, question=%s\n%s", jpeg_data_.len, question.c_str(), result.c_str());
  294. return result;
  295. }