Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a camera livestream handler #2286

Merged
merged 1 commit into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions code/components/jomjol_controlcamera/ClassControllCamera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@

static const char *TAG = "CAM";


/* Camera live stream */
#define PART_BOUNDARY "123456789000000000000987654321"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";


static camera_config_t camera_config = {
.pin_pwdn = CAM_PIN_PWDN,
.pin_reset = CAM_PIN_RESET,
Expand Down Expand Up @@ -521,6 +529,74 @@ esp_err_t CCamera::CaptureToHTTP(httpd_req_t *req, int delay)
}


esp_err_t CCamera::CaptureToStream(httpd_req_t *req, bool FlashlightOn)
{
esp_err_t res = ESP_OK;
size_t fb_len = 0;
int64_t fr_start;
char * part_buf[64];

LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Live stream started");

if (FlashlightOn) {
LEDOnOff(true);
LightOnOff(true);
}

//httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); //stream is blocking web interface, only serving to local

httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));

while(1)
{
fr_start = esp_timer_get_time();
camera_fb_t *fb = esp_camera_fb_get();
esp_camera_fb_return(fb);
fb = esp_camera_fb_get();
if (!fb) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CaptureToStream: Camera framebuffer not available");
break;
}
fb_len = fb->len;

if (res == ESP_OK){
size_t hlen = snprintf((char *)part_buf, sizeof(part_buf), _STREAM_PART, fb_len);
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
}
if (res == ESP_OK){
res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb_len);
}
if (res == ESP_OK){
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}

esp_camera_fb_return(fb);

int64_t fr_end = esp_timer_get_time();
ESP_LOGD(TAG, "JPG: %uKB %ums", (uint32_t)(fb_len/1024), (uint32_t)((fr_end - fr_start)/1000));

if (res != ESP_OK){ // Exit loop, e.g. also when closing the webpage
break;
}

int64_t fr_delta_ms = (fr_end - fr_start) / 1000;
if (CAM_LIVESTREAM_REFRESHRATE > fr_delta_ms) {
const TickType_t xDelay = (CAM_LIVESTREAM_REFRESHRATE - fr_delta_ms) / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "Stream: sleep for: %ldms", (long) xDelay*10);
vTaskDelay(xDelay);
}
}

LEDOnOff(false);
LightOnOff(false);

LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Live stream stopped");

return res;
}


void CCamera::LightOnOff(bool status)
{
GpioHandler* gpioHandler = gpio_handler_get();
Expand Down
1 change: 1 addition & 0 deletions code/components/jomjol_controlcamera/ClassControllCamera.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class CCamera {
void LightOnOff(bool status);
void LEDOnOff(bool status);
esp_err_t CaptureToHTTP(httpd_req_t *req, int delay = 0);
esp_err_t CaptureToStream(httpd_req_t *req, bool FlashlightOn);
void SetQualitySize(int qual, framesize_t resol);
bool SetBrightnessContrastSaturation(int _brightness, int _contrast, int _saturation);
void GetCameraParameter(httpd_req_t *req, int &qual, framesize_t &resol);
Expand Down
40 changes: 40 additions & 0 deletions code/components/jomjol_flowcontroll/MainFlowControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,40 @@ esp_err_t handler_init(httpd_req_t *req)
}


esp_err_t handler_stream(httpd_req_t *req)
{
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("handler_stream - Start");
ESP_LOGD(TAG, "handler_stream uri: %s", req->uri);
#endif

char _query[50];
char _value[10];
bool flashlightOn = false;

if (httpd_req_get_url_query_str(req, _query, 50) == ESP_OK)
{
// ESP_LOGD(TAG, "Query: %s", _query);
if (httpd_query_key_value(_query, "flashlight", _value, 10) == ESP_OK)
{
#ifdef DEBUG_DETAIL_ON
ESP_LOGD(TAG, "flashlight is found%s", _size);
#endif
if (strlen(_value) > 0)
flashlightOn = true;
}
}

Camera.CaptureToStream(req, flashlightOn);

#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("handler_stream - Done");
#endif

return ESP_OK;
}


esp_err_t handler_flow_start(httpd_req_t *req) {

#ifdef DEBUG_DETAIL_ON
Expand Down Expand Up @@ -1094,4 +1128,10 @@ void register_server_main_flow_task_uri(httpd_handle_t server)
camuri.handler = handler_get_heap;
camuri.user_ctx = (void*) "Heap";
httpd_register_uri_handler(server, &camuri);

camuri.uri = "/stream";
camuri.handler = handler_stream;
camuri.user_ctx = (void*) "stream";
httpd_register_uri_handler(server, &camuri);

}
40 changes: 30 additions & 10 deletions code/include/defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,39 +55,48 @@

//compiler optimization for tflite-micro-esp-examples
#define XTENSA
//#define CONFIG_IDF_TARGET_ARCH_XTENSA //not needed with platformio/espressif32 @ 5.2.0
//#define CONFIG_IDF_TARGET_ARCH_XTENSA //not needed with platformio/espressif32 @ 5.2.0


//ClassControllCamera + ClassFlowTakeImage + connect_wlan + main
#define FLASH_GPIO GPIO_NUM_4
#define BLINK_GPIO GPIO_NUM_33
//Statusled + ClassControllCamera
#define BLINK_GPIO GPIO_NUM_33 // PIN for red board LED

//interface_mqtt + read_wlanini
#define __HIDE_PASSWORD

//ClassControllCamera
#define USE_PWM_LEDFLASH // if __LEDGLOBAL is defined, a global variable is used for LED control, otherwise locally and each time a new

//server_GPIO
#define __LEDGLOBAL
#define FLASH_GPIO GPIO_NUM_4 // PIN for flashlight LED
#define USE_PWM_LEDFLASH // if __LEDGLOBAL is defined, a global variable is used for LED control, otherwise locally and each time a new
#define CAM_LIVESTREAM_REFRESHRATE 500 // Camera livestream feature: Waiting time in milliseconds to refresh image


//ClassControllCamera + ClassFlowTakeImage
#define CAMERA_MODEL_AI_THINKER
#define BOARD_ESP32CAM_AITHINKER


//server_GPIO
#define __LEDGLOBAL


//server_GPIO + server_file + SoftAP
#define CONFIG_FILE "/sdcard/config/config.ini"
#define CONFIG_FILE_BACKUP "/sdcard/config/config.bak"


//interface_mqtt + read_wlanini
#define __HIDE_PASSWORD


//ClassFlowControll + Main + SoftAP
#define WLAN_CONFIG_FILE "/sdcard/wlan.ini"


//main
#define __SD_USE_ONE_LINE_MODE__

// server_file + Helper
#define FILE_PATH_MAX (255) //Max length a file path can have on storage


//server_file +(ota_page.html + upload_script.html)
#define MAX_FILE_SIZE (8000*1024) // 8 MB Max size of an individual file. Make sure this value is same as that set in upload_script.html and ota_page.html!
#define MAX_FILE_SIZE_STR "8MB"
Expand All @@ -98,37 +107,45 @@
#define SERVER_HELPER_SCRATCH_BUFSIZE 8192
#define SERVER_OTA_SCRATCH_BUFSIZE 1024


//server_file + server_help
#define IS_FILE_EXT(filename, ext) \
(strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0)


//server_ota
#define HASH_LEN 32 // SHA-256 digest length
#define OTA_URL_SIZE 256


//ClassFlow + ClassFlowImage + server_tflite
#define LOGFILE_TIME_FORMAT "%Y%m%d-%H%M%S"
#define LOGFILE_TIME_FORMAT_DATE_EXTR substr(0, 8)
#define LOGFILE_TIME_FORMAT_HOUR_EXTR substr(9, 2)


//ClassFlowControll
#define READOUT_TYPE_VALUE 0
#define READOUT_TYPE_PREVALUE 1
#define READOUT_TYPE_RAWVALUE 2
#define READOUT_TYPE_ERROR 3


//ClassFlowControll: Serve alg_roi.jpg from memory as JPG
#define ALGROI_LOAD_FROM_MEM_AS_JPG // Load ALG_ROI.JPG as rendered JPG from RAM


//ClassFlowMQTT
#define LWT_TOPIC "connection"
#define LWT_CONNECTED "connected"
#define LWT_DISCONNECTED "connection lost"


//ClassFlowPostProcessing
#define PREVALUE_TIME_FORMAT_OUTPUT "%Y-%m-%dT%H:%M:%S%z"
#define PREVALUE_TIME_FORMAT_INPUT "%d-%d-%dT%d:%d:%d"


//CImageBasis
#define HTTP_BUFFER_SENT 1024
#define MAX_JPG_SIZE 128000
Expand All @@ -139,14 +156,17 @@
//#define STB_IMAGE_RESIZE_IMPLEMENTATION
#define STBI_ONLY_JPEG // (save 2% of Flash, but breaks the alignment mark generation, see https://github.com/jomjol/AI-on-the-edge-device/issues/1721)


//interface_influxdb
#define MAX_HTTP_OUTPUT_BUFFER 2048


//server_mqtt
#define LWT_TOPIC "connection"
#define LWT_CONNECTED "connected"
#define LWT_DISCONNECTED "connection lost"


//CTfLiteClass
#define TFLITE_MINIMAL_CHECK(x) \
if (!(x)) { \
Expand Down
2 changes: 1 addition & 1 deletion code/main/server_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ httpd_handle_t start_webserver(void)
config.server_port = 80;
config.ctrl_port = 32768;
config.max_open_sockets = 5; //20210921 --> previously 7
config.max_uri_handlers = 38; // previously 24, 20220511: 35, 20221220: 37, 2023-01-02:38
config.max_uri_handlers = 39; // previously 24, 20220511: 35, 20221220: 37, 2023-01-02:38
config.max_resp_headers = 8;
config.backlog_conn = 5;
config.lru_purge_enable = true; // this cuts old connections if new ones are needed.
Expand Down