🙏 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
+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; }