🙏 Working camera stream with p4
This commit is contained in:
@@ -11,26 +11,22 @@
|
|||||||
|
|
||||||
namespace Camera {
|
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 <esp_camera.h>
|
||||||
#include <peripherals/camera_pins.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();
|
camera_fb_t *safe_camera_fb_get();
|
||||||
sensor_t *safe_sensor_get();
|
sensor_t *safe_sensor_get();
|
||||||
void safe_sensor_return();
|
void safe_sensor_return();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PART_BOUNDARY "frame"
|
||||||
|
|
||||||
class CameraService
|
class CameraService
|
||||||
#if USE_CAMERA && !CONFIG_IDF_TARGET_ESP32P4
|
#if USE_DVP_CAMERA
|
||||||
: public StatefulService<CameraSettings>
|
: public StatefulService<CameraSettings>
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
@@ -42,7 +38,7 @@ class CameraService
|
|||||||
esp_err_t cameraStill(httpd_req_t *request);
|
esp_err_t cameraStill(httpd_req_t *request);
|
||||||
esp_err_t cameraStream(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;
|
StatefulProtoEndpoint<CameraSettings, api_CameraSettings> protoEndpoint;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <driver/i2c.h>
|
#include <driver/i2c_master.h>
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
class I2CBus {
|
class I2CBus {
|
||||||
public:
|
public:
|
||||||
@@ -24,30 +25,17 @@ class I2CBus {
|
|||||||
_scl = scl;
|
_scl = scl;
|
||||||
_freq = freq;
|
_freq = freq;
|
||||||
|
|
||||||
i2c_config_t conf = {};
|
i2c_master_bus_config_t bus_cfg = {};
|
||||||
conf.mode = I2C_MODE_MASTER;
|
bus_cfg.i2c_port = port;
|
||||||
conf.sda_io_num = sda;
|
bus_cfg.sda_io_num = sda;
|
||||||
conf.scl_io_num = scl;
|
bus_cfg.scl_io_num = scl;
|
||||||
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
|
bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
|
||||||
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
|
bus_cfg.glitch_ignore_cnt = 7;
|
||||||
conf.master.clk_speed = freq;
|
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) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "i2c_param_config failed: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "i2c_new_master_bus 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));
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,73 +45,51 @@ class I2CBus {
|
|||||||
|
|
||||||
void end() {
|
void end() {
|
||||||
if (_initialized) {
|
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;
|
_initialized = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isInitialized() const { return _initialized; }
|
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) {
|
esp_err_t writeBytes(uint8_t addr, const uint8_t* data, size_t len) {
|
||||||
if (!_initialized) return ESP_ERR_INVALID_STATE;
|
if (!_initialized) return ESP_ERR_INVALID_STATE;
|
||||||
|
esp_err_t err = ensureDevice(addr);
|
||||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
if (err != ESP_OK) return err;
|
||||||
i2c_master_start(cmd);
|
return i2c_master_transmit(_dev, data, len, pdMS_TO_TICKS(100));
|
||||||
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 writeReg(uint8_t addr, uint8_t reg, const uint8_t* data, size_t len) {
|
esp_err_t writeReg(uint8_t addr, uint8_t reg, const uint8_t* data, size_t len) {
|
||||||
if (!_initialized) return ESP_ERR_INVALID_STATE;
|
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();
|
uint8_t buf[len + 1];
|
||||||
i2c_master_start(cmd);
|
buf[0] = reg;
|
||||||
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
|
|
||||||
i2c_master_write_byte(cmd, reg, true);
|
|
||||||
if (len > 0 && data != nullptr) {
|
if (len > 0 && data != nullptr) {
|
||||||
i2c_master_write(cmd, data, len, true);
|
memcpy(buf + 1, data, len);
|
||||||
}
|
}
|
||||||
i2c_master_stop(cmd);
|
return i2c_master_transmit(_dev, buf, len + 1, pdMS_TO_TICKS(100));
|
||||||
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, pdMS_TO_TICKS(100));
|
|
||||||
i2c_cmd_link_delete(cmd);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t readReg(uint8_t addr, uint8_t reg, uint8_t* data, size_t len) {
|
esp_err_t readReg(uint8_t addr, uint8_t reg, uint8_t* data, size_t len) {
|
||||||
if (!_initialized) return ESP_ERR_INVALID_STATE;
|
if (!_initialized) return ESP_ERR_INVALID_STATE;
|
||||||
|
esp_err_t err = ensureDevice(addr);
|
||||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
if (err != ESP_OK) return err;
|
||||||
i2c_master_start(cmd);
|
return i2c_master_transmit_receive(_dev, ®, 1, data, len, pdMS_TO_TICKS(100));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool probe(uint8_t addr) {
|
bool probe(uint8_t addr) {
|
||||||
if (!_initialized) return false;
|
if (!_initialized) return false;
|
||||||
|
return i2c_master_probe(_bus, addr, pdMS_TO_TICKS(50)) == ESP_OK;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> scan(uint8_t lower = 1, uint8_t upper = 127) {
|
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;
|
gpio_num_t _scl = GPIO_NUM_NC;
|
||||||
uint32_t _freq = 100000;
|
uint32_t _freq = 100000;
|
||||||
bool _initialized = false;
|
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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
set(COMPONENT_REQUIRES
|
set(COMPONENT_REQUIRES
|
||||||
driver
|
driver
|
||||||
|
esp_driver_i2c
|
||||||
esp_http_server
|
esp_http_server
|
||||||
nvs_flash
|
nvs_flash
|
||||||
esp_wifi
|
esp_wifi
|
||||||
@@ -14,7 +15,7 @@ set(COMPONENT_REQUIRES
|
|||||||
)
|
)
|
||||||
|
|
||||||
if(IDF_TARGET STREQUAL "esp32p4")
|
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()
|
else()
|
||||||
list(APPEND COMPONENT_REQUIRES esp32-camera)
|
list(APPEND COMPONENT_REQUIRES esp32-camera)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -27,5 +27,10 @@ dependencies:
|
|||||||
|
|
||||||
espressif/esp_hosted:
|
espressif/esp_hosted:
|
||||||
version: ">=0.0.6"
|
version: ">=0.0.6"
|
||||||
|
rules:
|
||||||
|
- if: "target in [esp32p4]"
|
||||||
|
|
||||||
|
espressif/esp_cam_sensor:
|
||||||
|
version: ">=0.5.0"
|
||||||
rules:
|
rules:
|
||||||
- if: "target in [esp32p4]"
|
- if: "target in [esp32p4]"
|
||||||
@@ -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/still", HTTP_GET, [&](httpd_req_t *request) { return cameraService.cameraStill(request); });
|
||||||
server.on("/api/camera/stream", HTTP_GET,
|
server.on("/api/camera/stream", HTTP_GET,
|
||||||
[&](httpd_req_t *request) { return cameraService.cameraStream(request); });
|
[&](httpd_req_t *request) { return cameraService.cameraStream(request); });
|
||||||
|
#if USE_DVP_CAMERA
|
||||||
server.on("/api/camera/settings", HTTP_GET,
|
server.on("/api/camera/settings", HTTP_GET,
|
||||||
[&](httpd_req_t *request) { return cameraService.protoEndpoint.getState(request); });
|
[&](httpd_req_t *request) { return cameraService.protoEndpoint.getState(request); });
|
||||||
server.on("/api/camera/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
server.on("/api/camera/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
||||||
return cameraService.protoEndpoint.handleStateUpdate(request, protoReq);
|
return cameraService.protoEndpoint.handleStateUpdate(request, protoReq);
|
||||||
});
|
});
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
server.on("/api/servo/config", HTTP_GET,
|
server.on("/api/servo/config", HTTP_GET,
|
||||||
[&](httpd_req_t *request) { return servoController.protoEndpoint.getState(request); });
|
[&](httpd_req_t *request) { return servoController.protoEndpoint.getState(request); });
|
||||||
|
|||||||
@@ -5,11 +5,14 @@
|
|||||||
namespace Camera {
|
namespace Camera {
|
||||||
|
|
||||||
static const char *const TAG = "CameraService";
|
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_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_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";
|
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();
|
SemaphoreHandle_t cameraMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
@@ -179,11 +182,359 @@ void CameraService::updateCamera() {
|
|||||||
safe_sensor_return();
|
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() {}
|
CameraService::CameraService() {}
|
||||||
esp_err_t CameraService::begin() { return ESP_ERR_NOT_SUPPORTED; }
|
esp_err_t CameraService::begin() { return ESP_ERR_NOT_SUPPORTED; }
|
||||||
|
|||||||
Reference in New Issue
Block a user