forked from external-repos/esp32cam-rtsp
Work in progress
This commit is contained in:
@@ -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();
|
||||||
19
lib/micro-rtsp-server/include/micro_rtsp_jpeg.h
Normal file
19
lib/micro-rtsp-server/include/micro_rtsp_jpeg.h
Normal 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_;
|
||||||
|
};
|
||||||
53
lib/micro-rtsp-server/include/micro_rtsp_requests.h
Normal file
53
lib/micro-rtsp-server/include/micro_rtsp_requests.h
Normal 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_;
|
||||||
|
};
|
||||||
@@ -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_;
|
||||||
};
|
};
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
6
lib/micro-rtsp-server/include/micro_rtssp_streamer.h
Normal file
6
lib/micro-rtsp-server/include/micro_rtssp_streamer.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class micro_rtsp_streamer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
};
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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 *);
|
|
||||||
};
|
|
||||||
@@ -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);
|
||||||
107
lib/micro-rtsp-server/src/micro_rtsp_jpeg.cpp
Normal file
107
lib/micro-rtsp-server/src/micro_rtsp_jpeg.cpp
Normal 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;
|
||||||
|
}
|
||||||
213
lib/micro-rtsp-server/src/micro_rtsp_requests.cpp
Normal file
213
lib/micro-rtsp-server/src/micro_rtsp_requests.cpp
Normal 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");
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
1
lib/micro-rtsp-server/src/micro_rtssp_streamer.cpp
Normal file
1
lib/micro-rtsp-server/src/micro_rtssp_streamer.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "micro_rtssp_streamer.h"
|
||||||
35
src/main.cpp
35
src/main.cpp
@@ -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);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user