|
/* |
|
MIT License: |
|
Permission is hereby granted, free of charge, to any person |
|
obtaining a copy of this software and associated documentation |
|
files (the "Software"), to deal in the Software without restriction, |
|
including without limitation the rights to use, copy, modify, |
|
merge, publish, distribute, sublicense, and/or sell copies of |
|
the Software, and to permit persons to whom the Software is |
|
furnished to do so, subject to the following conditions: |
|
|
|
The above copyright notice and this permission notice shall be |
|
included in all copies or substantial portions of the Software. |
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
|
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN |
|
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR |
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
THE SOFTWARE. |
|
|
|
Copyright 2018 Punit Soni <[email protected]> |
|
*/ |
|
|
|
#include <chrono> |
|
#include <fstream> |
|
#include <iostream> |
|
#include <thread> |
|
#include "absl/strings/str_format.h" |
|
#include "opencv2/opencv.hpp" |
|
#include "mongoose.h" |
|
|
|
using std::cout; |
|
using std::endl; |
|
|
|
void GenerateFrame(cv::Mat *img, const std::string &text, int frameid, |
|
int timestamp_ms) { |
|
// clear image |
|
*img = cv::Scalar::all(0); |
|
int baseline = 0; |
|
int thickness = 3; |
|
int fontscale = 6; |
|
auto fontface = cv::HersheyFonts::FONT_HERSHEY_PLAIN; |
|
|
|
// Draw text |
|
cv::Size textsize = |
|
cv::getTextSize(text, fontface, fontscale, thickness, &baseline); |
|
|
|
cv::Point text_origin((img->cols - textsize.width) / 2, |
|
(img->rows + textsize.height) / 2); |
|
|
|
putText(*img, text, text_origin, fontface, fontscale, cv::Scalar::all(255), |
|
thickness, cv::LINE_AA); |
|
|
|
// Draw frame id |
|
fontscale = 2; |
|
thickness = 1; |
|
const std::string frameid_text = absl::StrFormat("Frame %6d", frameid); |
|
cv::Size frameid_text_size = |
|
cv::getTextSize(frameid_text, fontface, fontscale, thickness, nullptr); |
|
cv::Point frameid_text_origin(img->cols - frameid_text_size.width - 10, |
|
frameid_text_size.height + 10); |
|
putText(*img, frameid_text, frameid_text_origin, fontface, fontscale, |
|
cv::Scalar::all(255), thickness, cv::LINE_AA); |
|
|
|
// Draw timestamp |
|
fontscale = 1; |
|
thickness = 1; |
|
const std::string timestamp_text = absl::StrFormat("%8d ms", timestamp_ms); |
|
cv::Size timestamp_text_size = |
|
cv::getTextSize(timestamp_text, fontface, fontscale, thickness, nullptr); |
|
cv::Point timestamp_text_origin(img->cols - timestamp_text_size.width - 10, |
|
img->rows - 10); |
|
putText(*img, timestamp_text, timestamp_text_origin, fontface, fontscale, |
|
cv::Scalar::all(255), thickness, cv::LINE_AA); |
|
} |
|
|
|
void http_handle_root(struct mg_connection *conn, struct http_message *req) { |
|
const std::string res = |
|
"<html><body>" |
|
"<p><h2>Test MJPEG Stream</h2>" |
|
"<img src=\"test_mjpeg\"></p></body></html>"; |
|
|
|
mg_send_head(conn, 200, res.size(), "Content-Type: text/html"); |
|
mg_printf(conn, "%.*s", (int)res.size(), res.c_str()); |
|
} |
|
|
|
class TestFrameGen { |
|
public: |
|
TestFrameGen() : img(cv::Mat::zeros(480, 640, CV_8UC3)) {} |
|
std::vector<uint8_t> GenerateNextFrame() { |
|
int ms_per_min = 1000 * 60; |
|
int ms_per_sec = 1000; |
|
int time_ms = millis; |
|
int time_min = time_ms / ms_per_min; |
|
time_ms = time_ms % ms_per_min; |
|
int time_sec = time_ms / ms_per_sec; |
|
time_ms = time_ms % ms_per_sec; |
|
|
|
const std::string text = |
|
absl::StrFormat("%02d:%02d:%03d", time_min, time_sec, time_ms); |
|
|
|
GenerateFrame(&img, text, frameid, millis); |
|
|
|
// encode the frame to jpeg |
|
std::vector<uint8_t> buffer; |
|
std::vector<int> params; |
|
params.push_back(cv::IMWRITE_JPEG_QUALITY); |
|
params.push_back(80); |
|
cv::imencode(".jpg", img, buffer, params); |
|
millis += int(1000 / fps); |
|
frameid++; |
|
return buffer; |
|
} |
|
|
|
float fps = 30.0; |
|
int millis = 0; |
|
int frameid = 0; |
|
cv::Mat img; |
|
}; |
|
|
|
void http_handle_test_mjpeg(struct mg_connection *conn, |
|
struct http_message *req) { |
|
double lastframe_time = mg_time(); |
|
|
|
mg_printf(conn, |
|
"HTTP/1.1 200 OK\r\n" |
|
"Cache-Control: no-cache\r\n" |
|
"Cache-Control: private\r\n" |
|
"Content-Type: multipart/x-mixed-replace; " |
|
"boundary=frame_boundary\r\n\r\n"); |
|
|
|
TestFrameGen *gen = new TestFrameGen(); |
|
absl::PrintF("sending frame %d\n", gen->frameid); |
|
std::vector<uint8_t> buffer = gen->GenerateNextFrame(); |
|
|
|
mg_printf(conn, |
|
"--frame_boundary\r\n" |
|
"Content-Type: image/jpeg\r\n" |
|
"Content-Length: %lu\r\n\r\n", |
|
buffer.size()); |
|
|
|
mg_send(conn, (const char *)&buffer[0], buffer.size()); |
|
mg_printf(conn, "\r\n"); |
|
|
|
// setup timer |
|
conn->user_data = (void *)gen; |
|
mg_set_timer(conn, lastframe_time + 0.030); // 30 ms |
|
} |
|
|
|
static void event_handler(struct mg_connection *conn, int ev, void *p) { |
|
|
|
if (ev == MG_EV_HTTP_REQUEST) { |
|
struct http_message *req = (struct http_message *)p; |
|
|
|
const std::string request_method(req->method.p, req->method.len); |
|
const std::string request_uri(req->uri.p, req->uri.len); |
|
const std::string request_query(req->query_string.p, req->query_string.len); |
|
const std::string proto(req->proto.p, req->proto.len); |
|
|
|
absl::PrintF("--------------------------------\n"); |
|
absl::PrintF("[HTTP Request]\n"); |
|
absl::PrintF("method: %s\n", request_method); |
|
absl::PrintF("uri: %s\n", request_uri); |
|
absl::PrintF("query: %s\n", request_query); |
|
absl::PrintF("--------------------------------\n"); |
|
absl::PrintF("conn=%p, conn->flags=%x\n", conn, conn->flags); |
|
|
|
if (request_uri == "/") { |
|
return http_handle_root(conn, req); |
|
} |
|
if (request_uri == "/test_mjpeg") { |
|
return http_handle_test_mjpeg(conn, req); |
|
} |
|
} else if (ev == MG_EV_TIMER) { |
|
double lastframe_time = *(double *)p; |
|
TestFrameGen *gen = (TestFrameGen *)(conn->user_data); |
|
|
|
absl::PrintF("sending frame %d\n", gen->frameid); |
|
std::vector<uint8_t> buffer = gen->GenerateNextFrame(); |
|
|
|
mg_printf(conn, |
|
"--frame_boundary\r\n" |
|
"Content-Type: image/jpeg\r\n" |
|
"Content-Length: %lu\r\n\r\n", |
|
buffer.size()); |
|
|
|
mg_send(conn, (const char *)&buffer[0], buffer.size()); |
|
mg_printf(conn, "\r\n"); |
|
mg_set_timer( |
|
conn, |
|
lastframe_time + 0.033); |
|
} |
|
} |
|
|
|
int main(int argc, char **argv) { |
|
cout << "MJPG Streaming server" << endl; |
|
struct mg_mgr mgr; |
|
mg_mgr_init(&mgr, NULL); |
|
|
|
struct mg_connection *conn = mg_bind(&mgr, "8000", event_handler); |
|
|
|
mg_set_protocol_http_websocket(conn); |
|
|
|
cout << "Starting server." << endl; |
|
while (true) { |
|
mg_mgr_poll(&mgr, 1000); |
|
} |
|
mg_mgr_free(&mgr); |
|
return 0; |
|
} |