Skip to content

Commit

Permalink
Merge pull request easytarget#195 from 15498th/framerate_limit
Browse files Browse the repository at this point in the history
* Add framerate limit, print delay in debug dump.
* Add global variable and #define in config  to set default.
* Add new parameter in /control handler and preferences.
* Add max fps control in full index page for ovh2640.
* Add max fps control for ov3660 index page.
* Update API documentation.
* Use better name for framerate limit variables.
* Change UI to show the same values of framerate control as used in command interface.
* Apply framerate limiting delay after serving content boundary.

Putting delay after finishing serving the frame causes image to appear
in stream with a lag. The reason for this appears to be that browser
relies on next frame content boundary in order to determine the end of
image.

That means that in order for browser to show frame right after receiving
it, server needs to send next frame content boundary right after image
itself, and delay to limit framerate should be applied after content
boundary, not before it.

According to https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
sending first frame without content boundary will likely cause its
content to be ignored, which seems to be acceptable price for not
treating first iteration of the loop as special case.

* Fix delay before first frame by sending first request boundary before
entering the loop.

Co-authored-by: 15498th <user@localhost>
Co-authored-by: Owen Carter <owen.carter@gmail.com>
  • Loading branch information
easytarget and 15498th authored Mar 9, 2022
2 parents d064e88 + cfe81b0 commit 9426f3f
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 7 deletions.
1 change: 1 addition & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Call `/control?var=<key>&val=<val>` with a settings key and value to set camera
```
lamp - Lamp value in percent; integer, 0 - 100 (-1 = disabled)
framesize - See below
min_frame_time - Minimal frame duration in ms, used to limit max FPS. Must be positive integer
quality - 10 to 63 (ov3660: 4 to 10)
contrast - -2 to 2 (ov3660: -3 to 3)
brightness - -2 to 2 (ov3660: -3 to 3)
Expand Down
22 changes: 16 additions & 6 deletions app_httpd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ extern int8_t streamCount;
extern unsigned long streamsServed;
extern unsigned long imagesServed;
extern int myRotation;
extern int minFrameTime;
extern int lampVal;
extern bool autoLamp;
extern bool filesystem;
Expand Down Expand Up @@ -249,6 +250,10 @@ static esp_err_t stream_handler(httpd_req_t *req){

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

if(res == ESP_OK){
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}

while(true){
fb = esp_camera_fb_get();
if (!fb) {
Expand All @@ -263,16 +268,16 @@ static esp_err_t stream_handler(httpd_req_t *req){
_jpg_buf = fb->buf;
}
}
if(res == ESP_OK){
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
if(res == ESP_OK){
size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
}
if(res == ESP_OK){
res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
}
if(res == ESP_OK){
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
if(fb){
esp_camera_fb_return(fb);
fb = NULL;
Expand All @@ -287,13 +292,16 @@ static esp_err_t stream_handler(httpd_req_t *req){
break;
}
int64_t frame_time = esp_timer_get_time() - last_frame;
last_frame = esp_timer_get_time();;
frame_time /= 1000;
int32_t frame_delay = (minFrameTime > frame_time) ? minFrameTime - frame_time : 0;
delay(frame_delay);

if (debugData) {
Serial.printf("MJPG: %uB %ums (%.1ffps)\r\n",
Serial.printf("MJPG: %uB %ums, delay: %ums, framerate (%.1ffps)\r\n",
(uint32_t)(_jpg_buf_len),
(uint32_t)frame_time, 1000.0 / (uint32_t)frame_time);
(uint32_t)frame_time, frame_delay, 1000.0 / (uint32_t)(frame_time + frame_delay));
}
last_frame = esp_timer_get_time();
}

streamsServed++;
Expand Down Expand Up @@ -369,6 +377,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, "autolamp") && (lampVal != -1)) {
autoLamp = val;
if (autoLamp) {
Expand Down Expand Up @@ -425,6 +434,7 @@ static esp_err_t status_handler(httpd_req_t *req){
*p++ = '{';
p+=sprintf(p, "\"lamp\":%d,", lampVal);
p+=sprintf(p, "\"autolamp\":%d,", autoLamp);
p+=sprintf(p, "\"min_frame_time\":%d,", minFrameTime);
p+=sprintf(p, "\"framesize\":%u,", s->status.framesize);
p+=sprintf(p, "\"quality\":%u,", s->status.quality);
p+=sprintf(p, "\"xclk\":%u,", xclk);
Expand Down
6 changes: 6 additions & 0 deletions esp32-cam-webserver.ino
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ char myVer[] PROGMEM = __DATE__ " @ " __TIME__;
#endif
int myRotation = CAM_ROTATION;

// minimal frame duration in ms, effectively 1/maxFPS
#if !defined(MIN_FRAME_TIME)
#define MIN_FRAME_TIME 0
#endif
int minFrameTime = MIN_FRAME_TIME;

// Illumination LAMP and status LED
#if defined(LAMP_DISABLE)
int lampVal = -1; // lamp is disabled in config
Expand Down
20 changes: 20 additions & 0 deletions index_ov2640.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,20 @@ const uint8_t index_ov2640_html[] = R"=====(<!doctype html>
<label class="slider" for="colorbar"></label>
</div>
</div>
<div class="input-group" id="min_frame_time-group">
<label for="min_frame_time">Frame Duration Limit</label>
<select id="min_frame_time" class="default-action">
<option value="3333">3333ms (0.3fps)</option>
<option value="2000">2000ms (0.5fps)</option>
<option value="1000">1000ms (1fps)</option>
<option value="500">500ms (2fps)</option>
<option value="333">333ms (3fps)</option>
<option value="200">200ms (5fps)</option>
<option value="100">100ms (10fps)</option>
<option value="50">50ms (20fps)</option>
<option value="0" selected="selected">Disabled</option>
</select>
</div>
<div class="input-group" id="preferences-group">
<label for="prefs" style="line-height: 2em;">Preferences</label>
<button id="reboot" title="Reboot the camera module">Reboot</button>
Expand Down Expand Up @@ -304,6 +318,7 @@ const uint8_t index_ov2640_html[] = R"=====(<!doctype html>
const savePrefsButton = document.getElementById('save_prefs')
const clearPrefsButton = document.getElementById('clear_prefs')
const rebootButton = document.getElementById('reboot')
const minFrameTime = document.getElementById('min_frame_time')

const hide = el => {
el.classList.add('hidden')
Expand Down Expand Up @@ -367,6 +382,8 @@ const uint8_t index_ov2640_html[] = R"=====(<!doctype html>
} else if(el.id === "rotate"){
rotate.value = value;
applyRotation();
} else if(el.id === "min_frame_time"){
min_frame_time.value = value;
} else if(el.id === "stream_url"){
streamURL = value;
viewerURL = value + 'view';
Expand Down Expand Up @@ -571,6 +588,9 @@ const uint8_t index_ov2640_html[] = R"=====(<!doctype html>
updateConfig(framesize)
}

minFrameTime.onchange = () => {
updateConfig(minFrameTime)

xclk.onchange = () => {
console.log("xclk:" , xclk);
updateConfig(xclk)
Expand Down
20 changes: 20 additions & 0 deletions index_ov3660.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,20 @@ const uint8_t index_ov3660_html[] = R"=====(<!doctype html>
<label class="slider" for="colorbar"></label>
</div>
</div>
<div class="input-group" id="min_frame_time-group">
<label for="min_frame_time">Frame Duration Limit</label>
<select id="min_frame_time" class="default-action">
<option value="3333">3333ms (0.3fps)</option>
<option value="2000">2000ms (0.5fps)</option>
<option value="1000">1000ms (1fps)</option>
<option value="500">500ms (2fps)</option>
<option value="333">333ms (3fps)</option>
<option value="200">200ms (5fps)</option>
<option value="100">100ms (10fps)</option>
<option value="50">50ms (20fps)</option>
<option value="0" selected="selected">Disabled</option>
</select>
</div>
<div class="input-group" id="preferences-group">
<label for="prefs" style="line-height: 2em;">Preferences</label>
<button id="reboot" title="Reboot the camera module">Reboot</button>
Expand Down Expand Up @@ -318,6 +332,7 @@ const uint8_t index_ov3660_html[] = R"=====(<!doctype html>
const savePrefsButton = document.getElementById('save_prefs')
const clearPrefsButton = document.getElementById('clear_prefs')
const rebootButton = document.getElementById('reboot')
const minFrameTime = document.getElementById('min_frame_time')

const hide = el => {
el.classList.add('hidden')
Expand Down Expand Up @@ -379,6 +394,8 @@ const uint8_t index_ov3660_html[] = R"=====(<!doctype html>
} else if(el.id === "rotate"){
rotate.value = value;
applyRotation();
} else if(el.id === "min_frame_time"){
min_frame_time.value = value;
} else if(el.id === "stream_url"){
streamURL = value;
viewerURL = value + 'view';
Expand Down Expand Up @@ -580,6 +597,9 @@ const uint8_t index_ov3660_html[] = R"=====(<!doctype html>
updateConfig(framesize)
}

minFrameTime.onchange = () => {
updateConfig(minFrameTime)

xclk.onchange = () => {
console.log("xclk:" , xclk);
updateConfig(xclk)
Expand Down
3 changes: 3 additions & 0 deletions myconfig.sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ struct station stationList[] = {{"ssid1", "pass1", true},
// Browser Rotation (one of: -90,0,90, default 0)
// #define CAM_ROTATION 0

// Minimal frame duration in ms, used to limit max FPS
// max_fps = 1000/min_frame_time
// #define MIN_FRAME_TIME 500

/*
* Additional Features
Expand Down
4 changes: 3 additions & 1 deletion storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ void loadPrefs(fs::FS &fs){
sensor_t * s = esp_camera_sensor_get();
// process all the settings
lampVal = jsonExtract(prefs, "lamp").toInt();
if (jsonExtract(prefs, "autolamp").toInt() == 0) autoLamp = false; else autoLamp = true;
if (jsonExtract(prefs, "autolamp").toInt() == 0) autoLamp = false; else autoLamp = true;
minFrameTime = jsonExtract(prefs, "min_frame_time").toInt();
s->set_framesize(s, (framesize_t)jsonExtract(prefs, "framesize").toInt());
s->set_quality(s, jsonExtract(prefs, "quality").toInt());
int xclkPref = jsonExtract(prefs, "xclk").toInt();
Expand Down Expand Up @@ -140,6 +141,7 @@ void savePrefs(fs::FS &fs){
*p++ = '{';
p+=sprintf(p, "\"lamp\":%i,", lampVal);
p+=sprintf(p, "\"autolamp\":%u,", autoLamp);
p+=sprintf(p, "\"min_frame_time\":%d,", minFrameTime);
p+=sprintf(p, "\"framesize\":%u,", s->status.framesize);
p+=sprintf(p, "\"quality\":%u,", s->status.quality);
p+=sprintf(p, "\"xclk\":%u,", xclk);
Expand Down

0 comments on commit 9426f3f

Please sign in to comment.