11#include " streamServer.hpp"
22
3- constexpr static const char *STREAM_CONTENT_TYPE = " multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
4- constexpr static const char *STREAM_BOUNDARY = " \r\n --" PART_BOUNDARY " \r\n " ;
5- constexpr static const char *STREAM_PART = " Content-Type: image/jpeg\r\n Content-Length: %u\r\n X-Timestamp: %d.%06d\r\n\r\n " ;
3+ constexpr static const char * STREAM_CONTENT_TYPE =
4+ " multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
5+ constexpr static const char * STREAM_BOUNDARY = " \r\n --" PART_BOUNDARY " \r\n " ;
6+ constexpr static const char * STREAM_PART =
7+ " Content-Type: image/jpeg\r\n Content-Length: %u\r\n X-Timestamp: "
8+ " %d.%06d\r\n\r\n " ;
69
7- esp_err_t StreamHelpers::stream (httpd_req_t *req)
8- {
9- long last_request_time = 0 ;
10- camera_fb_t *fb = NULL ;
11- struct timeval _timestamp;
10+ esp_err_t StreamHelpers::stream (httpd_req_t * req) {
11+ long last_request_time = 0 ;
12+ camera_fb_t * fb = NULL ;
13+ struct timeval _timestamp;
1214
13- esp_err_t res = ESP_OK;
15+ esp_err_t res = ESP_OK;
1416
15- size_t _jpg_buf_len = 0 ;
16- uint8_t * _jpg_buf = NULL ;
17+ size_t _jpg_buf_len = 0 ;
18+ uint8_t * _jpg_buf = NULL ;
1719
18- char * part_buf[256 ];
20+ char * part_buf[256 ];
1921
20- static int64_t last_frame = 0 ;
21- if (!last_frame)
22- last_frame = esp_timer_get_time ();
22+ static int64_t last_frame = 0 ;
23+ if (!last_frame)
24+ last_frame = esp_timer_get_time ();
2325
24- res = httpd_resp_set_type (req, STREAM_CONTENT_TYPE);
25- if (res != ESP_OK)
26- return res;
27-
28- httpd_resp_set_hdr (req, " Access-Control-Allow-Origin" , " *" );
29- httpd_resp_set_hdr (req, " X-Framerate" , " 60" );
30-
31- while (true )
32- {
33- fb = esp_camera_fb_get ();
34- if (!fb)
35- {
36- log_e (" Camera capture failed with response: %s" , esp_err_to_name (res));
37- res = ESP_FAIL;
38- }
39- else
40- {
41- _timestamp.tv_sec = fb->timestamp .tv_sec ;
42- _timestamp.tv_usec = fb->timestamp .tv_usec ;
43- _jpg_buf_len = fb->len ;
44- _jpg_buf = fb->buf ;
45- }
46- if (res == ESP_OK)
47- res = httpd_resp_send_chunk (req, STREAM_BOUNDARY, strlen (STREAM_BOUNDARY));
48- if (res == ESP_OK)
49- {
50- size_t hlen = snprintf ((char *)part_buf, 128 , STREAM_PART, _jpg_buf_len, _timestamp.tv_sec , _timestamp.tv_usec );
51- res = httpd_resp_send_chunk (req, (const char *)part_buf, hlen);
52- }
53- if (res == ESP_OK)
54- res = httpd_resp_send_chunk (req, (const char *)_jpg_buf, _jpg_buf_len);
55- if (fb)
56- {
57- esp_camera_fb_return (fb);
58- fb = NULL ;
59- _jpg_buf = NULL ;
60- }
61- else if (_jpg_buf)
62- {
63- free (_jpg_buf);
64- _jpg_buf = NULL ;
65- }
66- if (res != ESP_OK)
67- break ;
68- long request_end = millis ();
69- long latency = (request_end - last_request_time);
70- last_request_time = request_end;
71- log_d (" Size: %uKB, Time: %ums (%ifps)\n " , _jpg_buf_len / 1024 , latency, 1000 / latency);
72- }
73- last_frame = 0 ;
26+ res = httpd_resp_set_type (req, STREAM_CONTENT_TYPE);
27+ if (res != ESP_OK)
7428 return res;
29+
30+ httpd_resp_set_hdr (req, " Access-Control-Allow-Origin" , " *" );
31+ httpd_resp_set_hdr (req, " X-Framerate" , " 60" );
32+
33+ while (true ) {
34+ fb = esp_camera_fb_get ();
35+ if (!fb) {
36+ log_e (" Camera capture failed with response: %s" , esp_err_to_name (res));
37+ res = ESP_FAIL;
38+ } else {
39+ _timestamp.tv_sec = fb->timestamp .tv_sec ;
40+ _timestamp.tv_usec = fb->timestamp .tv_usec ;
41+ _jpg_buf_len = fb->len ;
42+ _jpg_buf = fb->buf ;
43+ }
44+ if (res == ESP_OK)
45+ res =
46+ httpd_resp_send_chunk (req, STREAM_BOUNDARY, strlen (STREAM_BOUNDARY));
47+ if (res == ESP_OK) {
48+ size_t hlen = snprintf ((char *)part_buf, 128 , STREAM_PART, _jpg_buf_len,
49+ _timestamp.tv_sec , _timestamp.tv_usec );
50+ res = httpd_resp_send_chunk (req, (const char *)part_buf, hlen);
51+ }
52+ if (res == ESP_OK)
53+ res = httpd_resp_send_chunk (req, (const char *)_jpg_buf, _jpg_buf_len);
54+ if (fb) {
55+ esp_camera_fb_return (fb);
56+ fb = NULL ;
57+ _jpg_buf = NULL ;
58+ } else if (_jpg_buf) {
59+ free (_jpg_buf);
60+ _jpg_buf = NULL ;
61+ }
62+ if (res != ESP_OK)
63+ break ;
64+ long request_end = millis ();
65+ long latency = (request_end - last_request_time);
66+ last_request_time = request_end;
67+ log_d (" Size: %uKB, Time: %ums (%ifps)\n " , _jpg_buf_len / 1024 , latency,
68+ 1000 / latency);
69+ }
70+ last_frame = 0 ;
71+ return res;
7572}
7673
77- StreamServer::StreamServer (const int STREAM_PORT) : STREAM_SERVER_PORT(STREAM_PORT) {}
78-
79- int StreamServer::startStreamServer ()
80- {
81- // WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //! Turn-off the 'brownout detector'
82- httpd_config_t config = HTTPD_DEFAULT_CONFIG ();
83- config. stack_size = 20480 ;
84- config.max_uri_handlers = 1 ;
85- config.server_port = this -> STREAM_SERVER_PORT ;
86- config.ctrl_port = this ->STREAM_SERVER_PORT ;
87- config.stack_size = 20480 ;
88-
89- httpd_uri_t stream_page = {
90- .uri = " /" ,
91- .method = HTTP_GET,
92- .handler = &StreamHelpers::stream,
93- .user_ctx = nullptr };
94-
95- int status = httpd_start (&camera_stream, &config);
96-
97- if (status != ESP_OK)
98- return -1 ;
99- else
100- {
101- httpd_register_uri_handler (camera_stream, &stream_page );
102- Serial. println ( " Stream server initialized " );
103- switch (wifiStateManager. getCurrentState ())
104- {
105- case WiFiState_e::WiFiState_ADHOC:
106- Serial. printf ( " \n\r The stream is under: http://%s:%i \n\r " , WiFi. softAPIP (). toString (). c_str (), this ->STREAM_SERVER_PORT );
107- break ;
108- default :
109- Serial.printf (" \n\r The stream is under: http://%s:%i\n\r " , WiFi. localIP (). toString (). c_str (), this -> STREAM_SERVER_PORT );
110- break ;
111- }
112- return 0 ;
74+ StreamServer::StreamServer (const int STREAM_PORT)
75+ : STREAM_SERVER_PORT(STREAM_PORT) {
76+ memcpy (initial_packet_buffer, ETVR_HEADER, sizeof (ETVR_HEADER));
77+ }
78+
79+ int StreamServer::startStreamServer () {
80+ httpd_config_t config = HTTPD_DEFAULT_CONFIG () ;
81+ config.stack_size = 20480 ;
82+ config.max_uri_handlers = 1 ;
83+ config.server_port = this ->STREAM_SERVER_PORT ;
84+ config.ctrl_port = this -> STREAM_SERVER_PORT ;
85+ config. stack_size = 20480 ;
86+
87+ httpd_uri_t stream_page = { .uri = " /" ,
88+ .method = HTTP_GET,
89+ .handler = &StreamHelpers::stream,
90+ .user_ctx = nullptr };
91+
92+ int status = httpd_start (&camera_stream, &config);
93+
94+ if (status != ESP_OK)
95+ return -1 ;
96+ else {
97+ httpd_register_uri_handler (camera_stream, &stream_page);
98+ Serial. println ( " Stream server initialized " );
99+ switch (wifiStateManager. getCurrentState ()) {
100+ case WiFiState_e::WiFiState_ADHOC:
101+ Serial. printf ( " \n\r The stream is under: http://%s:%i \n\r " ,
102+ WiFi. softAPIP (). toString (). c_str (),
103+ this ->STREAM_SERVER_PORT );
104+ break ;
105+ default :
106+ Serial.printf (" \n\r The stream is under: http://%s:%i\n\r " ,
107+ WiFi. localIP (). toString (). c_str (),
108+ this -> STREAM_SERVER_PORT );
109+ break ;
113110 }
111+ return 0 ;
112+ }
113+ }
114+
115+ bool StreamServer::startUDPStreamServer () {
116+ socket = AsyncUDP ();
117+ return socket.listen (this ->STREAM_SERVER_PORT + 1 );
118+ }
119+
120+ void StreamServer::sendUDPFrame () {
121+ // /////////////////////////////////////////////////////
122+ // /////////////////////////////////////////////////////
123+ // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
124+ //
125+ // ADD PROTOCOL VERSION
126+ //
127+ // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
128+ // /////////////////////////////////////////////////////
129+ // /////////////////////////////////////////////////////
130+
131+ if (!last_frame)
132+ last_frame = esp_timer_get_time ();
133+
134+ size_t len = 0 ;
135+ uint8_t * buf = NULL ;
136+
137+ auto fb = esp_camera_fb_get ();
138+ if (fb) {
139+ len = fb->len ;
140+ buf = fb->buf ;
141+ } else {
142+ log_e (" Camera capture failed" );
143+ return ;
144+ }
145+
146+ // we're sending the initial header with the total number of chunks first
147+ // we can then later detect new frame with the header packets
148+ uint8_t totalChunks = (len + CHUNK_SIZE - 1 ) / CHUNK_SIZE;
149+ initial_packet_buffer[sizeof (ETVR_HEADER)] = totalChunks;
150+ socket.broadcastTo (initial_packet_buffer, sizeof (initial_packet_buffer),
151+ this ->STREAM_SERVER_PORT );
152+
153+ for (uint8_t i = 0 ; i < totalChunks; i++) {
154+ auto offset = i * CHUNK_SIZE;
155+ // we need to make sure we don't overread
156+ auto chunkSize = (offset + CHUNK_SIZE <= len) ? CHUNK_SIZE : len - offset;
157+ packet_buffer[0 ] = static_cast <uint8_t >(i);
158+ // since this is a pointer, we can just add an offset to it, with a
159+ // chunksize to read and we're done
160+ memcpy (packet_buffer + 1 , buf + offset, chunkSize);
161+ socket.broadcastTo (packet_buffer, chunkSize + 1 , this ->STREAM_SERVER_PORT );
162+ }
163+
164+ if (fb) {
165+ esp_camera_fb_return (fb);
166+ fb = NULL ;
167+ buf = NULL ;
168+ } else if (buf) {
169+ free (buf);
170+ buf = NULL ;
171+ }
172+
173+ long request_end = millis ();
174+ long latency = request_end - last_request_time;
175+ last_request_time = request_end;
176+
177+ log_d (" Size: %uKB, Time: %ums (%ifps) chunks: %u \n " , len / 1024 , latency,
178+ 1000 / latency, totalChunks);
114179}
180+
181+ bool StreamServer::startTCPStreamServer () {
182+ tcp_server = new AsyncServer (this ->STREAM_SERVER_PORT );
183+ tcp_server->onClient (
184+ [this ](void * arg, AsyncClient* client) {
185+ this ->handleNewTCPClient (arg, client);
186+ },
187+ tcp_server);
188+
189+ tcp_server->begin ();
190+ return true ;
191+ }
192+
193+ void StreamServer::handleNewTCPClient (void * arg, AsyncClient* client) {
194+ Serial.printf (" Client connecting with ip: %s \n\r " ,
195+ client->remoteIP ().toString ().c_str ());
196+
197+ if (this ->tcp_connected_client == nullptr ) {
198+ this ->tcp_connected_client = client;
199+
200+ this ->tcp_connected_client ->onError (
201+ [this ](void * arg, AsyncClient* client, int8_t error) {
202+ Serial.printf (" \n connection error %s from client %s \n " ,
203+ client->errorToString (error),
204+ client->remoteIP ().toString ().c_str ());
205+
206+ this ->tcp_connected_client = nullptr ;
207+ });
208+
209+ this ->tcp_connected_client ->onDisconnect (
210+ [this ](void * arg, AsyncClient* client) {
211+ this ->tcp_connected_client = nullptr ;
212+ });
213+
214+ Serial.println (" Client connected!" );
215+ } else {
216+ client->close ();
217+ Serial.println (" Rejected client, only one connection allowed!" );
218+ }
219+ }
220+
221+ void StreamServer::sendTCPFrame () {
222+ if (this ->tcp_connected_client == nullptr ||
223+ !this ->tcp_connected_client ->connected ()) {
224+ return ;
225+ }
226+
227+ if (!last_frame)
228+ last_frame = esp_timer_get_time ();
229+
230+ auto fb = esp_camera_fb_get ();
231+ if (!fb) {
232+ log_e (" Camera capture failed" );
233+ return ;
234+ }
235+
236+ size_t len = fb->len ;
237+
238+ this ->tcp_connected_client ->write (ETVR_HEADER_BYTES, 4 );
239+ this ->tcp_connected_client ->write ((const char *)fb->buf , fb->len );
240+
241+ if (fb) {
242+ esp_camera_fb_return (fb);
243+ }
244+
245+ long request_end = millis ();
246+ long latency = request_end - last_request_time;
247+ last_request_time = request_end;
248+ log_d (" Size: %uKB, Time: %ums (%ifps)\n " , len / 1024 , latency,
249+ 1000 / latency);
250+ }
0 commit comments