Skip to content

Commit

Permalink
feat: Fix issue with streaming as httpd_resp_send_chunk breaks stream…
Browse files Browse the repository at this point in the history
…ing.
  • Loading branch information
seaniefs committed Mar 5, 2022
1 parent 4b6cd9a commit cde3497
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 16 deletions.
80 changes: 65 additions & 15 deletions app_httpd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,33 @@ static esp_err_t capture_handler(httpd_req_t *req){
return res;
}

static int sendData(httpd_req_t *req, const char *buf, const size_t len) {
size_t index = 0;
int res = 0;
int attempts = 0;
const int maxAttempts = 100;
do {
res = httpd_send(req, &buf[index], len - index);
if ((res >= 0 && res < (len - index)) || res == HTTPD_SOCK_ERR_TIMEOUT) {
delay(10);
attempts++;
if (res == HTTPD_SOCK_ERR_TIMEOUT && attempts < maxAttempts) {
res = 0;
}
}
if (res >= 0) {
index += res;
}
}
while(res >= 0 && index < len && attempts < maxAttempts);
if (index < len && attempts >= maxAttempts) {
res = -1;
}
return res;
}

static esp_err_t stream_handler(httpd_req_t *req){
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL;
char * part_buf[64];
Expand All @@ -235,39 +259,65 @@ static esp_err_t stream_handler(httpd_req_t *req){
last_frame = esp_timer_get_time();
}

res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if(res != ESP_OK){
const char *httpd_chunked_hdr_str = "HTTP/1.1 %s%sContent-Type: %s%s";
const char *colon_separator = ": ";
const char *cr_lf_seperator = "\r\n";
static char buffer[256 + 1];
const int headerSize = snprintf(buffer, sizeof(buffer) - 1, httpd_chunked_hdr_str, "200 OK", cr_lf_seperator, _STREAM_CONTENT_TYPE, cr_lf_seperator);
int res = sendData(req, buffer, headerSize);
if (res != headerSize) {
// TODO: Make a lambda so as we can re-use...
streamCount = 0;
if (autoLamp && (lampVal != -1)) setLamp(0);
Serial.println("STREAM: failed to set HTTP response type");
return res;
}
const int originSize = snprintf(buffer, sizeof(buffer) - 1, "Access-Control-Allow-Origin: *%s", cr_lf_seperator);
res = sendData(req, buffer, originSize);
if (res != originSize) {
// TODO: Make a lambda so as we can re-use...
streamCount = 0;
if (autoLamp && (lampVal != -1)) setLamp(0);
Serial.println("STREAM: failed to set HTTP response type");
return res;
}

httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");

while(true){
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("STREAM: failed to acquire frame");
res = ESP_FAIL;
res = -1;
} else {
if(fb->format != PIXFORMAT_JPEG){
Serial.println("STREAM: Non-JPEG frame returned by camera module");
res = ESP_FAIL;
res = -2;
} else {
_jpg_buf_len = fb->len;
_jpg_buf = fb->buf;
}
}
if(res == ESP_OK){
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
if(res > 0){
res = sendData(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
if (res != strlen(_STREAM_BOUNDARY)) {
res = -3;
}
}
if(res == ESP_OK){
if(res > 0){
size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
res = sendData(req, (const char *)part_buf, hlen);
if (res != hlen) {
res = -4;
}
}
if(res == ESP_OK){
res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
if(res > 0){
yield();
res = sendData(req, (const char *)_jpg_buf, _jpg_buf_len);
if (res != _jpg_buf_len) {
Serial.printf("Failed sending chunk of [%d] error: [%d]\n", _jpg_buf_len, res);
Serial.print(_STREAM_BOUNDARY);
Serial.printf((char *)part_buf);
res = -5;
}
}
if(fb){
esp_camera_fb_return(fb);
Expand All @@ -277,7 +327,7 @@ static esp_err_t stream_handler(httpd_req_t *req){
free(_jpg_buf);
_jpg_buf = NULL;
}
if(res != ESP_OK){
if(res < 0){
// This is the only exit point from the stream loop.
// We end the stream here only if a Hard failure has been encountered or the connection has been interrupted.
break;
Expand All @@ -297,7 +347,7 @@ static esp_err_t stream_handler(httpd_req_t *req){
if (autoLamp && (lampVal != -1)) setLamp(0);
Serial.println("Stream ended");
last_frame = 0;
return res;
return res < 0 ? ESP_FAIL : ESP_OK;
}

static esp_err_t cmd_handler(httpd_req_t *req){
Expand Down
2 changes: 1 addition & 1 deletion esp32-cam-webserver.ino
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ void setup() {
debugOff();
#endif
} else {
Serial.printf("\r\nCamera unavailable due to initialisation errors.\r\n\r\n");
Serial.printf("\r\nCamera unavailable due to initialisation errors [%s].\r\n\r\n", critERR.c_str());
}

// Used when dumping status; these are slow functions, so just do them once during startup
Expand Down
4 changes: 4 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ platform_packages = framework-arduinoespressif32@https://github.com/espressif/ar
board = esp32dev
board_build.partitions = min_spiffs.csv
framework = arduino
upload_port = /dev/ttyUSB0
upload_speed = 460800
monitor_port = /dev/ttyUSB0
monitor_speed = 115200
build_flags =
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
Expand Down

3 comments on commit cde3497

@easytarget
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting.. does it work?
I've been failing to fix this, but I'd really like to get PlatformIO operation fixed if I can

@seaniefs
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly is more reliable for me on VLC and Firefox than it was - might not be entirely bulletproof, but it's better...

Chrome still doesn't like it.

PlatformIO builds under vscode works for me, but YMMV, I guess...

@easytarget
Copy link

@easytarget easytarget commented on cde3497 Mar 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thankyou for the quick reply, sorry mine is much slower.
I'm going to look at porting this to my fork after I've dealt with some conflicting changes related to multiclient streaming that are in progress (easytarget#237) . These involve running the streams in parallel RTOS processes, it refactors the stream code and I'm curious to see how that plays out in PlatformIO, but adapting your changes to add a chunk fail/resend mechanism is also a good idea anyway.

Please sign in to comment.