🙏 Working camera stream with p4

This commit is contained in:
Rune Harlyk
2026-02-06 14:20:50 +01:00
committed by Rune Harlyk
parent bf2fd957af
commit d81b1b0851
6 changed files with 426 additions and 86 deletions
+9 -13
View File
@@ -11,26 +11,22 @@
namespace Camera {
#if USE_CAMERA && !CONFIG_IDF_TARGET_ESP32P4
#define USE_DVP_CAMERA (USE_CAMERA && !CONFIG_IDF_TARGET_ESP32P4)
#define USE_CSI_CAMERA (USE_CAMERA && CONFIG_IDF_TARGET_ESP32P4)
#if USE_DVP_CAMERA
#include <esp_camera.h>
#include <peripherals/camera_pins.h>
#else
typedef struct {
uint8_t *buf;
size_t len;
} camera_fb_t;
typedef struct {
} sensor_t;
#endif
#define PART_BOUNDARY "frame"
camera_fb_t *safe_camera_fb_get();
sensor_t *safe_sensor_get();
void safe_sensor_return();
#endif
#define PART_BOUNDARY "frame"
class CameraService
#if USE_CAMERA && !CONFIG_IDF_TARGET_ESP32P4
#if USE_DVP_CAMERA
: public StatefulService<CameraSettings>
#endif
{
@@ -42,7 +38,7 @@ class CameraService
esp_err_t cameraStill(httpd_req_t *request);
esp_err_t cameraStream(httpd_req_t *request);
#if USE_CAMERA && !CONFIG_IDF_TARGET_ESP32P4
#if USE_DVP_CAMERA
StatefulProtoEndpoint<CameraSettings, api_CameraSettings> protoEndpoint;
private:
+52 -67
View File
@@ -1,11 +1,12 @@
#pragma once
#include <driver/i2c.h>
#include <driver/i2c_master.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <functional>
#include <vector>
#include <cstring>
class I2CBus {
public:
@@ -24,30 +25,17 @@ class I2CBus {
_scl = scl;
_freq = freq;
i2c_config_t conf = {};
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = sda;
conf.scl_io_num = scl;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = freq;
i2c_master_bus_config_t bus_cfg = {};
bus_cfg.i2c_port = port;
bus_cfg.sda_io_num = sda;
bus_cfg.scl_io_num = scl;
bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
bus_cfg.glitch_ignore_cnt = 7;
bus_cfg.flags.enable_internal_pullup = true;
esp_err_t err = i2c_param_config(_port, &conf);
esp_err_t err = i2c_new_master_bus(&bus_cfg, &_bus);
if (err != ESP_OK) {
ESP_LOGE(TAG, "i2c_param_config failed: %s", esp_err_to_name(err));
return err;
}
err = i2c_driver_install(_port, I2C_MODE_MASTER, 0, 0, 0);
if (err == ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "I2C driver already installed for port %d, reconfiguring", _port);
i2c_driver_delete(_port);
err = i2c_param_config(_port, &conf);
if (err != ESP_OK) return err;
err = i2c_driver_install(_port, I2C_MODE_MASTER, 0, 0, 0);
}
if (err != ESP_OK) {
ESP_LOGE(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err));
ESP_LOGE(TAG, "i2c_new_master_bus failed: %s", esp_err_to_name(err));
return err;
}
@@ -57,73 +45,51 @@ class I2CBus {
void end() {
if (_initialized) {
i2c_driver_delete(_port);
if (_dev) {
i2c_master_bus_rm_device(_dev);
_dev = NULL;
_dev_addr = 0xFF;
}
i2c_del_master_bus(_bus);
_bus = NULL;
_initialized = false;
}
}
bool isInitialized() const { return _initialized; }
i2c_master_bus_handle_t busHandle() const { return _bus; }
esp_err_t writeBytes(uint8_t addr, const uint8_t* data, size_t len) {
if (!_initialized) return ESP_ERR_INVALID_STATE;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
if (len > 0 && data != nullptr) {
i2c_master_write(cmd, data, len, true);
}
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);
return ret;
esp_err_t err = ensureDevice(addr);
if (err != ESP_OK) return err;
return i2c_master_transmit(_dev, data, len, pdMS_TO_TICKS(100));
}
esp_err_t writeReg(uint8_t addr, uint8_t reg, const uint8_t* data, size_t len) {
if (!_initialized) return ESP_ERR_INVALID_STATE;
esp_err_t err = ensureDevice(addr);
if (err != ESP_OK) return err;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg, true);
uint8_t buf[len + 1];
buf[0] = reg;
if (len > 0 && data != nullptr) {
i2c_master_write(cmd, data, len, true);
memcpy(buf + 1, data, len);
}
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);
return ret;
return i2c_master_transmit(_dev, buf, len + 1, pdMS_TO_TICKS(100));
}
esp_err_t readReg(uint8_t addr, uint8_t reg, uint8_t* data, size_t len) {
if (!_initialized) return ESP_ERR_INVALID_STATE;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg, true);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, true);
if (len > 1) {
i2c_master_read(cmd, data, len - 1, I2C_MASTER_ACK);
}
i2c_master_read_byte(cmd, data + len - 1, I2C_MASTER_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);
return ret;
esp_err_t err = ensureDevice(addr);
if (err != ESP_OK) return err;
return i2c_master_transmit_receive(_dev, &reg, 1, data, len, pdMS_TO_TICKS(100));
}
bool probe(uint8_t addr) {
if (!_initialized) return false;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, pdMS_TO_TICKS(50));
i2c_cmd_link_delete(cmd);
return ret == ESP_OK;
return i2c_master_probe(_bus, addr, pdMS_TO_TICKS(50)) == ESP_OK;
}
std::vector<uint8_t> scan(uint8_t lower = 1, uint8_t upper = 127) {
@@ -157,4 +123,23 @@ class I2CBus {
gpio_num_t _scl = GPIO_NUM_NC;
uint32_t _freq = 100000;
bool _initialized = false;
i2c_master_bus_handle_t _bus = NULL;
i2c_master_dev_handle_t _dev = NULL;
uint8_t _dev_addr = 0xFF;
esp_err_t ensureDevice(uint8_t addr) {
if (_dev && _dev_addr == addr) return ESP_OK;
if (_dev) {
i2c_master_bus_rm_device(_dev);
_dev = NULL;
}
i2c_device_config_t dev_cfg = {};
dev_cfg.dev_addr_length = I2C_ADDR_BIT_LEN_7;
dev_cfg.device_address = addr;
dev_cfg.scl_speed_hz = _freq;
esp_err_t err = i2c_master_bus_add_device(_bus, &dev_cfg, &_dev);
if (err == ESP_OK) _dev_addr = addr;
return err;
}
};
+2 -1
View File
@@ -1,5 +1,6 @@
set(COMPONENT_REQUIRES
driver
esp_driver_i2c
esp_http_server
nvs_flash
esp_wifi
@@ -14,7 +15,7 @@ set(COMPONENT_REQUIRES
)
if(IDF_TARGET STREQUAL "esp32p4")
list(APPEND COMPONENT_REQUIRES esp_wifi_remote esp_hosted)
list(APPEND COMPONENT_REQUIRES esp_wifi_remote esp_hosted esp_driver_cam esp_driver_isp esp_driver_jpeg espressif__esp_sccb_intf espressif__esp_cam_sensor)
else()
list(APPEND COMPONENT_REQUIRES esp32-camera)
endif()
+5
View File
@@ -27,5 +27,10 @@ dependencies:
espressif/esp_hosted:
version: ">=0.0.6"
rules:
- if: "target in [esp32p4]"
espressif/esp_cam_sensor:
version: ">=0.5.0"
rules:
- if: "target in [esp32p4]"
+2
View File
@@ -59,11 +59,13 @@ void setupServer() {
server.on("/api/camera/still", HTTP_GET, [&](httpd_req_t *request) { return cameraService.cameraStill(request); });
server.on("/api/camera/stream", HTTP_GET,
[&](httpd_req_t *request) { return cameraService.cameraStream(request); });
#if USE_DVP_CAMERA
server.on("/api/camera/settings", HTTP_GET,
[&](httpd_req_t *request) { return cameraService.protoEndpoint.getState(request); });
server.on("/api/camera/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
return cameraService.protoEndpoint.handleStateUpdate(request, protoReq);
});
#endif
#endif
server.on("/api/servo/config", HTTP_GET,
[&](httpd_req_t *request) { return servoController.protoEndpoint.getState(request); });
+356 -5
View File
@@ -5,11 +5,14 @@
namespace Camera {
static const char *const TAG = "CameraService";
#if USE_CAMERA && !CONFIG_IDF_TARGET_ESP32P4
#if USE_DVP_CAMERA || USE_CSI_CAMERA
static constexpr const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static constexpr const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static constexpr const char *_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
#endif
#if USE_DVP_CAMERA
SemaphoreHandle_t cameraMutex = xSemaphoreCreateMutex();
@@ -179,11 +182,359 @@ void CameraService::updateCamera() {
safe_sensor_return();
}
#else
#elif USE_CSI_CAMERA
camera_fb_t *safe_camera_fb_get() { return nullptr; }
sensor_t *safe_sensor_get() { return nullptr; }
void safe_sensor_return() {}
}
extern "C" {
#include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_csi.h"
#include "esp_cam_ctlr_types.h"
#include "driver/isp.h"
#include "driver/jpeg_encode.h"
#include "esp_sccb_intf.h"
#include "esp_sccb_i2c.h"
#include "esp_cam_sensor.h"
#include "ov5647.h"
#include "esp_ldo_regulator.h"
}
#include <peripherals/i2c_bus.h>
namespace Camera {
#ifndef MIPI_CSI_HRES
#define MIPI_CSI_HRES 640
#endif
#ifndef MIPI_CSI_VRES
#define MIPI_CSI_VRES 480
#endif
#ifndef MIPI_CSI_LANE_BITRATE_MBPS
#define MIPI_CSI_LANE_BITRATE_MBPS 200
#endif
#ifndef MIPI_CSI_DATA_LANES
#define MIPI_CSI_DATA_LANES 2
#endif
#ifndef CAM_SCCB_FREQ_HZ
#define CAM_SCCB_FREQ_HZ 100000
#endif
#ifndef CAM_SENSOR_ADDR
#define CAM_SENSOR_ADDR 0x36
#endif
#ifndef CAM_XCLK_PIN
#define CAM_XCLK_PIN -1
#endif
#ifndef CAM_XCLK_FREQ_HZ
#define CAM_XCLK_FREQ_HZ 25000000
#endif
#ifndef CAM_RESET_PIN
#define CAM_RESET_PIN -1
#endif
#ifndef CAM_PWDN_PIN
#define CAM_PWDN_PIN -1
#endif
#ifndef CSI_JPEG_QUALITY
#define CSI_JPEG_QUALITY 80
#endif
static SemaphoreHandle_t s_cam_mutex = NULL;
static SemaphoreHandle_t s_frame_done = NULL;
static esp_cam_ctlr_handle_t s_cam_handle = NULL;
static isp_proc_handle_t s_isp_proc = NULL;
static jpeg_encoder_handle_t s_jpeg_enc = NULL;
static uint8_t *s_frame_buf = NULL;
static size_t s_frame_buf_size = 0;
static uint8_t *s_jpeg_buf = NULL;
static size_t s_jpeg_buf_size = 0;
static bool s_cam_initialized = false;
static bool on_trans_finished(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data) {
if (trans->buffer != s_frame_buf) return false;
BaseType_t woken = pdFALSE;
xSemaphoreGiveFromISR(s_frame_done, &woken);
return (woken == pdTRUE);
}
static bool capture_and_encode(uint8_t **jpeg_out, size_t *jpeg_len) {
if (!s_cam_initialized) return false;
esp_cam_ctlr_trans_t trans = {};
trans.buffer = s_frame_buf;
trans.buflen = s_frame_buf_size;
esp_err_t err = esp_cam_ctlr_receive(s_cam_handle, &trans, 2000);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Frame capture failed: %s", esp_err_to_name(err));
return false;
}
if (xSemaphoreTake(s_frame_done, pdMS_TO_TICKS(2000)) != pdTRUE) {
ESP_LOGE(TAG, "Frame receive timed out");
return false;
}
jpeg_encode_cfg_t enc_cfg = {};
enc_cfg.src_type = JPEG_ENCODE_IN_FORMAT_RGB565;
enc_cfg.sub_sample = JPEG_DOWN_SAMPLING_YUV422;
enc_cfg.image_quality = CSI_JPEG_QUALITY;
enc_cfg.width = MIPI_CSI_HRES;
enc_cfg.height = MIPI_CSI_VRES;
uint32_t out_size = 0;
err = jpeg_encoder_process(s_jpeg_enc, &enc_cfg,
s_frame_buf, trans.received_size,
s_jpeg_buf, s_jpeg_buf_size, &out_size);
if (err != ESP_OK) {
ESP_LOGE(TAG, "JPEG encode failed: %s", esp_err_to_name(err));
return false;
}
*jpeg_out = s_jpeg_buf;
*jpeg_len = out_size;
return true;
}
CameraService::CameraService() {
s_cam_mutex = xSemaphoreCreateMutex();
s_frame_done = xSemaphoreCreateBinary();
}
esp_err_t CameraService::begin() {
ESP_LOGI(TAG, "Initializing MIPI-CSI camera for ESP32-P4");
esp_ldo_channel_handle_t ldo_mipi_phy = NULL;
esp_ldo_channel_config_t ldo_cfg = {};
ldo_cfg.chan_id = 3;
ldo_cfg.voltage_mv = 2500;
esp_err_t ldo_err = esp_ldo_acquire_channel(&ldo_cfg, &ldo_mipi_phy);
if (ldo_err != ESP_OK) {
ESP_LOGE(TAG, "Failed to acquire MIPI PHY LDO: %s", esp_err_to_name(ldo_err));
return ldo_err;
}
i2c_master_bus_handle_t i2c_bus = I2CBus::instance().busHandle();
if (!i2c_bus) {
ESP_LOGE(TAG, "I2C bus not initialized, cannot init camera SCCB");
return ESP_ERR_INVALID_STATE;
}
esp_sccb_io_handle_t sccb_io = NULL;
sccb_i2c_config_t sccb_cfg = {};
sccb_cfg.scl_speed_hz = CAM_SCCB_FREQ_HZ;
sccb_cfg.device_address = CAM_SENSOR_ADDR;
sccb_cfg.dev_addr_length = I2C_ADDR_BIT_LEN_7;
esp_err_t err = sccb_new_i2c_io(i2c_bus, &sccb_cfg, &sccb_io);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to create SCCB I/O handle: %s", esp_err_to_name(err));
return err;
}
esp_cam_sensor_config_t cam_sensor_cfg = {};
cam_sensor_cfg.sccb_handle = sccb_io;
cam_sensor_cfg.reset_pin = static_cast<gpio_num_t>(CAM_RESET_PIN);
cam_sensor_cfg.pwdn_pin = static_cast<gpio_num_t>(CAM_PWDN_PIN);
cam_sensor_cfg.xclk_pin = static_cast<gpio_num_t>(CAM_XCLK_PIN);
cam_sensor_cfg.xclk_freq_hz = CAM_XCLK_FREQ_HZ;
cam_sensor_cfg.sensor_port = ESP_CAM_SENSOR_MIPI_CSI;
esp_cam_sensor_device_t *cam_sensor = ov5647_detect(&cam_sensor_cfg);
if (!cam_sensor) {
ESP_LOGE(TAG, "OV5647 detection failed");
return ESP_FAIL;
}
ESP_LOGI(TAG, "OV5647 camera sensor detected");
esp_cam_sensor_format_array_t fmt_array = {};
err = esp_cam_sensor_query_format(cam_sensor, &fmt_array);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to query sensor formats: %s", esp_err_to_name(err));
return err;
}
bool format_set = false;
for (uint32_t i = 0; i < fmt_array.count; i++) {
if (fmt_array.format_array[i].width == MIPI_CSI_HRES &&
fmt_array.format_array[i].height == MIPI_CSI_VRES) {
err = esp_cam_sensor_set_format(cam_sensor, &fmt_array.format_array[i]);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Sensor format set: %dx%d",
MIPI_CSI_HRES, MIPI_CSI_VRES);
format_set = true;
break;
}
}
}
if (!format_set && fmt_array.count > 0) {
err = esp_cam_sensor_set_format(cam_sensor, &fmt_array.format_array[0]);
if (err == ESP_OK) {
ESP_LOGW(TAG, "Using fallback sensor format: %dx%d",
fmt_array.format_array[0].width, fmt_array.format_array[0].height);
}
}
int stream_on = 1;
err = esp_cam_sensor_ioctl(cam_sensor, ESP_CAM_SENSOR_IOC_S_STREAM, &stream_on);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start sensor stream: %s", esp_err_to_name(err));
return err;
}
esp_isp_processor_cfg_t isp_cfg = {};
isp_cfg.clk_src = static_cast<isp_clk_src_t>(0);
isp_cfg.clk_hz = 80 * 1000 * 1000;
isp_cfg.input_data_source = ISP_INPUT_DATA_SOURCE_CSI;
isp_cfg.input_data_color_type = ISP_COLOR_RAW8;
isp_cfg.output_data_color_type = ISP_COLOR_RGB565;
isp_cfg.has_line_start_packet = false;
isp_cfg.has_line_end_packet = false;
isp_cfg.h_res = MIPI_CSI_HRES;
isp_cfg.v_res = MIPI_CSI_VRES;
isp_cfg.bayer_order = COLOR_RAW_ELEMENT_ORDER_BGGR;
err = esp_isp_new_processor(&isp_cfg, &s_isp_proc);
if (err != ESP_OK) {
ESP_LOGE(TAG, "ISP processor init failed: %s", esp_err_to_name(err));
return err;
}
err = esp_isp_enable(s_isp_proc);
if (err != ESP_OK) {
ESP_LOGE(TAG, "ISP enable failed: %s", esp_err_to_name(err));
return err;
}
esp_cam_ctlr_csi_config_t csi_cfg = {};
csi_cfg.ctlr_id = 0;
csi_cfg.h_res = MIPI_CSI_HRES;
csi_cfg.v_res = MIPI_CSI_VRES;
csi_cfg.lane_bit_rate_mbps = MIPI_CSI_LANE_BITRATE_MBPS;
csi_cfg.input_data_color_type = CAM_CTLR_COLOR_RAW8;
csi_cfg.output_data_color_type = CAM_CTLR_COLOR_RGB565;
csi_cfg.data_lane_num = MIPI_CSI_DATA_LANES;
csi_cfg.byte_swap_en = false;
csi_cfg.queue_items = 1;
err = esp_cam_new_csi_ctlr(&csi_cfg, &s_cam_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "CSI controller init failed: %s", esp_err_to_name(err));
return err;
}
esp_cam_ctlr_evt_cbs_t cbs = {};
cbs.on_trans_finished = on_trans_finished;
err = esp_cam_ctlr_register_event_callbacks(s_cam_handle, &cbs, NULL);
if (err != ESP_OK) {
ESP_LOGE(TAG, "CSI register callbacks failed: %s", esp_err_to_name(err));
return err;
}
static constexpr size_t CACHE_LINE_SIZE = 64;
s_frame_buf_size = MIPI_CSI_HRES * MIPI_CSI_VRES * 2;
s_frame_buf_size = (s_frame_buf_size + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1);
s_frame_buf = (uint8_t *)heap_caps_aligned_alloc(CACHE_LINE_SIZE, s_frame_buf_size, MALLOC_CAP_SPIRAM);
if (!s_frame_buf) {
ESP_LOGE(TAG, "Failed to allocate frame buffer (%d bytes)", (int)s_frame_buf_size);
return ESP_ERR_NO_MEM;
}
jpeg_encode_memory_alloc_cfg_t jpeg_mem_cfg = {};
jpeg_mem_cfg.buffer_direction = JPEG_ENC_ALLOC_OUTPUT_BUFFER;
s_jpeg_buf = (uint8_t *)jpeg_alloc_encoder_mem(
MIPI_CSI_HRES * MIPI_CSI_VRES, &jpeg_mem_cfg, &s_jpeg_buf_size);
if (!s_jpeg_buf) {
ESP_LOGE(TAG, "Failed to allocate JPEG buffer");
return ESP_ERR_NO_MEM;
}
jpeg_encode_engine_cfg_t enc_eng_cfg = {};
enc_eng_cfg.timeout_ms = 40;
err = jpeg_new_encoder_engine(&enc_eng_cfg, &s_jpeg_enc);
if (err != ESP_OK) {
ESP_LOGE(TAG, "JPEG encoder init failed: %s", esp_err_to_name(err));
return err;
}
err = esp_cam_ctlr_enable(s_cam_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "CSI controller enable failed: %s", esp_err_to_name(err));
return err;
}
err = esp_cam_ctlr_start(s_cam_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "CSI controller start failed: %s", esp_err_to_name(err));
return err;
}
s_cam_initialized = true;
ESP_LOGI(TAG, "MIPI-CSI camera initialized (%dx%d, %d-lane, %d Mbps)",
MIPI_CSI_HRES, MIPI_CSI_VRES, MIPI_CSI_DATA_LANES, MIPI_CSI_LANE_BITRATE_MBPS);
return ESP_OK;
}
esp_err_t CameraService::cameraStill(httpd_req_t *request) {
if (!s_cam_initialized) {
return WebServer::sendError(request, 503, "Camera not initialized");
}
xSemaphoreTake(s_cam_mutex, portMAX_DELAY);
uint8_t *jpeg_buf = NULL;
size_t jpeg_len = 0;
if (!capture_and_encode(&jpeg_buf, &jpeg_len)) {
xSemaphoreGive(s_cam_mutex);
return WebServer::sendError(request, 500, "Camera capture failed");
}
httpd_resp_set_type(request, "image/jpeg");
httpd_resp_set_hdr(request, "Content-Disposition", "inline; filename=capture.jpg");
esp_err_t res = httpd_resp_send(request, (const char *)jpeg_buf, jpeg_len);
xSemaphoreGive(s_cam_mutex);
return res;
}
esp_err_t CameraService::cameraStream(httpd_req_t *request) {
if (!s_cam_initialized) {
return WebServer::sendError(request, 503, "Camera not initialized");
}
httpd_resp_set_type(request, _STREAM_CONTENT_TYPE);
char part_buf[64];
esp_err_t res = ESP_OK;
while (res == ESP_OK) {
xSemaphoreTake(s_cam_mutex, portMAX_DELAY);
uint8_t *jpeg_buf = NULL;
size_t jpeg_len = 0;
if (!capture_and_encode(&jpeg_buf, &jpeg_len)) {
xSemaphoreGive(s_cam_mutex);
break;
}
size_t hlen = snprintf(part_buf, 64, _STREAM_PART, (unsigned int)jpeg_len);
res = httpd_resp_send_chunk(request, part_buf, hlen);
if (res == ESP_OK) res = httpd_resp_send_chunk(request, (const char *)jpeg_buf, jpeg_len);
if (res == ESP_OK) res = httpd_resp_send_chunk(request, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
xSemaphoreGive(s_cam_mutex);
vTaskDelay(pdMS_TO_TICKS(30));
}
ESP_LOGI(TAG, "Stream ended");
httpd_resp_send_chunk(request, NULL, 0);
return ESP_OK;
}
#else
CameraService::CameraService() {}
esp_err_t CameraService::begin() { return ESP_ERR_NOT_SUPPORTED; }