Work in progress

This commit is contained in:
Rene Zeldenthuis
2024-02-13 01:23:40 +01:00
parent 97fc4ceb33
commit 26d1af2f28
14 changed files with 472 additions and 400 deletions

View File

@@ -1,14 +1,12 @@
#pragma once #pragma once
#include <micro_rtsp_source.h>
#include <esp_camera.h> #include <esp_camera.h>
class micro_rtsp_source_camera : public micro_rtsp_source class micro_rtsp_camera
{ {
public: public:
micro_rtsp_source_camera(); micro_rtsp_camera();
virtual ~micro_rtsp_source_camera(); virtual ~micro_rtsp_camera();
esp_err_t initialize(camera_config_t *camera_config); esp_err_t initialize(camera_config_t *camera_config);
esp_err_t deinitialize(); esp_err_t deinitialize();

View File

@@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <tuple>
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<const uint8_t *, size_t> find_jpeg_section(uint8_t **ptr, uint8_t *end, uint8_t flag);
std::tuple<const uint8_t *, size_t> quantization_table_0_;
std::tuple<const uint8_t *, size_t> quantization_table_1_;
std::tuple<const uint8_t *, size_t> jpeg_data_;
};

View File

@@ -0,0 +1,53 @@
#pragma once
#include <string>
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_;
};

View File

@@ -1,74 +1,43 @@
#pragma once #pragma once
#include <Arduino.h>
#include <WiFiServer.h> #include <WiFiServer.h>
#include <micro_rtsp_source.h>
#include <vector> #include <string>
#include <list> #include <list>
class micro_rtsp_server : public WiFiServer #include "micro_rtsp_camera.h"
#include "micro_rtsp_requests.h"
class micro_rtsp_server : WiFiServer
{ {
public: 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(); ~micro_rtsp_server();
unsigned get_frame_interval() { return frame_interval_; }
unsigned set_frame_interval(unsigned value) { return frame_interval_ = value; }
void loop(); 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(); } size_t clients() { return clients_.size(); }
class rtsp_client class rtsp_client : public WiFiClient, public micro_rtsp_requests
{ {
public: public:
rtsp_client(const WiFiClient &wifi_client); rtsp_client(const WiFiClient &client);
~rtsp_client();
void handle_request(); 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: private:
micro_rtsp_source *source_; const micro_rtsp_camera &source_;
unsigned frame_interval_;
unsigned frame_interval_;
unsigned long next_frame_update_; unsigned long next_frame_update_;
unsigned long next_check_client_; unsigned long next_check_client_;
std::list<rtsp_client *> clients_; std::list<rtsp_client> clients_;
}; };

View File

@@ -1,15 +0,0 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
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;
};

View File

@@ -0,0 +1,6 @@
#pragma once
class micro_rtsp_streamer
{
public:
};

View File

@@ -1,53 +0,0 @@
#include "rtsp_server.h"
#include <esp32-hal-log.h>
// 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<OV2640Streamer>(new OV2640Streamer(&wifi_client, cam));
session = std::shared_ptr<CRtspSession>(new CRtspSession(&wifi_client, streamer.get()));
}
bool rtsp_server::client_handler(void *arg)
{
auto self = static_cast<rtsp_server *>(arg);
// Check if a client wants to connect
auto new_client = self->accept();
if (new_client)
self->clients_.push_back(std::unique_ptr<rtsp_client>(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<rtsp_client> const &c)
{ return c->session->m_stopped; });
return true;
}

View File

@@ -1,39 +0,0 @@
#pragma once
#include <list>
#include <WiFiServer.h>
#include <ESPmDNS.h>
#include <OV2640.h>
#include <CRtspSession.h>
#include <arduino-timer.h>
#include <OV2640Streamer.h>
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<CRtspSession> session;
};
OV2640 cam_;
OV2640Streamer _streamer;
std::list<std::unique_ptr<rtsp_client>> clients_;
uintptr_t task_;
Timer<> timer_;
static bool client_handler(void *);
};

View File

@@ -1,18 +1,18 @@
#include <Arduino.h> #include <esp32-hal-log.h>
#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; init_result == ESP_FAIL;
} }
micro_rtsp_source_camera::~micro_rtsp_source_camera() micro_rtsp_camera::~micro_rtsp_camera()
{ {
deinitialize(); 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); 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); 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; 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) if (fb)
esp_camera_fb_return(fb); esp_camera_fb_return(fb);

View File

@@ -0,0 +1,107 @@
#include <esp32-hal-log.h>
#include "micro_rtsp_jpeg.h"
micro_rtsp_jpeg::micro_rtsp_jpeg(const uint8_t *jpeg, size_t size)
{
}
std::tuple<const uint8_t *, size_t> 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<const uint8_t *, size_t>(*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<const uint8_t *, size_t>(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<const uint8_t *, size_t>(ptr, size - (ptr - data));
return true;
}

View File

@@ -0,0 +1,213 @@
#include <esp32-hal-log.h>
#include <regex>
#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");
}

View File

@@ -4,18 +4,19 @@
// Check client connections every 100 milliseconds // Check client connections every 100 milliseconds
#define CHECK_CLIENT_INTERVAL 100 #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"); log_i("starting RTSP server");
frame_interval_ = frame_interval; frame_interval_ = frame_interval;
source_ = source; begin(port);
WiFiServer::begin();
} }
micro_rtsp_server::~micro_rtsp_server() micro_rtsp_server::~micro_rtsp_server()
{ {
end();
} }
void micro_rtsp_server::loop() void micro_rtsp_server::loop()
@@ -29,241 +30,54 @@ void micro_rtsp_server::loop()
// Check if a client wants to connect // Check if a client wants to connect
auto client = accept(); auto client = accept();
if (client) if (client)
clients_.push_back(new rtsp_client(client)); clients_.push_back(rtsp_client(client));
// Check for idle clients // Check for idle clients
// clients_.remove_if([](std::unique_ptr<rtsp_client> const &c) clients_.remove_if([](rtsp_client &c)
// { return c->session->m_stopped; }); { return !c.connected(); });
for (auto client : clients_)
client.handle_request();
} }
if (next_frame_update_ < now) if (next_frame_update_ < now)
{ {
next_frame_update_ = now + frame_interval_; next_frame_update_ = now + frame_interval_;
for (auto client : clients_)
for (const auto &client : clients_)
{ {
// Handle requests ;
client->handle_request();
// Send the frame. For now return the uptime as time marker, currMs
// client->session->broadcastCurrentFrame(now); // client->session->broadcastCurrentFrame(now);
} }
} }
} }
micro_rtsp_server::rtsp_client::rtsp_client(const WiFiClient &wifi_client) micro_rtsp_server::rtsp_client::rtsp_client(const WiFiClient &wifi_client)
: WiFiClient(wifi_client)
{ {
wifi_client_ = wifi_client; log_i("rtsp_client");
// state_ = rtsp_state_unknown;
} }
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"); log_i("~rtsp_client");
// Check for RTSP commands: Option, Describe, Setup, Play, Teardown stop();
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;
} }
void micro_rtsp_server::rtsp_client::handle_request() void micro_rtsp_server::rtsp_client::handle_request()
{ {
log_i("handle_request"); log_i("handle_request");
// Read if data available // Read if data available
auto available = wifi_client_.available(); auto bytes_available = available();
if (available > 0) if (bytes_available > 0)
{ {
auto request = wifi_client_.readString(); std::string request;
// Make response lowercase request.reserve(bytes_available);
request.toLowerCase(); if (read((uint8_t *)request.data(), bytes_available) == bytes_available)
auto command = parse_command(request);
if (command == rtsp_command_unknown)
{ {
log_w("Invalid RTSP command received: %s", request.c_str()); log_i("Request: %s", request);
return; auto response = process_request(request);
} log_i("Response: %s", response);
write(response.data(), response.size());
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);
} }
} }
} }
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;
}

View File

@@ -0,0 +1 @@
#include "micro_rtssp_streamer.h"

View File

@@ -16,8 +16,8 @@
#include <moustache.h> #include <moustache.h>
#include <settings.h> #include <settings.h>
#include <micro_rtsp_camera.h>
#include <micro_rtsp_server.h> #include <micro_rtsp_server.h>
#include <micro_rtsp_source_camera.h>
// HTML files // HTML files
extern const char index_html_min_start[] asm("_binary_html_index_min_html_start"); extern const char index_html_min_start[] asm("_binary_html_index_min_html_start");
@@ -50,14 +50,15 @@ auto param_dcw = iotwebconf::Builder<iotwebconf::CheckboxTParameter>("dcw").labe
auto param_colorbar = iotwebconf::Builder<iotwebconf::CheckboxTParameter>("cb").label("Colorbar").defaultValue(DEFAULT_COLORBAR).build(); auto param_colorbar = iotwebconf::Builder<iotwebconf::CheckboxTParameter>("cb").label("Colorbar").defaultValue(DEFAULT_COLORBAR).build();
// Camera // Camera
OV2640 cam; // OV2640 cam;
micro_rtsp_source_camera source_camera;
// DNS Server // DNS Server
DNSServer dnsServer; DNSServer dnsServer;
// RTSP Server // RTSP Server
std::unique_ptr<rtsp_server> camera_server; // std::unique_ptr<rtsp_server> camera_server;
micro_rtsp_camera camera;
micro_rtsp_server server(camera);
micro_rtsp_server micro_rtsp_server(&source_camera, RTSP_PORT);
// Web server // Web server
WebServer web_server(80); WebServer web_server(80);
@@ -106,7 +107,7 @@ void handle_root()
{"Uptime", String(format_duration(millis() / 1000))}, {"Uptime", String(format_duration(millis() / 1000))},
{"FreeHeap", format_memory(ESP.getFreeHeap())}, {"FreeHeap", format_memory(ESP.getFreeHeap())},
{"MaxAllocHeap", format_memory(ESP.getMaxAllocHeap())}, {"MaxAllocHeap", format_memory(ESP.getMaxAllocHeap())},
{"NumRTSPSessions", camera_server != nullptr ? String(camera_server->num_connected()) : "RTSP server disabled"}, {"NumRTSPSessions", String(server.clients())},
// Network // Network
{"HostName", hostname}, {"HostName", hostname},
{"MacAddress", WiFi.macAddress()}, {"MacAddress", WiFi.macAddress()},
@@ -168,10 +169,10 @@ void handle_snapshot()
// Remove old images stored in the frame buffer // Remove old images stored in the frame buffer
auto frame_buffers = CAMERA_CONFIG_FB_COUNT; auto frame_buffers = CAMERA_CONFIG_FB_COUNT;
while (frame_buffers--) while (frame_buffers--)
cam.run(); camera.update_frame();
auto fb_len = cam.getSize(); auto fb_len = camera.size();
auto fb = (const char *)cam.getfb(); auto fb = camera.data();
if (fb == nullptr) if (fb == nullptr)
{ {
web_server.send(404, "text/plain", "Unable to obtain frame buffer from the camera"); 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.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
web_server.setContentLength(fb_len); web_server.setContentLength(fb_len);
web_server.send(200, "image/jpeg", ""); 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" #define STREAM_CONTENT_BOUNDARY "123456789000000000000987654321"
@@ -203,11 +204,11 @@ void handle_stream()
while (client.connected()) while (client.connected())
{ {
client.write("\r\n--" STREAM_CONTENT_BOUNDARY "\r\n"); client.write("\r\n--" STREAM_CONTENT_BOUNDARY "\r\n");
cam.run(); camera.update_frame();
client.write("Content-Type: image/jpeg\r\nContent-Length: "); 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(size_buf);
client.write(cam.getfb(), cam.getSize()); client.write(camera.data(), camera.size());
} }
log_v("client disconnected"); log_v("client disconnected");
@@ -227,7 +228,7 @@ esp_err_t initialize_camera()
log_i("Frame duration: %d ms", param_frame_duration.value()); log_i("Frame duration: %d ms", param_frame_duration.value());
// Set frame duration // 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 = { camera_config_t camera_config = {
.pin_pwdn = CAMERA_CONFIG_PIN_PWDN, // GPIO pin for camera power down line .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 .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); // return cam.init(camera_config);
} }
@@ -302,7 +303,6 @@ void update_camera_settings()
void start_rtsp_server() void start_rtsp_server()
{ {
log_v("start_rtsp_server"); log_v("start_rtsp_server");
camera_server = std::unique_ptr<rtsp_server>(new rtsp_server(cam, param_frame_duration.value(), RTSP_PORT));
// Add RTSP service to mDNS // Add RTSP service to mDNS
// HTTP is already set by iotWebConf // HTTP is already set by iotWebConf
MDNS.addService("rtsp", "tcp", RTSP_PORT); MDNS.addService("rtsp", "tcp", RTSP_PORT);
@@ -413,8 +413,7 @@ void loop()
{ {
iotWebConf.doLoop(); iotWebConf.doLoop();
if (camera_server) server.loop();
camera_server->doLoop();
sleep(0); sleep(0);
} }