Skip to content

Commit 5058cd9

Browse files
committed
add multiclient streaming
1 parent 7d49901 commit 5058cd9

File tree

4 files changed

+227
-98
lines changed

4 files changed

+227
-98
lines changed

app_httpd.cpp

Lines changed: 16 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#include "src/logo.h"
2929
#include "storage.h"
3030

31+
extern "C"{
32+
#include "cam_streamer.h"
33+
}
3134
// Functions from the main .ino
3235
extern void flashLED(int flashtime);
3336
extern void setLamp(int newVal);
@@ -67,6 +70,8 @@ extern char otaPassword[];
6770
extern unsigned long xclk;
6871
extern int sensorPID;
6972

73+
cam_streamer_t *cam_streamer;
74+
7075
typedef struct {
7176
httpd_req_t *req;
7277
size_t len;
@@ -222,102 +227,13 @@ static esp_err_t capture_handler(httpd_req_t *req){
222227
}
223228

224229
static esp_err_t stream_handler(httpd_req_t *req){
225-
camera_fb_t * fb = NULL;
226-
esp_err_t res = ESP_OK;
227-
size_t _jpg_buf_len = 0;
228-
uint8_t * _jpg_buf = NULL;
229-
char * part_buf[64];
230-
231-
streamKill = false;
232-
233-
Serial.println("Stream requested");
234-
if (autoLamp && (lampVal != -1)) setLamp(lampVal);
235-
streamCount = 1; // at present we only have one stream handler, so values are 0 or 1..
236-
flashLED(75); // double flash of status LED
237-
delay(75);
238-
flashLED(75);
239-
240-
static int64_t last_frame = 0;
241-
if(!last_frame) {
242-
last_frame = esp_timer_get_time();
243-
}
244-
245-
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
246-
if(res != ESP_OK){
247-
streamCount = 0;
248-
if (autoLamp && (lampVal != -1)) setLamp(0);
249-
Serial.println("STREAM: failed to set HTTP response type");
250-
return res;
251-
}
252-
253-
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
254-
255-
if(res == ESP_OK){
256-
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
257-
}
258-
259-
while(true){
260-
fb = esp_camera_fb_get();
261-
if (!fb) {
262-
Serial.println("STREAM: failed to acquire frame");
263-
res = ESP_FAIL;
264-
} else {
265-
if(fb->format != PIXFORMAT_JPEG){
266-
Serial.println("STREAM: Non-JPEG frame returned by camera module");
267-
res = ESP_FAIL;
268-
} else {
269-
_jpg_buf_len = fb->len;
270-
_jpg_buf = fb->buf;
271-
}
272-
}
273-
if(res == ESP_OK){
274-
size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
275-
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
276-
}
277-
if(res == ESP_OK){
278-
res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
279-
}
280-
if(res == ESP_OK){
281-
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
282-
}
283-
if(fb){
284-
esp_camera_fb_return(fb);
285-
fb = NULL;
286-
_jpg_buf = NULL;
287-
} else if(_jpg_buf){
288-
free(_jpg_buf);
289-
_jpg_buf = NULL;
290-
}
291-
if(res != ESP_OK){
292-
// This is the error exit point from the stream loop.
293-
// We end the stream here only if a Hard failure has been encountered or the connection has been interrupted.
294-
Serial.printf("Stream failed, code = %i : %s\r\n", res, esp_err_to_name(res));
295-
break;
296-
}
297-
if((res != ESP_OK) || streamKill){
298-
// We end the stream here when a kill is signalled.
299-
Serial.printf("Stream killed\r\n");
300-
break;
301-
}
302-
int64_t frame_time = esp_timer_get_time() - last_frame;
303-
frame_time /= 1000;
304-
int32_t frame_delay = (minFrameTime > frame_time) ? minFrameTime - frame_time : 0;
305-
delay(frame_delay);
306-
307-
if (debugData) {
308-
Serial.printf("MJPG: %uB %ums, delay: %ums, framerate (%.1ffps)\r\n",
309-
(uint32_t)(_jpg_buf_len),
310-
(uint32_t)frame_time, frame_delay, 1000.0 / (uint32_t)(frame_time + frame_delay));
311-
}
312-
last_frame = esp_timer_get_time();
313-
}
314-
315-
streamsServed++;
316-
streamCount = 0;
317-
if (autoLamp && (lampVal != -1)) setLamp(0);
318-
Serial.println("Stream ended");
319-
last_frame = 0;
320-
return res;
230+
int fd=httpd_req_to_sockfd(req);
231+
if(fd==-1){
232+
printf("[stream_handler] could not get socket fd!\n");
233+
return ESP_FAIL;
234+
}
235+
cam_streamer_enqueue_client(cam_streamer, fd);
236+
return ESP_OK;
321237
}
322238

323239
static esp_err_t cmd_handler(httpd_req_t *req){
@@ -877,7 +793,10 @@ void startCameraServer(int hPort, int sPort){
877793
httpd_register_uri_handler(stream_httpd, &stream_uri);
878794
httpd_register_uri_handler(stream_httpd, &info_uri);
879795
httpd_register_uri_handler(stream_httpd, &streamviewer_uri);
880-
}
796+
cam_streamer=(cam_streamer_t *) malloc(sizeof(cam_streamer_t));
797+
cam_streamer_init(cam_streamer, stream_httpd, 3);
798+
cam_streamer_start(cam_streamer);
799+
}
881800
httpd_register_uri_handler(stream_httpd, &favicon_16x16_uri);
882801
httpd_register_uri_handler(stream_httpd, &favicon_32x32_uri);
883802
httpd_register_uri_handler(stream_httpd, &favicon_ico_uri);

cam_streamer.c

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#include <esp_timer.h>
2+
#include <esp_http_server.h>
3+
#include <string.h>
4+
#include <stdlib.h>
5+
#include <stdint.h>
6+
7+
#include <freertos/FreeRTOS.h>
8+
#include <freertos/queue.h>
9+
#include <freertos/task.h>
10+
11+
#include "cam_streamer.h"
12+
13+
#define PART_BOUNDARY "123456789000000000000987654321"
14+
15+
#define _STREAM_HEADERS "HTTP/1.1 200 OK\r\n"\
16+
"Access-Control-Allow-Origin: *\r\n"\
17+
"Connection: Keep-Alive\r\n"\
18+
"Keep-Alive: timeout=15\r\n"\
19+
"Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"
20+
21+
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
22+
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
23+
24+
static uint8_t is_send_error(int r) {
25+
switch(r) {
26+
case HTTPD_SOCK_ERR_INVALID:
27+
#ifdef DEBUG_DEFAULT_ON
28+
printf("[cam_streamer] invalid argument occured!\n");
29+
#endif
30+
return 1;
31+
case HTTPD_SOCK_ERR_TIMEOUT:
32+
#ifdef DEBUG_DEFAULT_ON
33+
printf("[cam_streamer] timeout/interrupt occured!\n");
34+
#endif
35+
return 1;
36+
case HTTPD_SOCK_ERR_FAIL:
37+
#ifdef DEBUG_DEFAULT_ON
38+
printf("[cam_streamer] unrecoverable error while send()!\n");
39+
#endif
40+
return 1;
41+
case ESP_ERR_INVALID_ARG:
42+
#ifdef DEBUG_DEFAULT_ON
43+
printf("[text-streamer] session closed!\n");
44+
#endif
45+
return 1;
46+
default:
47+
#ifdef DEBUG_DEFAULT_ON
48+
printf("[cam_streamer] sent %d bytes!\n", r);
49+
#endif
50+
return 0;
51+
}
52+
}
53+
54+
void cam_streamer_init(cam_streamer_t *s, httpd_handle_t server, uint16_t fps) {
55+
memset(s, 0, sizeof(cam_streamer_t));
56+
s->frame_delay=1000000/fps;
57+
s->clients=xQueueCreate(CAM_STREAMER_MAX_CLIENTS*2, sizeof(int));
58+
s->server=server;
59+
}
60+
61+
static void cam_streamer_update_frame(cam_streamer_t *s) {
62+
uint8_t l=0;
63+
while(!__atomic_compare_exchange_n(&s->buf_lock, &l, 1, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
64+
l=0;
65+
vTaskDelay(1/portTICK_PERIOD_MS);
66+
}
67+
68+
if(s->buf)
69+
esp_camera_fb_return(s->buf);
70+
71+
s->buf=esp_camera_fb_get();
72+
73+
s->last_updated=esp_timer_get_time();
74+
s->part_len=snprintf(s->part_buf, 64, _STREAM_PART, s->buf->len);
75+
__atomic_store_n(&s->buf_lock, 0, __ATOMIC_RELAXED);
76+
#ifdef DEBUG_DEFAULT_ON
77+
printf("[cam_streamer] fetched new frame\n");
78+
#endif
79+
}
80+
81+
void cam_streamer_task(void *p) {
82+
cam_streamer_t *s=(cam_streamer_t *) p;
83+
84+
uint8_t res;
85+
uint64_t last_time=0, current_time;
86+
int fd;
87+
unsigned int n_entries;
88+
for(;;) {
89+
while(!(n_entries=uxQueueMessagesWaiting(s->clients)))
90+
vTaskSuspend(NULL);
91+
92+
current_time=esp_timer_get_time();
93+
if((current_time-last_time)<s->frame_delay)
94+
vTaskDelay((s->frame_delay-(current_time-last_time))/(1000*portTICK_PERIOD_MS));
95+
last_time=current_time;
96+
97+
cam_streamer_update_frame(s);
98+
99+
for(unsigned int i=0; i<n_entries; ++i) {
100+
if(xQueueReceive(s->clients, &fd, 10/portTICK_PERIOD_MS)==pdFALSE) {
101+
#ifdef DEBUG_DEFAULT_ON
102+
printf("[cam_streamer] failed to dequeue fd!\n");
103+
#endif
104+
continue;
105+
}
106+
107+
#ifdef DEBUG_DEFAULT_ON
108+
printf("[cam_streamer] dequeued fd %d\n", fd);
109+
printf("[cam_streamer] sending part: \"%.*s\"\n", (int) s->part_len, s->part_buf);
110+
#endif
111+
if((res=is_send_error(httpd_socket_send(s->server, fd, s->part_buf, s->part_len, 0))))
112+
continue;
113+
114+
if((res|=is_send_error(httpd_socket_send(s->server, fd, s->buf->buf, s->buf->len, 0))))
115+
continue;
116+
117+
if((res|=is_send_error(httpd_socket_send(s->server, fd, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY), 0))))
118+
continue;
119+
120+
if(!res) {
121+
#ifdef DEBUG_DEFAULT_ON
122+
printf("[cam_streamer] fd %d requeued\n", fd);
123+
#endif
124+
xQueueSend(s->clients, (void *) &fd, 10/portTICK_PERIOD_MS);
125+
}
126+
}
127+
}
128+
}
129+
130+
void cam_streamer_start(cam_streamer_t *s) {
131+
BaseType_t r=xTaskCreate(cam_streamer_task, "cam_streamer", 10*1024, (void *) s, tskIDLE_PRIORITY+3, &s->task);
132+
133+
#ifdef DEBUG_DEFAULT_ON
134+
if(r!=pdPASS)
135+
printf("[cam_streamer] failed to create task!\n");
136+
#endif
137+
}
138+
139+
void cam_streamer_stop(cam_streamer_t *s) {
140+
vTaskDelete(s->task);
141+
}
142+
143+
bool cam_streamer_enqueue_client(cam_streamer_t *s, int fd) {
144+
#ifdef DEBUG_DEFAULT_ON
145+
printf("sending stream headers:\n%s\nLength: %d\n", _STREAM_HEADERS, strlen(_STREAM_HEADERS));
146+
#endif
147+
if(is_send_error(httpd_socket_send(s->server, fd, _STREAM_HEADERS, strlen(_STREAM_HEADERS), 0))) {
148+
#ifdef DEBUG_DEFAULT_ON
149+
printf("failed sending headers!\n");
150+
#endif
151+
return false;
152+
}
153+
154+
if(is_send_error(httpd_socket_send(s->server, fd, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY), 0))) {
155+
#ifdef DEBUG_DEFAULT_ON
156+
printf("failed sending boundary!\n");
157+
#endif
158+
return false;
159+
}
160+
161+
const BaseType_t r=xQueueSend(s->clients, (void *) &fd, 10*portTICK_PERIOD_MS);
162+
if(r!=pdTRUE) {
163+
#ifdef DEBUG_DEFAULT_ON
164+
printf("[cam_streamer] failed to enqueue fd %d\n", fd);
165+
#endif
166+
#define EMSG "failed to enqueue"
167+
httpd_socket_send(s->server, fd, EMSG, strlen(EMSG), 0);
168+
#undef EMSG
169+
} else {
170+
#ifdef DEBUG_DEFAULT_ON
171+
printf("[cam_streamer] socket %d enqueued\n", fd);
172+
#endif
173+
vTaskResume(s->task);
174+
}
175+
176+
return r==pdTRUE?true:false;
177+
}
178+

cam_streamer.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#ifndef _INC_CAM_STREAMER
2+
#define _INC_CAM_STREAMER
3+
4+
#include <esp_timer.h>
5+
#include <esp_http_server.h>
6+
#include <esp_camera.h>
7+
#include <string.h>
8+
#include <stdint.h>
9+
10+
#include <freertos/FreeRTOS.h>
11+
#include <freertos/queue.h>
12+
#include <freertos/task.h>
13+
14+
#define CAM_STREAMER_MAX_CLIENTS 10
15+
typedef struct {
16+
QueueHandle_t clients;
17+
TaskHandle_t task;
18+
uint64_t last_updated;
19+
int64_t frame_delay;
20+
uint8_t buf_lock;
21+
camera_fb_t *buf;
22+
char part_buf[64];
23+
size_t part_len;
24+
httpd_handle_t server;
25+
} cam_streamer_t;
26+
27+
void cam_streamer_init(cam_streamer_t *s, httpd_handle_t server, uint16_t fps);
28+
void cam_streamer_task(void *p);
29+
void cam_streamer_start(cam_streamer_t *s);
30+
void cam_streamer_stop(cam_streamer_t *s);
31+
bool cam_streamer_enqueue_client(cam_streamer_t *s, int fd);
32+
33+
#endif

esp32-cam-webserver.ino

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
#include "time.h"
1010
#include <ESPmDNS.h>
1111

12-
1312
/* This sketch is a extension/expansion/reork of the 'official' ESP32 Camera example
1413
* sketch from Expressif:
1514
* https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer

0 commit comments

Comments
 (0)