From 26d1af2f2851bdd0deb7164febbaa78943d4266a Mon Sep 17 00:00:00 2001 From: Rene Zeldenthuis Date: Tue, 13 Feb 2024 01:23:40 +0100 Subject: [PATCH] Work in progress --- ...sp_source_camera.h => micro_rtsp_camera.h} | 8 +- .../include/micro_rtsp_jpeg.h | 19 ++ .../include/micro_rtsp_requests.h | 53 ++++ .../include/micro_rtsp_server.h | 69 ++--- .../include/micro_rtsp_source.h | 15 -- .../include/micro_rtssp_streamer.h | 6 + lib/micro-rtsp-server/rtsp_server.cpp | 53 ---- lib/micro-rtsp-server/rtsp_server.h | 39 --- ...ource_camera.cpp => micro_rtsp_camera.cpp} | 14 +- lib/micro-rtsp-server/src/micro_rtsp_jpeg.cpp | 107 ++++++++ .../src/micro_rtsp_requests.cpp | 213 ++++++++++++++++ .../src/micro_rtsp_server.cpp | 240 ++---------------- .../src/micro_rtssp_streamer.cpp | 1 + src/main.cpp | 35 ++- 14 files changed, 472 insertions(+), 400 deletions(-) rename lib/micro-rtsp-server/include/{micro_rtsp_source_camera.h => micro_rtsp_camera.h} (73%) create mode 100644 lib/micro-rtsp-server/include/micro_rtsp_jpeg.h create mode 100644 lib/micro-rtsp-server/include/micro_rtsp_requests.h delete mode 100644 lib/micro-rtsp-server/include/micro_rtsp_source.h create mode 100644 lib/micro-rtsp-server/include/micro_rtssp_streamer.h delete mode 100644 lib/micro-rtsp-server/rtsp_server.cpp delete mode 100644 lib/micro-rtsp-server/rtsp_server.h rename lib/micro-rtsp-server/src/{micro_rtsp_source_camera.cpp => micro_rtsp_camera.cpp} (53%) create mode 100644 lib/micro-rtsp-server/src/micro_rtsp_jpeg.cpp create mode 100644 lib/micro-rtsp-server/src/micro_rtsp_requests.cpp create mode 100644 lib/micro-rtsp-server/src/micro_rtssp_streamer.cpp diff --git a/lib/micro-rtsp-server/include/micro_rtsp_source_camera.h b/lib/micro-rtsp-server/include/micro_rtsp_camera.h similarity index 73% rename from lib/micro-rtsp-server/include/micro_rtsp_source_camera.h rename to lib/micro-rtsp-server/include/micro_rtsp_camera.h index 72b2d6f..9800d70 100644 --- a/lib/micro-rtsp-server/include/micro_rtsp_source_camera.h +++ b/lib/micro-rtsp-server/include/micro_rtsp_camera.h @@ -1,14 +1,12 @@ #pragma once -#include - #include -class micro_rtsp_source_camera : public micro_rtsp_source +class micro_rtsp_camera { public: - micro_rtsp_source_camera(); - virtual ~micro_rtsp_source_camera(); + micro_rtsp_camera(); + virtual ~micro_rtsp_camera(); esp_err_t initialize(camera_config_t *camera_config); esp_err_t deinitialize(); diff --git a/lib/micro-rtsp-server/include/micro_rtsp_jpeg.h b/lib/micro-rtsp-server/include/micro_rtsp_jpeg.h new file mode 100644 index 0000000..dc1e640 --- /dev/null +++ b/lib/micro-rtsp-server/include/micro_rtsp_jpeg.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +class micro_rtsp_jpeg +{ +public: + micro_rtsp_jpeg(const uint8_t *jpeg, size_t size); + +protected: + bool decode_jpeg(uint8_t *jpg, size_t size); + std::tuple find_jpeg_section(uint8_t **ptr, uint8_t *end, uint8_t flag); + + std::tuple quantization_table_0_; + std::tuple quantization_table_1_; + std::tuple jpeg_data_; +}; \ No newline at end of file diff --git a/lib/micro-rtsp-server/include/micro_rtsp_requests.h b/lib/micro-rtsp-server/include/micro_rtsp_requests.h new file mode 100644 index 0000000..0a876bc --- /dev/null +++ b/lib/micro-rtsp-server/include/micro_rtsp_requests.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +class micro_rtsp_requests +{ +public: + std::string process_request(const std::string& request); + +private: + enum rtsp_command + { + rtsp_command_unknown, + rtsp_command_option, // OPTIONS + rtsp_command_describe, // DESCRIBE + rtsp_command_setup, // SETUP + rtsp_command_play, // PLAY + rtsp_command_teardown // TEARDOWN + }; + + const std::string available_stream_name_ = "mjpeg/1"; + + rtsp_command parse_command(const std::string &request); + bool parse_client_port(const std::string &request); + bool parse_cseq(const std::string &request); + bool parse_stream_url(const std::string &request); + + std::string date_header(); + std::string rtsp_error(unsigned short code, const std::string& message); + + std::string handle_option(const std::string &request); + std::string handle_describe(const std::string &request); + std::string handle_setup(const std::string &request); + std::string handle_play(const std::string &request); + std::string handle_teardown(const std::string &request); + + unsigned long cseq_; + + std::string host_url_; + unsigned short host_port_; + std::string stream_name_; + + bool tcp_transport_; + unsigned short client_port_; + + unsigned short rtp_streamer_port_; + unsigned short rtcp_streamer_port_; + + unsigned long rtsp_session_id_; + + bool stream_active_; + bool stream_stopped_; +}; \ No newline at end of file diff --git a/lib/micro-rtsp-server/include/micro_rtsp_server.h b/lib/micro-rtsp-server/include/micro_rtsp_server.h index a9492da..d30983e 100644 --- a/lib/micro-rtsp-server/include/micro_rtsp_server.h +++ b/lib/micro-rtsp-server/include/micro_rtsp_server.h @@ -1,74 +1,43 @@ #pragma once +#include #include -#include -#include + +#include #include -class micro_rtsp_server : public WiFiServer +#include "micro_rtsp_camera.h" +#include "micro_rtsp_requests.h" + +class micro_rtsp_server : WiFiServer { public: - micro_rtsp_server(micro_rtsp_source *source, unsigned frame_interval = 100, unsigned short port = 554); + micro_rtsp_server(const micro_rtsp_camera& source, unsigned frame_interval = 100, unsigned short port = 554); ~micro_rtsp_server(); + unsigned get_frame_interval() { return frame_interval_; } + unsigned set_frame_interval(unsigned value) { return frame_interval_ = value; } + void loop(); - unsigned get_frame_interval() { return frame_interval_; } - unsigned set_frame_interval(unsigned value) { return frame_interval_ = value; } - size_t clients() { return clients_.size(); } - class rtsp_client + class rtsp_client : public WiFiClient, public micro_rtsp_requests { public: - rtsp_client(const WiFiClient &wifi_client); + rtsp_client(const WiFiClient &client); + ~rtsp_client(); + void handle_request(); - - private: - enum rtsp_command - { - rtsp_command_unknown, - rtsp_command_option, // OPTIONS - rtsp_command_describe, // DESCRIBE - rtsp_command_setup, // SETUP - rtsp_command_play, // PLAY - rtsp_command_teardown // TEARDOWN - }; - - rtsp_command parse_command(const String& request); - unsigned long parse_cseq(const String& request); - bool parse_stream_url(const String& request); - - int parse_client_port(const String& request); - - - WiFiClient wifi_client_; - - bool tcp_transport_; - - String host_url_; - unsigned short host_port_; - String stream_name_; - uint stream_id_; - - unsigned short rtp_port_; - // enum rtsp_state state_; - - String date_header(); - - void handle_option(unsigned long cseq); - void handle_describe(unsigned long cseq, const String& stream_url); - void handle_setup(unsigned long cseq, const String& stream_url); - void handle_play(); - void handle_teardown(); }; private: - micro_rtsp_source *source_; + const micro_rtsp_camera &source_; + + unsigned frame_interval_; - unsigned frame_interval_; unsigned long next_frame_update_; unsigned long next_check_client_; - std::list clients_; + std::list clients_; }; \ No newline at end of file diff --git a/lib/micro-rtsp-server/include/micro_rtsp_source.h b/lib/micro-rtsp-server/include/micro_rtsp_source.h deleted file mode 100644 index 699285c..0000000 --- a/lib/micro-rtsp-server/include/micro_rtsp_source.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include - -class micro_rtsp_source -{ -public: - virtual void update_frame(); - - virtual uint8_t *data() const = 0; - virtual size_t width() const = 0; - virtual size_t height() const = 0; - virtual size_t size() const = 0; -}; diff --git a/lib/micro-rtsp-server/include/micro_rtssp_streamer.h b/lib/micro-rtsp-server/include/micro_rtssp_streamer.h new file mode 100644 index 0000000..0b7c316 --- /dev/null +++ b/lib/micro-rtsp-server/include/micro_rtssp_streamer.h @@ -0,0 +1,6 @@ +#pragma once + +class micro_rtsp_streamer +{ +public: +}; \ No newline at end of file diff --git a/lib/micro-rtsp-server/rtsp_server.cpp b/lib/micro-rtsp-server/rtsp_server.cpp deleted file mode 100644 index 4308299..0000000 --- a/lib/micro-rtsp-server/rtsp_server.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "rtsp_server.h" -#include - - -// URI: e.g. rtsp://192.168.178.27:554/mjpeg/1 -rtsp_server::rtsp_server(OV2640 &cam, unsigned long interval, int port /*= 554*/) - : WiFiServer(port), cam_(cam) -{ - log_i("Starting RTSP server"); - _streamer = new OV2640Streamer() - WiFiServer::begin(); - timer_.every(interval, client_handler, this); -} - -size_t rtsp_server::num_connected() -{ - return clients_.size(); -} - -void rtsp_server::doLoop() -{ - timer_.tick(); -} - -rtsp_server::rtsp_client::rtsp_client(const WiFiClient &client, OV2640 &cam) -{ - wifi_client = client; - streamer = std::shared_ptr(new OV2640Streamer(&wifi_client, cam)); - session = std::shared_ptr(new CRtspSession(&wifi_client, streamer.get())); -} - -bool rtsp_server::client_handler(void *arg) -{ - auto self = static_cast(arg); - // Check if a client wants to connect - auto new_client = self->accept(); - if (new_client) - self->clients_.push_back(std::unique_ptr(new rtsp_client(new_client, self->cam_))); - - auto now = millis(); - for (const auto &client : self->clients_) - { - // Handle requests - client->session->handleRequests(0); - // Send the frame. For now return the uptime as time marker, currMs - client->session->broadcastCurrentFrame(now); - } - - self->clients_.remove_if([](std::unique_ptr const &c) - { return c->session->m_stopped; }); - - return true; -} \ No newline at end of file diff --git a/lib/micro-rtsp-server/rtsp_server.h b/lib/micro-rtsp-server/rtsp_server.h deleted file mode 100644 index e287b3a..0000000 --- a/lib/micro-rtsp-server/rtsp_server.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -class rtsp_server : public WiFiServer -{ -public: - rtsp_server(OV2640 &cam, unsigned long interval, int port = 554); - - void doLoop(); - - size_t num_connected(); - -private: - struct rtsp_client - { - public: - rtsp_client(const WiFiClient &client, OV2640 &cam); - - WiFiClient wifi_client; - // RTSP session and state - std::shared_ptr session; - }; - - OV2640 cam_; - OV2640Streamer _streamer; - - std::list> clients_; - uintptr_t task_; - Timer<> timer_; - - static bool client_handler(void *); -}; \ No newline at end of file diff --git a/lib/micro-rtsp-server/src/micro_rtsp_source_camera.cpp b/lib/micro-rtsp-server/src/micro_rtsp_camera.cpp similarity index 53% rename from lib/micro-rtsp-server/src/micro_rtsp_source_camera.cpp rename to lib/micro-rtsp-server/src/micro_rtsp_camera.cpp index f819c41..945731c 100644 --- a/lib/micro-rtsp-server/src/micro_rtsp_source_camera.cpp +++ b/lib/micro-rtsp-server/src/micro_rtsp_camera.cpp @@ -1,18 +1,18 @@ -#include +#include -#include "micro_rtsp_source_camera.h" +#include "micro_rtsp_camera.h" -micro_rtsp_source_camera::micro_rtsp_source_camera() +micro_rtsp_camera::micro_rtsp_camera() { init_result == ESP_FAIL; } -micro_rtsp_source_camera::~micro_rtsp_source_camera() +micro_rtsp_camera::~micro_rtsp_camera() { deinitialize(); } -esp_err_t micro_rtsp_source_camera::initialize(camera_config_t *camera_config) +esp_err_t micro_rtsp_camera::initialize(camera_config_t *camera_config) { init_result = esp_camera_init(camera_config); @@ -22,12 +22,12 @@ esp_err_t micro_rtsp_source_camera::initialize(camera_config_t *camera_config) log_e("Camera initialization failed: 0x%x", init_result); } -esp_err_t micro_rtsp_source_camera::deinitialize() +esp_err_t micro_rtsp_camera::deinitialize() { return init_result == ESP_OK ? esp_camera_deinit() : ESP_OK; } -void micro_rtsp_source_camera::update_frame() +void micro_rtsp_camera::update_frame() { if (fb) esp_camera_fb_return(fb); diff --git a/lib/micro-rtsp-server/src/micro_rtsp_jpeg.cpp b/lib/micro-rtsp-server/src/micro_rtsp_jpeg.cpp new file mode 100644 index 0000000..2391d16 --- /dev/null +++ b/lib/micro-rtsp-server/src/micro_rtsp_jpeg.cpp @@ -0,0 +1,107 @@ +#include + +#include "micro_rtsp_jpeg.h" + +micro_rtsp_jpeg::micro_rtsp_jpeg(const uint8_t *jpeg, size_t size) +{ +} + +std::tuple micro_rtsp_jpeg::find_jpeg_section(uint8_t **ptr, uint8_t *end, uint8_t flag) +{ + size_t len; + while (*ptr < end) + { + auto framing = *(*ptr++); + if (framing != 0xff) + { + log_e("Malformed jpeg. Expected framing 0xff but found: 0x%02x", framing); + break; + } + + // framing = 0xff, flag, len MSB, len LSB + auto flag_code = *(*ptr++); + // SOI and EOI have no length + len = flag_code == 0xd8 || flag_code == 0xd9 ? 0 : *(*ptr++) * 256 + *(*ptr++); + if (flag_code == flag) + return std::tuple(*ptr, len); + + // Skip the section + switch (flag_code) + { + case 0xd8: // SOI (start of image) + case 0xd9: // EOI (end of image) + case 0xe0: // APP00 + case 0xdb: // DQT (define quantization table) + case 0xc4: // DHT (define Huffman table) + case 0xc0: // SOF0 (start of frame) + case 0xda: // SOS (start of scan) + { + // length of section + log_d("Skipping jpeg section flag: 0x%02x, %d bytes", flag_code, len); + ptr += len; + break; + } + default: + log_e("Unexpected jpeg flag: 0x%02x", flag_code); + } + } + + // Not found + return std::tuple(nullptr, 0); +} + +// See https://create.stephan-brumme.com/toojpeg/ +bool micro_rtsp_jpeg::decode_jpeg(uint8_t *data, size_t size) +{ + // Look for start jpeg file (0xd8) + auto ptr = data; + auto end = ptr + size; + + // Check for SOI (start of image) 0xff, 0xd8 + auto soi = find_jpeg_section(&ptr, end, 0xd8); + if (std::get<0>(soi) == nullptr) + { + log_e("No valid start of image marker found"); + return false; + } + + // First quantization table + quantization_table_0_ = find_jpeg_section(&ptr, end, 0xdb); + if (std::get<0>(quantization_table_0_) == nullptr) + { + log_e("No quantization table 0 section found"); + } + + // Second quantization table (for color images) + quantization_table_1_ = find_jpeg_section(&ptr, end, 0xdb); + if (std::get<0>(quantization_table_1_) == nullptr) + { + log_e("No quantization table 1 section found"); + } + // Start of scan + auto sos = find_jpeg_section(&ptr, end, 0xda); + if (std::get<0>(sos) == nullptr) + { + log_e("No start of scan section found"); + } + + +// the scan data uses byte stuffing to guarantee anything that starts with 0xff +// followed by something not zero, is a new section. Look for that marker and return the ptr +// pointing there + + // Skip the scan + while (ptr < end - 1 && (*ptr != 0xff || ptr[1] == 0)) + ptr++; + + ptr -= 2; // Go back to the 0xff (marker) + + auto eoi = find_jpeg_section(&ptr, end, 0xd9); + if (std::get<0>(eoi) == nullptr) + { + log_e("No end of image marker found"); + } + + jpeg_data_ = std::tuple(ptr, size - (ptr - data)); + return true; +} diff --git a/lib/micro-rtsp-server/src/micro_rtsp_requests.cpp b/lib/micro-rtsp-server/src/micro_rtsp_requests.cpp new file mode 100644 index 0000000..a258967 --- /dev/null +++ b/lib/micro-rtsp-server/src/micro_rtsp_requests.cpp @@ -0,0 +1,213 @@ +#include + +#include +#include "micro_rtsp_requests.h" + +micro_rtsp_requests::rtsp_command micro_rtsp_requests::parse_command(const std::string &request) +{ + log_i("parse_command"); + // Check for RTSP commands: Option, Describe, Setup, Play, Teardown + + if (std::regex_match(request, std::regex("^OPTION ", std::regex_constants::icase))) + return rtsp_command::rtsp_command_option; + + if (std::regex_match(request, std::regex("^DESCRIBE ", std::regex_constants::icase))) + return rtsp_command::rtsp_command_describe; + + if (std::regex_match(request, std::regex("^SETUP ", std::regex_constants::icase))) + return rtsp_command::rtsp_command_setup; + + if (std::regex_match(request, std::regex("^PLAY ", std::regex_constants::icase))) + return rtsp_command::rtsp_command_play; + + if (std::regex_match(request, std::regex("^TEARDOWN ", std::regex_constants::icase))) + return rtsp_command::rtsp_command_teardown; + + log_e("Invalid rtsp command: %s", request.c_str()); + return rtsp_command::rtsp_command_unknown; +} + +bool micro_rtsp_requests::parse_client_port(const std::string &request) +{ + log_i("parse_client_port"); + std::regex regex("client_port=([0-9]+)", std::regex_constants::icase); + std::smatch match; + if (!std::regex_match(request, match, regex)) + { + log_e("client_port not found"); + return false; + } + + client_port_ = std::stoi(match[1].str()); + return true; +} + +bool micro_rtsp_requests::parse_cseq(const std::string &request) +{ + log_i("parse_cseq"); + // CSeq: 2 + std::regex regex("CSeq: (\\d+)", std::regex_constants::icase); + std::smatch match; + if (!std::regex_match(request, match, regex)) + { + log_e("CSeq not found"); + return false; + } + + cseq_ = std::stoul(match[1].str()); + return true; +} + +bool micro_rtsp_requests::parse_stream_url(const std::string &request) +{ + log_i("parse_host_url"); + // SETUP rtsp://192.168.10.93:1234/mjpeg/1 RTSP/1.0 + std::regex regex("rtsp:\\/\\/([\\w.]+):(\\d+)\\/([\\/\\w]+)\\s+RTSP/1.0", std::regex_constants::icase); + std::smatch match; + if (!std::regex_match(request, match, regex)) + { + log_e("Unable to parse url"); + return false; + } + + host_url_ = match[1].str(); + host_port_ = std::stoi(match[2].str()); + stream_name_ = match[3].str(); + return true; +} + +std::string micro_rtsp_requests::date_header() +{ + char buffer[50]; + auto now = std::time(nullptr); + std::strftime(buffer, sizeof(buffer), "Date: %a, %b %d %Y %H:%M:%S GMT", std::gmtime(&now)); + return buffer; +} + +std::string micro_rtsp_requests::rtsp_error(unsigned short code, const std::string &message) +{ + std::ostringstream oss; + oss << "RTSP/1.0 " << code << " " << message << "\r\n" + << "CSeq: " << cseq_ << "\r\n" + << date_header() << "\r\n" + << "\r\n"; + return oss.str(); +} + +std::string micro_rtsp_requests::handle_option(const std::string &request) +{ + log_i("handle_option"); + std::ostringstream oss; + oss << "RTSP/1.0 200 OK\r\n" + << "CSeq: " << cseq_ << "\r\n" + << date_header() << "\r\n" + << "Content-Length: 0\r\n" + << "Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n" + << "\r\n"; + return oss.str(); +} + +std::string micro_rtsp_requests::handle_describe(const std::string &request) +{ + log_i("handle_describe"); + if (!parse_stream_url(request)) + return rtsp_error(400, "Invalid Stream Name"); + + if (stream_name_ != available_stream_name_) + return rtsp_error(404, "Stream Not Found"); + + std::ostringstream sdpos; + sdpos << "v=0\r\n" + << "o=- " << host_port_ << " 1 IN IP4 " << std::rand() << "\r\n" + << "s=\r\n" + << "t=0 0\r\n" // start / stop - 0 -> unbounded and permanent session + << "m=video 0 RTP/AVP 26\r\n" // currently we just handle UDP sessions + << "c=IN IP4 0.0.0.0\r\n"; + auto sdp = sdpos.str(); + + std::ostringstream oss; + oss << "RTSP/1.0 200 OK\r\n" + << "CSeq: " << cseq_ << "\r\n" + << date_header() << "\r\n" + << "Content-Base: " << stream_name_ << "/\r\n" + << "Content-Type: application/sdp\r\n" + << "Content-Length: " << sdp.size() << "\r\n" + << "\r\n" + << sdp; + return oss.str(); +} + +std::string micro_rtsp_requests::handle_setup(const std::string &request) +{ + tcp_transport_ = request.find("rtp/avp/tcp") != std::string::npos; + + if (!parse_client_port(request)) + return rtsp_error(400, "Could Not Find Client Port"); + + std::ostringstream ostransport; + if (tcp_transport_) + ostransport << "RTP/AVP/TCP;unicast;interleaved=0-1"; + else + ostransport << "RTP/AVP;unicast;destination=127.0.0.1;source=127.0.0.1;client_port=" << client_port_ << "-" << client_port_ + 1 << ";server_port=" << rtp_streamer_port_ << "-" << rtcp_streamer_port_; + + std::ostringstream oss; + oss << "RTSP/1.0 200 OK\r\n" + << "CSeq: " << cseq_ << "\r\n" + << date_header() << "\r\n" + << "Transport: " << ostransport.str() << "\r\n" + << "Session: " << rtsp_session_id_ << "\r\n" + << "\r\n"; + return oss.str(); +} + +std::string micro_rtsp_requests::handle_play(const std::string &request) +{ + stream_active_ = true; + std::ostringstream oss; + oss << "RTSP/1.0 200 OK\r\n" + << "CSeq: " << cseq_ << "\r\n" + << date_header() << "\r\n" + << "Range: npt=0.000-\r\n" + << "Session: " << rtsp_session_id_ << "\r\n" + << "RTP-Info: url=rtsp://127.0.0.1:8554/" << available_stream_name_ << "/track1\r\n" + << "\r\n"; + return oss.str(); +} + +std::string micro_rtsp_requests::handle_teardown(const std::string &request) +{ + stream_stopped_ = true; + std::ostringstream oss; + oss << "RTSP/1.0 200 OK\r\n" + << "CSeq: " << cseq_ << "\r\n" + << date_header() << "\r\n" + << "\r\n"; + return oss.str(); +} + +std::string micro_rtsp_requests::process_request(const std::string &request) +{ + log_i("handle_request"); + auto command = parse_command(request); + if (command != rtsp_command_unknown) + { + if (!parse_cseq(request)) + return rtsp_error(400, "No Sequence Found"); + + switch (command) + { + case rtsp_command_option: + return handle_option(request); + case rtsp_command_describe: + return handle_describe(request); + case rtsp_command_setup: + return handle_setup(request); + case rtsp_command_play: + return handle_play(request); + case rtsp_command_teardown: + return handle_teardown(request); + } + } + + return rtsp_error(400, "Unknown Command"); +} diff --git a/lib/micro-rtsp-server/src/micro_rtsp_server.cpp b/lib/micro-rtsp-server/src/micro_rtsp_server.cpp index 9a46c0c..9fdf770 100644 --- a/lib/micro-rtsp-server/src/micro_rtsp_server.cpp +++ b/lib/micro-rtsp-server/src/micro_rtsp_server.cpp @@ -4,18 +4,19 @@ // Check client connections every 100 milliseconds #define CHECK_CLIENT_INTERVAL 100 +#define STREAM_NAME "mjpeg/1" -micro_rtsp_server::micro_rtsp_server(micro_rtsp_source *source, unsigned frame_interval /*= 100*/, unsigned short port /*= 554*/) +micro_rtsp_server::micro_rtsp_server(const micro_rtsp_camera &source, unsigned frame_interval /*= 100*/, unsigned short port /*= 554*/) + : source_(source) { log_i("starting RTSP server"); - frame_interval_ = frame_interval; - source_ = source; - WiFiServer::begin(); + begin(port); } micro_rtsp_server::~micro_rtsp_server() { + end(); } void micro_rtsp_server::loop() @@ -29,241 +30,54 @@ void micro_rtsp_server::loop() // Check if a client wants to connect auto client = accept(); if (client) - clients_.push_back(new rtsp_client(client)); + clients_.push_back(rtsp_client(client)); // Check for idle clients - // clients_.remove_if([](std::unique_ptr const &c) - // { return c->session->m_stopped; }); + clients_.remove_if([](rtsp_client &c) + { return !c.connected(); }); + + for (auto client : clients_) + client.handle_request(); } if (next_frame_update_ < now) { next_frame_update_ = now + frame_interval_; - - for (const auto &client : clients_) + for (auto client : clients_) { - // Handle requests - client->handle_request(); - // Send the frame. For now return the uptime as time marker, currMs + ; // client->session->broadcastCurrentFrame(now); } } } micro_rtsp_server::rtsp_client::rtsp_client(const WiFiClient &wifi_client) + : WiFiClient(wifi_client) { - wifi_client_ = wifi_client; - // state_ = rtsp_state_unknown; + log_i("rtsp_client"); } -micro_rtsp_server::rtsp_client::rtsp_command micro_rtsp_server::rtsp_client::parse_command(const String &request) +micro_rtsp_server::rtsp_client::~rtsp_client() { - log_i("parse_command"); - // Check for RTSP commands: Option, Describe, Setup, Play, Teardown - if (request.startsWith("option")) - return rtsp_command::rtsp_command_option; - - if (request.startsWith("describe")) - return rtsp_command::rtsp_command_describe; - - if (request.startsWith("setup")) - return rtsp_command::rtsp_command_setup; - - if (request.startsWith("play")) - return rtsp_command::rtsp_command_play; - - if (request.startsWith("teardown")) - return rtsp_command::rtsp_command_teardown; - - return rtsp_command::rtsp_command_unknown; -} - -int micro_rtsp_server::rtsp_client::parse_client_port(const String &request) -{ - log_i("parse_client_port"); - auto pos = request.indexOf("client_port="); - if (pos < 0) - { - log_e("client_port not found"); - return 0; - } - - return strtoul(&request.c_str()[pos + 12], nullptr, 10); -} - -unsigned long micro_rtsp_server::rtsp_client::parse_cseq(const String &request) -{ - log_i("parse_cseq"); - auto pos = request.indexOf("cseq: "); - if (pos < 0) - { - log_e("CSeq not found"); - return 0; - } - - return strtoul(&request.c_str()[pos + 6], nullptr, 10); -} - -bool micro_rtsp_server::rtsp_client::parse_stream_url(const String &request) -{ - log_i("parse_host_url"); - // SETUP rtsp://192.168.10.93:1234/mjpeg/1 RTSP/1.0 - auto pos = request.indexOf("rtsp://"); - if (pos < 0) - { - log_e("rtsp:// not found"); - return false; - } - - // Look for : - auto start = pos + 7; - pos = request.indexOf(':', start); - if (pos < 0) - { - log_e("parse stream: no host url/post found (: missing)"); - return false; - } - - // Should be 192.168.10.93 - host_url_ = request.substring(start, pos); - // Should be 1234 - host_port_ = (unsigned short)(&request.c_str()[pos + 1], nullptr, 10); - - start = pos + 1; - pos = request.indexOf('/', start); - if (pos < 0) - { - log_e("parse stream: no host port found (/ missing)"); - return false; - } - - start = pos + 1; - pos = request.indexOf(' ', start); - if (pos < 0) - { - log_e("parse stream: no stream found (' ' missing)"); - return false; - } - - // should be mjpeg/1 - stream_name_ = request.substring(start, pos); - return true; + log_i("~rtsp_client"); + stop(); } void micro_rtsp_server::rtsp_client::handle_request() { log_i("handle_request"); // Read if data available - auto available = wifi_client_.available(); - if (available > 0) + auto bytes_available = available(); + if (bytes_available > 0) { - auto request = wifi_client_.readString(); - // Make response lowercase - request.toLowerCase(); - - auto command = parse_command(request); - if (command == rtsp_command_unknown) + std::string request; + request.reserve(bytes_available); + if (read((uint8_t *)request.data(), bytes_available) == bytes_available) { - log_w("Invalid RTSP command received: %s", request.c_str()); - return; - } - - unsigned long cseq = parse_cseq(request); - if (cseq == 0) - { - log_w("Invalid sequence: %s", request.c_str()); - return; - } - - switch (command) - { - case rtsp_command_option: - handle_option(cseq); - break; - case rtsp_command_describe: - handle_describe(cseq, request); - break; - case rtsp_command_setup: - handle_setup(cseq, request); - break; - case rtsp_command_play: - handle_play(); - break; - case rtsp_command_teardown: - handle_teardown(); - break; - default: - log_e("unknown rtsp_command"); - assert(false); + log_i("Request: %s", request); + auto response = process_request(request); + log_i("Response: %s", response); + write(response.data(), response.size()); } } } - -String micro_rtsp_server::rtsp_client::date_header() -{ - char buffer[200]; - auto now = time(nullptr); - strftime(buffer, sizeof(buffer), "Date: %a, %b %d %Y %H:%M:%S GMT", gmtime(&now)); - return buffer; -} - -void micro_rtsp_server::rtsp_client::handle_option(unsigned long cseq) -{ - log_i("handle_option"); - auto response = String("RTSP/1.0 200 OK\r\n") + - String("CSeq: ") + String(cseq) + String("\r\n") + - String("Content-Length: 0\r\n") + - String("Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n") + - String("\r\n"); - wifi_client_.write(response.c_str()); -} - -void micro_rtsp_server::rtsp_client::handle_describe(unsigned long cseq, const String &request) -{ - log_i("handle_describe"); - if (!parse_stream_url(request)) - { - log_w("Unable to parse stream url", request.c_str()); - auto response = String("RTSP/1.0 404 Stream Not Found\r\n") + - String("CSeq: ") + String(cseq) + String("\r\n") + - date_header() + String("\r\n"); - wifi_client_.write(response.c_str()); - return; - } - - if (stream_name_ != "mjpeg/1") - { - log_w("stream %s was requested but is not available", stream_name_.c_str()); - auto response = String("RTSP/1.0 404 Stream Not Found\r\n") + - String("CSeq: ") + String(cseq) + String("\r\n") + - date_header() + - String("\r\n"); - wifi_client_.write(response.c_str()); - return; - } - - auto sdp = String("v=0\r\n") + - String("o=- ") + String(host_port_) + String(" 1 IN IP4 ") + String(rand()) + String("\r\n") + - String("s=\r\n") + - String("t=0 0\r\n") + // start / stop - 0 -> unbounded and permanent session - String("m=video 0 RTP/AVP 26\r\n") + // currently we just handle UDP sessions - String("c=IN IP4 0.0.0.0\r\n"); - - auto response = - String("RTSP/1.0 200 OK\r\n") + - String("CSeq: ") + String(cseq) + String("\r\n") + - date_header() + String("\r\n") + - String("Content-Base: ") + stream_name_ + ("/\r\n") + - String("Content-Type: application/sdp\r\n") + - String("Content-Length: ") + String(sdp.length()) + String("\r\n") + - String("\r\n") + - sdp; - - wifi_client_.write(response.c_str()); -} - -void micro_rtsp_server::rtsp_client::handle_setup(unsigned long cseq, const String &request) -{ - tcp_transport_ = request.indexOf("rtp/avp/tcp") >= 0; - -} \ No newline at end of file diff --git a/lib/micro-rtsp-server/src/micro_rtssp_streamer.cpp b/lib/micro-rtsp-server/src/micro_rtssp_streamer.cpp new file mode 100644 index 0000000..e785ed5 --- /dev/null +++ b/lib/micro-rtsp-server/src/micro_rtssp_streamer.cpp @@ -0,0 +1 @@ +#include "micro_rtssp_streamer.h" \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 832e7b8..b17f665 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,8 +16,8 @@ #include #include +#include #include -#include // HTML files extern const char index_html_min_start[] asm("_binary_html_index_min_html_start"); @@ -50,14 +50,15 @@ auto param_dcw = iotwebconf::Builder("dcw").labe auto param_colorbar = iotwebconf::Builder("cb").label("Colorbar").defaultValue(DEFAULT_COLORBAR).build(); // Camera -OV2640 cam; -micro_rtsp_source_camera source_camera; +// OV2640 cam; // DNS Server DNSServer dnsServer; // RTSP Server -std::unique_ptr camera_server; +// std::unique_ptr camera_server; + +micro_rtsp_camera camera; +micro_rtsp_server server(camera); -micro_rtsp_server micro_rtsp_server(&source_camera, RTSP_PORT); // Web server WebServer web_server(80); @@ -106,7 +107,7 @@ void handle_root() {"Uptime", String(format_duration(millis() / 1000))}, {"FreeHeap", format_memory(ESP.getFreeHeap())}, {"MaxAllocHeap", format_memory(ESP.getMaxAllocHeap())}, - {"NumRTSPSessions", camera_server != nullptr ? String(camera_server->num_connected()) : "RTSP server disabled"}, + {"NumRTSPSessions", String(server.clients())}, // Network {"HostName", hostname}, {"MacAddress", WiFi.macAddress()}, @@ -168,10 +169,10 @@ void handle_snapshot() // Remove old images stored in the frame buffer auto frame_buffers = CAMERA_CONFIG_FB_COUNT; while (frame_buffers--) - cam.run(); + camera.update_frame(); - auto fb_len = cam.getSize(); - auto fb = (const char *)cam.getfb(); + auto fb_len = camera.size(); + auto fb = camera.data(); if (fb == nullptr) { web_server.send(404, "text/plain", "Unable to obtain frame buffer from the camera"); @@ -181,7 +182,7 @@ void handle_snapshot() web_server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); web_server.setContentLength(fb_len); web_server.send(200, "image/jpeg", ""); - web_server.sendContent(fb, fb_len); + web_server.sendContent((const char *)fb, fb_len); } #define STREAM_CONTENT_BOUNDARY "123456789000000000000987654321" @@ -203,11 +204,11 @@ void handle_stream() while (client.connected()) { client.write("\r\n--" STREAM_CONTENT_BOUNDARY "\r\n"); - cam.run(); + camera.update_frame(); client.write("Content-Type: image/jpeg\r\nContent-Length: "); - sprintf(size_buf, "%d\r\n\r\n", cam.getSize()); + sprintf(size_buf, "%d\r\n\r\n", camera.size()); client.write(size_buf); - client.write(cam.getfb(), cam.getSize()); + client.write(camera.data(), camera.size()); } log_v("client disconnected"); @@ -227,7 +228,7 @@ esp_err_t initialize_camera() log_i("Frame duration: %d ms", param_frame_duration.value()); // Set frame duration - micro_rtsp_server.set_frame_interval(param_frame_duration.value()); + server.set_frame_interval(param_frame_duration.value()); camera_config_t camera_config = { .pin_pwdn = CAMERA_CONFIG_PIN_PWDN, // GPIO pin for camera power down line @@ -261,7 +262,7 @@ esp_err_t initialize_camera() .sccb_i2c_port = CAMERA_CONFIG_SCCB_I2C_PORT // If pin_sccb_sda is -1, use the already configured I2C bus by number }; - return source_camera.initialize(&camera_config); + return camera.initialize(&camera_config); // return cam.init(camera_config); } @@ -302,7 +303,6 @@ void update_camera_settings() void start_rtsp_server() { log_v("start_rtsp_server"); - camera_server = std::unique_ptr(new rtsp_server(cam, param_frame_duration.value(), RTSP_PORT)); // Add RTSP service to mDNS // HTTP is already set by iotWebConf MDNS.addService("rtsp", "tcp", RTSP_PORT); @@ -413,8 +413,7 @@ void loop() { iotWebConf.doLoop(); - if (camera_server) - camera_server->doLoop(); + server.loop(); sleep(0); } \ No newline at end of file