Files
OpenIris-ESPIDF/components/StreamServer/StreamServer/StreamServer.cpp
2025-04-24 22:05:37 +02:00

135 lines
3.9 KiB
C++

#include "StreamServer.hpp"
constexpr static const char *STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
constexpr static const char *STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
constexpr static const char *STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %lli.%06li\r\n\r\n";
static const char *STREAM_SERVER_TAG = "[STREAM_SERVER]";
StreamServer::StreamServer(const int STREAM_PORT, StateManager *stateManager) : STREAM_SERVER_PORT(STREAM_PORT), stateManager(stateManager)
{
}
esp_err_t StreamHelpers::stream(httpd_req_t *req)
{
long last_request_time = 0;
camera_fb_t *fb = nullptr;
struct timeval _timestamp;
esp_err_t response = ESP_OK;
size_t _jpg_buf_len = 0;
uint8_t *_jpg_buf = nullptr;
char *part_buf[256];
static int64_t last_frame = 0;
if (!last_frame)
last_frame = esp_timer_get_time();
response = httpd_resp_set_type(req, STREAM_CONTENT_TYPE);
if (response != ESP_OK)
return response;
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_hdr(req, "X-Framerate", "60");
while (true)
{
fb = esp_camera_fb_get();
if (!fb)
{
ESP_LOGE(STREAM_SERVER_TAG, "Camera capture failed with resposne %s", esp_err_to_name(response));
response = ESP_FAIL;
}
else
{
_timestamp.tv_sec = fb->timestamp.tv_sec;
_timestamp.tv_usec = fb->timestamp.tv_usec;
_jpg_buf_len = fb->len;
_jpg_buf = fb->buf;
}
if (response == ESP_OK)
response = httpd_resp_send_chunk(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
if (response == ESP_OK)
{
size_t hlen = snprintf((char *)part_buf, 128, STREAM_PART, _jpg_buf_len, _timestamp.tv_sec, _timestamp.tv_usec);
response = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
}
if (response == ESP_OK)
response = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
if (fb)
{
esp_camera_fb_return(fb);
fb = NULL;
_jpg_buf = NULL;
}
else if (_jpg_buf)
{
free(_jpg_buf);
_jpg_buf = NULL;
}
if (response != ESP_OK)
break;
long request_end = Helpers::getTimeInMillis();
long latency = (request_end - last_request_time);
last_request_time = request_end;
ESP_LOGI(STREAM_SERVER_TAG, "Size: %uKB, Time: %lims (%lifps)\n", _jpg_buf_len / 1024, latency, 1000 / latency);
}
last_frame = 0;
return response;
}
esp_err_t StreamHelpers::ws_logs_handle(httpd_req_t *req)
{
auto ret = webSocketLogger.register_socket_client(req);
return ret;
}
esp_err_t StreamServer::startStreamServer()
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.stack_size = 20480;
// todo bring this back to 1 once we're done with logs over websockets
config.max_uri_handlers = 10;
config.server_port = STREAM_SERVER_PORT;
config.ctrl_port = STREAM_SERVER_PORT;
config.recv_wait_timeout = 100;
config.send_wait_timeout = 100;
httpd_uri_t stream_page = {
.uri = "/",
.method = HTTP_GET,
.handler = &StreamHelpers::stream,
.user_ctx = nullptr,
};
httpd_uri_t logs_ws = {
.uri = "/ws",
.method = HTTP_GET,
.handler = &StreamHelpers::ws_logs_handle,
.user_ctx = nullptr,
.is_websocket = true,
};
int status = httpd_start(&camera_stream, &config);
if (status != ESP_OK)
{
ESP_LOGE(STREAM_SERVER_TAG, "Cannot start stream server.");
return status;
}
httpd_register_uri_handler(camera_stream, &logs_ws);
if (this->stateManager->GetCameraState() != CameraState_e::Camera_Success)
{
ESP_LOGE(STREAM_SERVER_TAG, "Camera not initialized. Cannot start stream server. Logs server will be running.");
return ESP_FAIL;
}
httpd_register_uri_handler(camera_stream, &stream_page);
ESP_LOGI(STREAM_SERVER_TAG, "Stream server started on port %d", STREAM_SERVER_PORT);
// todo add printing IP addr here
return ESP_OK;
}