Skip to content

Commit

Permalink
Merge pull request #239 from daschr/fork/multi-stream
Browse files Browse the repository at this point in the history
Fix autoLamp and FPS settings
  • Loading branch information
easytarget authored May 9, 2022
2 parents 8e37e22 + 3bba1a5 commit 095e221
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 66 deletions.
20 changes: 13 additions & 7 deletions app_httpd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,18 @@
#include "src/logo.h"
#include "storage.h"

extern "C"{
#if __has_include("myconfig.h")
struct station { const char ssid[65]; const char password[65]; const bool dhcp;};
#include "myconfig.h"
#endif

#ifndef MIN_FRAME_TIME
#warning "MIN_FRAME_TIME undefined, using default value of 500"
#define MIN_FRAME_TIME 500
#endif

#include "cam_streamer.h"
}

// Functions from the main .ino
extern void flashLED(int flashtime);
extern void setLamp(int newVal);
Expand Down Expand Up @@ -302,7 +311,7 @@ static esp_err_t cmd_handler(httpd_req_t *req){
else if(!strcmp(variable, "wb_mode")) res = s->set_wb_mode(s, val);
else if(!strcmp(variable, "ae_level")) res = s->set_ae_level(s, val);
else if(!strcmp(variable, "rotate")) myRotation = val;
else if(!strcmp(variable, "min_frame_time")) minFrameTime = val;
else if(!strcmp(variable, "min_frame_time")) cam_streamer_set_frame_delay(cam_streamer, val);
else if(!strcmp(variable, "autolamp") && (lampVal != -1)) {
autoLamp = val;
if (autoLamp) {
Expand Down Expand Up @@ -793,10 +802,7 @@ void startCameraServer(int hPort, int sPort){
httpd_register_uri_handler(stream_httpd, &info_uri);
httpd_register_uri_handler(stream_httpd, &streamviewer_uri);
cam_streamer=(cam_streamer_t *) malloc(sizeof(cam_streamer_t));
#ifndef CAM_STREAMER_DESIRED_FPS
#define CAM_STREAMER_DESIRED_FPS 2
#endif
cam_streamer_init(cam_streamer, stream_httpd, CAM_STREAMER_DESIRED_FPS);
cam_streamer_init(cam_streamer, stream_httpd, MIN_FRAME_TIME);
cam_streamer_start(cam_streamer);
}
httpd_register_uri_handler(stream_httpd, &favicon_16x16_uri);
Expand Down
135 changes: 81 additions & 54 deletions cam_streamer.c → cam_streamer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,24 @@
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <sys/socket.h>

#include "cam_streamer.h"

#if __has_include("myconfig.h")
struct station {
const char ssid[65];
const char password[65];
const bool dhcp;
};
#include "myconfig.h"
#endif

#ifndef CAM_STREAMER_MAX_CLIENTS
#warning "CAM_STREAMER_MAX_CLIENTS undefined, using default value of 10"
#define CAM_STREAMER_MAX_CLIENTS 10
#endif

#define PART_BOUNDARY "123456789000000000000987654321"

#define _STREAM_HEADERS "HTTP/1.1 200 OK\r\n"\
Expand All @@ -18,46 +33,60 @@
"Keep-Alive: timeout=15\r\n"\
"Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"

#define _TEXT_HEADERS "HTTP/1.1 200 OK\r\n"\
"Access-Control-Allow-Origin: *\r\n"\
"Connection: Close\r\n"\
"Content-Type: text/plain\r\n\r\n"

extern bool debugData;
extern int lampVal;
extern bool autoLamp;
extern void setLamp(int);

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 inline void print_debug(const char *fmt, ...) {
if(debugData) {
va_list l;
va_start(l, fmt);
vprintf(fmt, l);
va_end(l);
}
}

static uint8_t is_send_error(int r) {
switch(r) {
case HTTPD_SOCK_ERR_INVALID:
#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] invalid argument occured!\n");
#endif
print_debug("[cam_streamer] invalid argument occured!\n");
return 1;
case HTTPD_SOCK_ERR_TIMEOUT:
#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] timeout/interrupt occured!\n");
#endif
print_debug("[cam_streamer] timeout/interrupt occured!\n");
return 1;
case HTTPD_SOCK_ERR_FAIL:
#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] unrecoverable error while send()!\n");
#endif
print_debug("[cam_streamer] unrecoverable error while send()!\n");
return 1;
case ESP_ERR_INVALID_ARG:
#ifdef DEBUG_DEFAULT_ON
printf("[text-streamer] session closed!\n");
#endif
print_debug("[text-streamer] session closed!\n");
return 1;
default:
#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] sent %d bytes!\n", r);
#endif
print_debug("[cam_streamer] sent %d bytes!\n", r);
return 0;
}
}

void cam_streamer_init(cam_streamer_t *s, httpd_handle_t server, uint16_t fps) {
void cam_streamer_init(cam_streamer_t *s, httpd_handle_t server, uint16_t frame_delay) {
memset(s, 0, sizeof(cam_streamer_t));
s->frame_delay=1000000/fps;
s->frame_delay=1000*frame_delay;
s->clients=xQueueCreate(CAM_STREAMER_MAX_CLIENTS*2, sizeof(int));
s->server=server;
}

// frame_delay must be in ms (not us)
void cam_streamer_set_frame_delay(cam_streamer_t *s, uint16_t frame_delay) {
s->frame_delay=1000*frame_delay;
}

static void cam_streamer_update_frame(cam_streamer_t *s) {
uint8_t l=0;
while(!__atomic_compare_exchange_n(&s->buf_lock, &l, 1, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
Expand All @@ -73,17 +102,13 @@ static void cam_streamer_update_frame(cam_streamer_t *s) {
s->last_updated=esp_timer_get_time();
s->part_len=snprintf(s->part_buf, 64, _STREAM_PART, s->buf->len);
__atomic_store_n(&s->buf_lock, 0, __ATOMIC_RELAXED);
#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] fetched new frame\n");
#endif
print_debug("[cam_streamer] fetched new frame\n");
}

static void cam_streamer_decrement_num_clients(cam_streamer_t *s) {
size_t num_clients=s->num_clients;
while(num_clients>0 && !__atomic_compare_exchange_n(&s->num_clients, &num_clients, num_clients-1, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] num_clients decremented\n");
#endif
print_debug("[cam_streamer] num_clients decremented\n");
}

void cam_streamer_task(void *p) {
Expand All @@ -93,35 +118,35 @@ void cam_streamer_task(void *p) {
int fd;
unsigned int n_entries;
for(;;) {
while(!(n_entries=uxQueueMessagesWaiting(s->clients)))
while(!(n_entries=uxQueueMessagesWaiting(s->clients))) {
if(autoLamp && lampVal!=-1) setLamp(0);
vTaskSuspend(NULL);
if(autoLamp && lampVal!=-1) setLamp(lampVal);
}

current_time=esp_timer_get_time();
if((current_time-last_time)<s->frame_delay)
vTaskDelay((s->frame_delay-(current_time-last_time))/(1000*portTICK_PERIOD_MS));
last_time=current_time;

cam_streamer_update_frame(s);

print_debug("[cam_streamer] frame_size: %luB %lums\n", s->buf->len, (current_time-last_time)/1000);
last_time=current_time;

for(unsigned int i=0; i<n_entries; ++i) {
if(xQueueReceive(s->clients, &fd, 10/portTICK_PERIOD_MS)==pdFALSE) {
#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] failed to dequeue fd!\n");
#endif
print_debug("[cam_streamer] failed to dequeue fd!\n");
continue;
}

#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] dequeued fd %d\n", fd);
printf("[cam_streamer] sending part: \"%.*s\"\n", (int) s->part_len, s->part_buf);
#endif
print_debug("[cam_streamer] fd %d dequeued\n", fd);

if(is_send_error(httpd_socket_send(s->server, fd, s->part_buf, s->part_len, 0))) {
cam_streamer_decrement_num_clients(s);
continue;
}

if(is_send_error(httpd_socket_send(s->server, fd, s->buf->buf, s->buf->len, 0))) {
if(is_send_error(httpd_socket_send(s->server, fd, (const char *) s->buf->buf, s->buf->len, 0))) {
cam_streamer_decrement_num_clients(s);
continue;
}
Expand All @@ -132,20 +157,16 @@ void cam_streamer_task(void *p) {
}

xQueueSend(s->clients, (void *) &fd, 10/portTICK_PERIOD_MS);
#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] fd %d requeued\n", fd);
#endif
print_debug("[cam_streamer] fd %d requeued\n", fd);
}
}
}

void cam_streamer_start(cam_streamer_t *s) {
BaseType_t r=xTaskCreate(cam_streamer_task, "cam_streamer", 10*1024, (void *) s, tskIDLE_PRIORITY+3, &s->task);

#ifdef DEBUG_DEFAULT_ON
if(r!=pdPASS)
printf("[cam_streamer] failed to create task!\n");
#endif
print_debug("[cam_streamer] failed to create task!\n");
}

void cam_streamer_stop(cam_streamer_t *s) {
Expand All @@ -162,35 +183,41 @@ void cam_streamer_dequeue_all_clients(cam_streamer_t *s) {
}

bool cam_streamer_enqueue_client(cam_streamer_t *s, int fd) {
#ifdef DEBUG_DEFAULT_ON
printf("sending stream headers:\n%s\nLength: %d\n", _STREAM_HEADERS, strlen(_STREAM_HEADERS));
#endif
if(s->num_clients>=CAM_STREAMER_MAX_CLIENTS) {
if(httpd_socket_send(s->server, fd, _TEXT_HEADERS, strlen(_TEXT_HEADERS), 0)) {
print_debug("failed sending text headers!\n");
return false;
}

#define EMSG "too many clients"
if(httpd_socket_send(s->server, fd, EMSG, strlen(EMSG), 0)) {
print_debug("failed sending message\n");
return false;
}
#undef EMSG
close(fd);
return false;
}

print_debug("sending stream headers:\n%s\nLength: %d\n", _STREAM_HEADERS, strlen(_STREAM_HEADERS));
if(is_send_error(httpd_socket_send(s->server, fd, _STREAM_HEADERS, strlen(_STREAM_HEADERS), 0))) {
#ifdef DEBUG_DEFAULT_ON
printf("failed sending headers!\n");
#endif
print_debug("failed sending headers!\n");
return false;
}

if(is_send_error(httpd_socket_send(s->server, fd, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY), 0))) {
#ifdef DEBUG_DEFAULT_ON
printf("failed sending boundary!\n");
#endif
print_debug("failed sending boundary!\n");
return false;
}

const BaseType_t r=xQueueSend(s->clients, (void *) &fd, 10*portTICK_PERIOD_MS);
if(r!=pdTRUE) {
#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] failed to enqueue fd %d\n", fd);
#endif
print_debug("[cam_streamer] failed to enqueue fd %d\n", fd);
#define EMSG "failed to enqueue"
httpd_socket_send(s->server, fd, EMSG, strlen(EMSG), 0);
#undef EMSG
} else {
#ifdef DEBUG_DEFAULT_ON
printf("[cam_streamer] socket %d enqueued\n", fd);
#endif
print_debug("[cam_streamer] socket %d enqueued\n", fd);
__atomic_fetch_add(&s->num_clients, 1, __ATOMIC_RELAXED);
vTaskResume(s->task);
}
Expand Down
4 changes: 2 additions & 2 deletions cam_streamer.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#include <freertos/queue.h>
#include <freertos/task.h>

#define CAM_STREAMER_MAX_CLIENTS 10
typedef struct {
QueueHandle_t clients;
TaskHandle_t task;
Expand All @@ -25,7 +24,8 @@ typedef struct {
size_t num_clients;
} cam_streamer_t;

void cam_streamer_init(cam_streamer_t *s, httpd_handle_t server, uint16_t fps);
void cam_streamer_init(cam_streamer_t *s, httpd_handle_t server, uint16_t frame_delay);
void cam_streamer_set_frame_delay(cam_streamer_t *s, uint16_t frame_delay);
void cam_streamer_task(void *p);
void cam_streamer_start(cam_streamer_t *s);
void cam_streamer_stop(cam_streamer_t *s);
Expand Down
3 changes: 1 addition & 2 deletions esp32-cam-webserver.ino
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,12 @@

// Primary config, or defaults.
#if __has_include("myconfig.h")
struct station { const char ssid[65]; const char password[65]; const bool dhcp;}; // do no edit
struct station { const char ssid[65]; const char password[65]; const bool dhcp;}; // do no edit
#include "myconfig.h"
#else
#warning "Using Defaults: Copy myconfig.sample.h to myconfig.h and edit that to use your own settings"
#define WIFI_AP_ENABLE
#define CAMERA_MODEL_AI_THINKER
struct station { const char ssid[65]; const char password[65]; const bool dhcp;}
stationList[] = {{"ESP32-CAM-CONNECT","InsecurePassword", true}};
#endif

Expand Down
5 changes: 4 additions & 1 deletion myconfig.sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* just replace your ssid and password in the line below.
*/

struct station stationList[] = {{"my_ssid","my_password", true}};
const struct station stationList[] = {{"my_ssid","my_password", true}};

/*
* You can extend the stationList[] above with additional SSID+Password pairs
Expand Down Expand Up @@ -148,6 +148,9 @@ struct station stationList[] = {{"ssid1", "pass1", true},
// max_fps = 1000/min_frame_time
// #define MIN_FRAME_TIME 500

// Maximum number of clients of the stream
// #define CAM_STREAMER_MAX_CLIENTS 10

/*
* Additional Features
*
Expand Down

0 comments on commit 095e221

Please sign in to comment.