move camera stream to webserver insteado of http async

still use own task but streaming is still locking processes
This commit is contained in:
Luc 2020-10-26 08:09:29 +01:00
parent adc7ae9778
commit 24d7d96c08
4 changed files with 247 additions and 100 deletions

View File

@ -205,24 +205,20 @@
//CAMERA_MODEL_M5STACK_WIDE 3 //CAMERA_MODEL_M5STACK_WIDE 3
//CAMERA_MODEL_AI_THINKER 4 e.g. used by ESP32-CAM //CAMERA_MODEL_AI_THINKER 4 e.g. used by ESP32-CAM
//CAMERA_MODEL_WROVER_KIT 5 //CAMERA_MODEL_WROVER_KIT 5
//#define CAMERA_DEVICE CAMERA_MODEL_AI_THINKER #define CAMERA_DEVICE CAMERA_MODEL_AI_THINKER
//#define CAMERA_DEVICE_FLIP_VERTICALY //comment to disable //#define CAMERA_DEVICE_FLIP_VERTICALY //comment to disable
//#define CAMERA_DEVICE_FLIP_HORIZONTALY//comment to disable //#define CAMERA_DEVICE_FLIP_HORIZONTALY//comment to disable
#define CUSTOM_CAMERA_NAME "ESP32-CAM" #define CUSTOM_CAMERA_NAME "ESP32-CAM"
//////////////////////////////////////////////// #define CAMERA_INDEPENDANT_TASK
//Warning until fix is found
#if defined(CAMERA_DEVICE) && defined(FTP_FEATURE)
#warning currently Camera and FTP server do not work together, disabling FTP SERVER
#undef FTP_FEATURE
#endif
//Allow remote access by enabling cross origin access //Allow remote access by enabling cross origin access
//check https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS //check https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
//this should be enabled only in specific cases //this should be enabled only in specific cases
//like show the camera in web page different than device web server //like show the camera in web page different than device web server
//if you do not know what is that then better left it commented //if you do not know what is that then better left it commented
//#define ESP_ACCESS_CONTROL_ALLOW_ORIGIN #define ESP_ACCESS_CONTROL_ALLOW_ORIGIN
//ESP_GCODE_HOST_FEATURE : allow to send GCODE with ack //ESP_GCODE_HOST_FEATURE : allow to send GCODE with ack
#define ESP_GCODE_HOST_FEATURE #define ESP_GCODE_HOST_FEATURE
@ -274,6 +270,9 @@
//Serial rx buffer size is 256 but can be extended //Serial rx buffer size is 256 but can be extended
#define SERIAL_RX_BUFFER_SIZE 512 #define SERIAL_RX_BUFFER_SIZE 512
//Serial need speed up on esp32
#define SERIAL_INDEPENDANT_TASK
/************************************ /************************************
* *
* Settings * Settings

View File

@ -24,8 +24,9 @@
#include "../../core/settings_esp3d.h" #include "../../core/settings_esp3d.h"
#include "../network/netservices.h" #include "../network/netservices.h"
#include "../../core/esp3doutput.h" #include "../../core/esp3doutput.h"
#include "../../core/esp3d.h"
#include "../network/netconfig.h" #include "../network/netconfig.h"
#include "esp_http_server.h" #include <WebServer.h>
#include <esp_camera.h> #include <esp_camera.h>
#include "fd_forward.h" #include "fd_forward.h"
#include <soc/soc.h> //not sure this one is needed #include <soc/soc.h> //not sure this one is needed
@ -33,60 +34,56 @@
#include <driver/i2c.h> #include <driver/i2c.h>
#define DEFAULT_FRAME_SIZE FRAMESIZE_SVGA #define DEFAULT_FRAME_SIZE FRAMESIZE_SVGA
#define HTTP_TASK_PRIORITY 5
#define PART_BUFFER_SIZE 64 #define PART_BUFFER_SIZE 64
#define JPEG_COMPRESSION 80 #define JPEG_COMPRESSION 80
#define MIN_WIDTH_COMPRESSION 400 #define MIN_WIDTH_COMPRESSION 400
#define PART_BOUNDARY "123456789000000000000987654321" #define PART_BOUNDARY "123456789000000000000987654321"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; #define ESP3DSTREAM_RUNNING_PRIORITY 1
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; #define ESP3DSTREAM_RUNNING_CORE 0
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; #define CAMERA_YIELD 10
#define _STREAM_CONTENT_TYPE "multipart/x-mixed-replace;boundary=" PART_BOUNDARY
#define _STREAM_BOUNDARY "\r\n--" PART_BOUNDARY "\r\n"
#define _STREAM_PART "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"
extern Esp3D myesp3d;
bool Camera::_initialised = false; bool Camera::_initialised = false;
httpd_handle_t stream_httpd = NULL; bool Camera::_connected = false;
Camera esp3d_camera; Camera esp3d_camera;
STREAMSERVER * Camera::_streamserver = nullptr;
#ifdef CAMERA_INDEPENDANT_TASK
TaskHandle_t _hcameratask= nullptr;
#endif //CAMERA_INDEPENDANT_TASK
//to break the loop void Camera::handle_stream()
static void disconnected_uri(httpd_handle_t hd, int sockfd)
{
log_esp3d("Camera stream disconnected");
esp3d_camera.connect(false);
}
static esp_err_t stream_handler(httpd_req_t *req)
{ {
log_esp3d("Camera stream reached"); log_esp3d("Camera stream reached");
if (!esp3d_camera.serverstarted()) { if (!_initialised) {
const char* resp = "Camera not started";
log_esp3d("Camera not started"); log_esp3d("Camera not started");
httpd_resp_send(req, resp, strlen(resp)); _streamserver->send (500, "text/plain", "Camera not started");
return ESP_FAIL; return;
} }
esp3d_camera.connect(true); _connected = true;
#ifdef ESP_ACCESS_CONTROL_ALLOW_ORIGIN #ifdef ESP_ACCESS_CONTROL_ALLOW_ORIGIN
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); _streamserver->enableCrossOrigin(true);
#endif //ESP_ACCESS_CONTROL_ALLOw_ORIGIN #endif //ESP_ACCESS_CONTROL_ALLOw_ORIGIN
camera_fb_t * fb = NULL; camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK; bool res_error = false;
size_t _jpg_buf_len = 0; size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL; uint8_t * _jpg_buf = NULL;
char * part_buf[PART_BUFFER_SIZE]; char * part_buf[PART_BUFFER_SIZE];
dl_matrix3du_t *image_matrix = NULL; dl_matrix3du_t *image_matrix = NULL;
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); _streamserver->sendHeader(String(F("Content-Type")), String(F(_STREAM_CONTENT_TYPE)),true);
if(res != ESP_OK) { _streamserver->setContentLength(CONTENT_LENGTH_UNKNOWN);
esp3d_camera.connect(false); _streamserver->send(200);
const char* resp = "Stream type failed";
log_esp3d("Stream type failed");
httpd_resp_send(req, resp, strlen(resp));
return res;
}
uint8_t retry = 0; uint8_t retry = 0;
while(true) { while(true) {
if (!esp3d_camera.isconnected()) { if (!_connected) {
const char* resp = "Camera is not connected";
log_esp3d("Camera is not connected"); log_esp3d("Camera is not connected");
httpd_resp_send(req, resp, strlen(resp)); _streamserver->send (500, "text/plain", "Camera is not connected");
return ESP_FAIL; _connected = false;
return;
} }
log_esp3d("Camera capture ongoing"); log_esp3d("Camera capture ongoing");
fb = esp_camera_fb_get(); fb = esp_camera_fb_get();
@ -97,7 +94,7 @@ static esp_err_t stream_handler(httpd_req_t *req)
retry ++; retry ++;
continue; continue;
} else { } else {
res = ESP_FAIL; res_error = true;
} }
} else { } else {
if(fb->width > MIN_WIDTH_COMPRESSION) { if(fb->width > MIN_WIDTH_COMPRESSION) {
@ -107,7 +104,7 @@ static esp_err_t stream_handler(httpd_req_t *req)
fb = NULL; fb = NULL;
if(!jpeg_converted) { if(!jpeg_converted) {
log_esp3d("JPEG compression failed"); log_esp3d("JPEG compression failed");
res = ESP_FAIL; res_error = true;
} }
} else { } else {
_jpg_buf_len = fb->len; _jpg_buf_len = fb->len;
@ -118,16 +115,16 @@ static esp_err_t stream_handler(httpd_req_t *req)
if (!image_matrix) { if (!image_matrix) {
log_esp3d("dl_matrix3du_alloc failed"); log_esp3d("dl_matrix3du_alloc failed");
res = ESP_FAIL; res_error = true;
} else { } else {
if(!fmt2rgb888(fb->buf, fb->len, fb->format, image_matrix->item)) { if(!fmt2rgb888(fb->buf, fb->len, fb->format, image_matrix->item)) {
log_esp3d("fmt2rgb888 failed"); log_esp3d("fmt2rgb888 failed");
res = ESP_FAIL; res_error = true;
} else { } else {
if (fb->format != PIXFORMAT_JPEG) { if (fb->format != PIXFORMAT_JPEG) {
if(!fmt2jpg(image_matrix->item, fb->width*fb->height*3, fb->width, fb->height, PIXFORMAT_RGB888, 90, &_jpg_buf, &_jpg_buf_len)) { if(!fmt2jpg(image_matrix->item, fb->width*fb->height*3, fb->width, fb->height, PIXFORMAT_RGB888, 90, &_jpg_buf, &_jpg_buf_len)) {
log_esp3d("fmt2jpg failed"); log_esp3d("fmt2jpg failed");
res = ESP_FAIL; res_error = true;
} }
esp_camera_fb_return(fb); esp_camera_fb_return(fb);
fb = NULL; fb = NULL;
@ -140,16 +137,28 @@ static esp_err_t stream_handler(httpd_req_t *req)
} }
} }
} }
//no one is connected so no need to stream
if(res == ESP_OK) { if (_streamserver->client().connected() == 0) {
break;
}
if(!res_error) {
size_t hlen = snprintf((char *)part_buf, PART_BUFFER_SIZE, _STREAM_PART, _jpg_buf_len); size_t hlen = snprintf((char *)part_buf, PART_BUFFER_SIZE, _STREAM_PART, _jpg_buf_len);
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); _streamserver->sendContent_P ((const char *)part_buf, hlen);
} }
if(res == ESP_OK) { if(!res_error) {
res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); size_t processed = 0;
size_t packetSize = 2000;
uint8_t * currentbuf = _jpg_buf;
while (processed < _jpg_buf_len) {
_streamserver->sendContent_P ((const char *)&currentbuf[processed], packetSize);
processed+=packetSize;
if ((_jpg_buf_len - processed) < packetSize)packetSize = (_jpg_buf_len - processed);
vTaskDelay(1/ portTICK_PERIOD_MS);
}
} }
if(res == ESP_OK) { if(!res_error) {
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); _streamserver->sendContent_P ((const char *)_STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
} }
if(fb) { if(fb) {
esp_camera_fb_return(fb); esp_camera_fb_return(fb);
@ -159,19 +168,135 @@ static esp_err_t stream_handler(httpd_req_t *req)
free(_jpg_buf); free(_jpg_buf);
_jpg_buf = NULL; _jpg_buf = NULL;
} }
if(res != ESP_OK) { if(res_error) {
log_esp3d("stream error stop connection");
break; break;
} }
//Hal::wait(CAMERA_YIELD*100);
vTaskDelay(100 / portTICK_PERIOD_MS);
} }
esp3d_camera.connect(false); _connected = false;
return res; _streamserver->sendContent("");
} }
void Camera::handle_snap()
{
log_esp3d("Camera stream reached");
if (!_initialised) {
log_esp3d("Camera not started");
_streamserver->send (500, "text/plain", "Camera not started");
return;
}
sensor_t * s = esp_camera_sensor_get();
if (_streamserver->hasArg ("framesize") ) {
if(s->status.framesize != _streamserver->arg ("framesize").toInt()) {
command("framesize", _streamserver->arg ("framesize").c_str());
}
}
if (_streamserver->hasArg ("hmirror") ) {
command("hmirror", _streamserver->arg ("hmirror").c_str());
}
if (_streamserver->hasArg ("vflip") ) {
command("vflip", _streamserver->arg ("vflip").c_str());
}
if (_streamserver->hasArg ("wb_mode") ) {
command("wb_mode", _streamserver->arg ("wb_mode").c_str());
}
_connected = true;
#ifdef ESP_ACCESS_CONTROL_ALLOW_ORIGIN
_streamserver->enableCrossOrigin(true);
#endif //ESP_ACCESS_CONTROL_ALLOw_ORIGIN
camera_fb_t * fb = NULL;
bool res_error = false;
size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL;
char * part_buf[PART_BUFFER_SIZE];
dl_matrix3du_t *image_matrix = NULL;
_streamserver->sendHeader(String(F("Content-Type")), String(F("image/jpeg")),true);
_streamserver->sendHeader(String(F("Content-Disposition")), String(F("inline; filename=capture.jpg")),true);
_streamserver->setContentLength(CONTENT_LENGTH_UNKNOWN);
_streamserver->send(200);
log_esp3d("Camera capture ongoing");
fb = esp_camera_fb_get();
if (!fb) {
log_esp3d("Camera capture failed");
_streamserver->send (500, "text/plain", "Capture failed");
} else {
if(fb->width > MIN_WIDTH_COMPRESSION) {
if(fb->format != PIXFORMAT_JPEG) {
bool jpeg_converted = frame2jpg(fb, JPEG_COMPRESSION, &_jpg_buf, &_jpg_buf_len);
esp_camera_fb_return(fb);
fb = NULL;
if(!jpeg_converted) {
log_esp3d("JPEG compression failed");
res_error = true;
}
} else {
_jpg_buf_len = fb->len;
_jpg_buf = fb->buf;
}
} else {
image_matrix = dl_matrix3du_alloc(1, fb->width, fb->height, 3);
if (!image_matrix) {
log_esp3d("dl_matrix3du_alloc failed");
res_error = true;
} else {
if(!fmt2rgb888(fb->buf, fb->len, fb->format, image_matrix->item)) {
log_esp3d("fmt2rgb888 failed");
res_error = true;
} else {
if (fb->format != PIXFORMAT_JPEG) {
if(!fmt2jpg(image_matrix->item, fb->width*fb->height*3, fb->width, fb->height, PIXFORMAT_RGB888, 90, &_jpg_buf, &_jpg_buf_len)) {
log_esp3d("fmt2jpg failed");
res_error = true;
}
esp_camera_fb_return(fb);
fb = NULL;
} else {
_jpg_buf = fb->buf;
_jpg_buf_len = fb->len;
}
}
dl_matrix3du_free(image_matrix);
}
}
}
if (!res_error) {
_streamserver->sendContent_P ((const char *)_jpg_buf, _jpg_buf_len);
}
if(fb) {
esp_camera_fb_return(fb);
fb = NULL;
_jpg_buf = NULL;
} else if(_jpg_buf) {
free(_jpg_buf);
_jpg_buf = NULL;
}
_connected = false;
_streamserver->sendContent("");
}
#ifdef CAMERA_INDEPENDANT_TASK
void ESP3DStreamTaskfn( void * parameter )
{
Hal::wait(100); // Yield to other tasks
for(;;) {
esp3d_camera.process();
//Hal::wait(CAMERA_YIELD); // Yield to other tasks
vTaskDelay(10 / portTICK_PERIOD_MS);
}
vTaskDelete( NULL );
}
#endif //CAMERA_INDEPENDANT_TASK
Camera::Camera() Camera::Camera()
{ {
_server_started = false; _server_started = false;
_started = false; _started = false;
_connected = false; _connected = false;
_streamserver = nullptr;
} }
Camera::~Camera() Camera::~Camera()
@ -179,9 +304,9 @@ Camera::~Camera()
end(); end();
} }
int Camera::command(const char * param, const char * value) int Camera::command(const char * param, const char * value)
{ {
log_esp3d("Camera: %s=%s\n",param, value);
int res = 0; int res = 0;
int val = atoi(value); int val = atoi(value);
sensor_t * s = esp_camera_sensor_get(); sensor_t * s = esp_camera_sensor_get();
@ -322,20 +447,6 @@ bool Camera::initHardware()
bool Camera::stopHardware() bool Camera::stopHardware()
{ {
return true; return true;
//no need to stop as it is done once at boot
/* _initialised = false;
log_esp3d("deinit camera");
esp_err_t err = esp_camera_deinit();
if(err == ESP_OK) {
//seems sometimes i2c install failed when doing camera init so let's remove if already installed
if(i2c_driver_delete(I2C_NUM_1)!= ESP_OK) {
log_esp3d("I2C 1 delete failed");
}
return true;
} else {
log_esp3d("Camera deinit failed with error 0x%x", err);
return false;
}*/
} }
bool Camera::startStreamServer() bool Camera::startStreamServer()
@ -347,31 +458,39 @@ bool Camera::startStreamServer()
} }
if (NetConfig::started() && (NetConfig::getMode()!= ESP_BT)) { if (NetConfig::started() && (NetConfig::getMode()!= ESP_BT)) {
ESP3DOutput output(ESP_ALL_CLIENTS); ESP3DOutput output(ESP_ALL_CLIENTS);
httpd_config_t httpdconfig = HTTPD_DEFAULT_CONFIG();
httpdconfig.close_fn =&disconnected_uri;
httpd_uri_t stream_uri = {
.uri = "/stream",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
_port = Settings_ESP3D::read_uint32(ESP_CAMERA_PORT); _port = Settings_ESP3D::read_uint32(ESP_CAMERA_PORT);
httpdconfig.server_port = _port;
httpdconfig.ctrl_port = httpdconfig.server_port +1;
httpdconfig.task_priority = HTTP_TASK_PRIORITY;
log_esp3d("Starting camera server"); log_esp3d("Starting camera server");
if (httpd_start(&stream_httpd, &httpdconfig) == ESP_OK) { _streamserver= new STREAMSERVER(_port);
String stmp = "Camera server started port " + String(httpdconfig.server_port); if (!_streamserver) {
output.printMSG(stmp.c_str());
log_esp3d("Registering /stream");
if (httpd_register_uri_handler(stream_httpd, &stream_uri) != ESP_OK) {
log_esp3d("Registering /stream failed");
}
} else {
log_esp3d("Starting camera server failed"); log_esp3d("Starting camera server failed");
output.printERROR("Starting camera server failed"); output.printERROR("Starting camera server failed");
return false; return false;
} }
_streamserver->on("/snap",HTTP_ANY, handle_snap);
_streamserver->on("/",HTTP_ANY, handle_snap);
_streamserver->on("/stream",HTTP_ANY, handle_stream);
_streamserver->begin();
String stmp = "Camera server started port " + String(_port);
output.printMSG(stmp.c_str());
#ifdef CAMERA_INDEPENDANT_TASK
//create serial task once
if (_hcameratask == nullptr) {
xTaskCreatePinnedToCore(
ESP3DStreamTaskfn, /* Task function. */
"ESP3DStream Task", /* name of task. */
8192, /* Stack size of task */
NULL, /* parameter of the task */
ESP3DSTREAM_RUNNING_PRIORITY, /* priority of the task */
&_hcameratask, /* Task handle to keep track of created task */
ESP3DSTREAM_RUNNING_CORE /* Core to run the task */
);
if (_hcameratask == nullptr) {
log_esp3d("Camera Task creation failed");
return false;
}
}
#endif //CAMERA_INDEPENDANT_TASK
_server_started = true; _server_started = true;
} }
for (int j = 0; j < 5; j++) { for (int j = 0; j < 5; j++) {
@ -389,13 +508,11 @@ bool Camera::stopStreamServer()
{ {
_connected = false; _connected = false;
if (_server_started) { if (_server_started) {
log_esp3d("unregister /stream"); if (_streamserver) {
if (ESP_OK != httpd_unregister_uri(stream_httpd, "/stream")) { log_esp3d("Stop stream server");
log_esp3d("Error unregistering /stream"); _streamserver->stop();
} delete _streamserver;
log_esp3d("Stop httpd"); _streamserver = NULL;
if (ESP_OK != httpd_stop(stream_httpd)) {
log_esp3d("Error stopping stream server");
} }
_server_started = false; _server_started = false;
} }
@ -443,9 +560,20 @@ void Camera::end()
} }
} }
void Camera::process()
{
if (_started) {
if (_streamserver) {
_streamserver->handleClient();
}
}
}
void Camera::handle() void Camera::handle()
{ {
//so far nothing to do #ifndef CAMERA_INDEPENDANT_TASK
process();
#endif //CAMERA_INDEPENDANT_TASK
} }
uint8_t Camera::GetModel() uint8_t Camera::GetModel()

View File

@ -22,6 +22,15 @@
#ifndef _CAMERA_H #ifndef _CAMERA_H
#define _CAMERA_H #define _CAMERA_H
//class WebSocketsServer;
#if defined (ARDUINO_ARCH_ESP32)
class WebServer;
#define STREAMSERVER WebServer
#endif //ARDUINO_ARCH_ESP32
#if defined (ARDUINO_ARCH_ESP8266)
#include <ESP8266WebServer.h>
#define STREAMSERVER ESP8266WebServer
#endif //ARDUINO_ARCH_ESP8266
class Camera class Camera
{ {
@ -34,8 +43,11 @@ public:
bool stopHardware(); bool stopHardware();
bool startStreamServer(); bool startStreamServer();
bool stopStreamServer(); bool stopStreamServer();
static void handle_stream();
static void handle_snap();
void process();
void handle(); void handle();
int command(const char * param, const char * value); static int command(const char * param, const char * value);
uint8_t GetModel(); uint8_t GetModel();
const char *GetModelString(); const char *GetModelString();
bool started() bool started()
@ -54,15 +66,20 @@ public:
{ {
return _connected; return _connected;
} }
bool isinitialised()
{
return _initialised;
}
uint16_t port() uint16_t port()
{ {
return _port; return _port;
} }
private: private:
static bool _initialised; static bool _initialised;
static STREAMSERVER * _streamserver;
bool _server_started; bool _server_started;
bool _started; bool _started;
bool _connected; static bool _connected;
uint16_t _port; uint16_t _port;
}; };

View File

@ -120,6 +120,9 @@ void DevicesServices::end()
void DevicesServices::handle() void DevicesServices::handle()
{ {
if (_started) { if (_started) {
#ifdef CAMERA_DEVICE
esp3d_camera.handle();
#endif //CAMERA_DEVICE
#ifdef DISPLAY_DEVICE #ifdef DISPLAY_DEVICE
esp3d_display.handle(); esp3d_display.handle();
#endif //DISPLAY_DEVICE #endif //DISPLAY_DEVICE