forked from external-repos/esp32cam-rtsp
first commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
10
.vscode/extensions.json
vendored
Normal file
10
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
],
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
"ms-vscode.cpptools-extension-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
39
include/README
Normal file
39
include/README
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the usual convention is to give header files names that end with `.h'.
|
||||||
|
It is most portable to use only letters, digits, dashes, and underscores in
|
||||||
|
header file names, and at most one dot.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||||
12
include/esp32cam.h
Normal file
12
include/esp32cam.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define APP_TITLE "ESP32CAM-RTSP"
|
||||||
|
#define APP_VERSION "1.0"
|
||||||
|
|
||||||
|
#define WIFI_SSID "ESP32CAM-RTSP"
|
||||||
|
#define WIFI_PASSWORD nullptr
|
||||||
|
#define CONFIG_VERSION "1.1"
|
||||||
|
|
||||||
|
#define RTSP_PORT 554
|
||||||
|
#define DEFAULT_FRAMERATE "20"
|
||||||
|
#define DEFAULT_FRAMESIZE "SVGA (800x600)"
|
||||||
53
include/frame_size.h
Normal file
53
include/frame_size.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <sensor.h>
|
||||||
|
|
||||||
|
typedef char frame_size_name_t[18];
|
||||||
|
|
||||||
|
typedef struct frame_size_entry
|
||||||
|
{
|
||||||
|
frame_size_name_t name;
|
||||||
|
framesize_t frame_size;
|
||||||
|
} frame_size_entry_t;
|
||||||
|
|
||||||
|
constexpr const frame_size_entry_t frame_sizes[] = {
|
||||||
|
|
||||||
|
{"96x96", FRAMESIZE_96X96},
|
||||||
|
{"QQVGA (160x120)", FRAMESIZE_QQVGA},
|
||||||
|
{"QCIF (176x144)", FRAMESIZE_QCIF},
|
||||||
|
{"HQVGA (240x176)", FRAMESIZE_HQVGA},
|
||||||
|
{"240x240", FRAMESIZE_240X240},
|
||||||
|
{"QVGA (320x240)", FRAMESIZE_QVGA},
|
||||||
|
{"CIF (400x296)", FRAMESIZE_CIF},
|
||||||
|
{"HVGA (480x320)", FRAMESIZE_HVGA},
|
||||||
|
{"VGA (640x480)", FRAMESIZE_VGA},
|
||||||
|
{"SVGA (800x600)", FRAMESIZE_SVGA},
|
||||||
|
{"XGA (1024x768)", FRAMESIZE_XGA},
|
||||||
|
{"HD (1280x720)", FRAMESIZE_HD},
|
||||||
|
{"SXGA (1280x1024)", FRAMESIZE_SXGA},
|
||||||
|
{"UXGA (1600x1200)", FRAMESIZE_UXGA},
|
||||||
|
{"FHD (1920x1080)", FRAMESIZE_FHD},
|
||||||
|
{"P HD (2560x1440)", FRAMESIZE_P_HD},
|
||||||
|
{"P 3MP (2560x1600)", FRAMESIZE_P_3MP},
|
||||||
|
{"QXGA (2560x1920)", FRAMESIZE_QXGA},
|
||||||
|
{"QHD (2560x1440)", FRAMESIZE_QHD},
|
||||||
|
{"WQXGA (2560x1600)", FRAMESIZE_WQXGA},
|
||||||
|
{"P FHD (1080x1920)", FRAMESIZE_P_FHD},
|
||||||
|
{"QSXGA (2560x1920)", FRAMESIZE_QSXGA},
|
||||||
|
{"", FRAMESIZE_INVALID}};
|
||||||
|
|
||||||
|
framesize_t lookup_frame_size(const char *pin)
|
||||||
|
{
|
||||||
|
// Lookup table for the frame name to framesize_t
|
||||||
|
auto entry = &frame_sizes[0];
|
||||||
|
while (*entry->name)
|
||||||
|
{
|
||||||
|
if (strncmp(entry->name, pin, sizeof(frame_size_name_t)) == 0)
|
||||||
|
return entry->frame_size;
|
||||||
|
|
||||||
|
entry++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FRAMESIZE_INVALID;
|
||||||
|
}
|
||||||
46
lib/README
Normal file
46
lib/README
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a an own separate directory
|
||||||
|
("lib/your_library_name/[here are source files]").
|
||||||
|
|
||||||
|
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
and a contents of `src/main.c`:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
8
lib/rtsp_server/library.json
Normal file
8
lib/rtsp_server/library.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "RTSPServer",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"contrem/arduino-timer": "^2.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
51
lib/rtsp_server/rtsp_server.cpp
Normal file
51
lib/rtsp_server/rtsp_server.cpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#include "rtsp_server.h"
|
||||||
|
#include <esp32-hal-log.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#include <OV2640Streamer.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");
|
||||||
|
WiFiServer::begin();
|
||||||
|
// Add service to mDNS - rtsp
|
||||||
|
MDNS.addService("rtsp", "tcp", 554);
|
||||||
|
|
||||||
|
timer_.every(interval, client_handler, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
36
lib/rtsp_server/rtsp_server.h
Normal file
36
lib/rtsp_server/rtsp_server.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <WiFiServer.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#include <OV2640.h>
|
||||||
|
#include <CRtspSession.h>
|
||||||
|
#include <arduino-timer.h>
|
||||||
|
|
||||||
|
class rtsp_server : public WiFiServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rtsp_server(OV2640 &cam, unsigned long interval, int port = 554);
|
||||||
|
|
||||||
|
void doLoop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct rtsp_client
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rtsp_client(const WiFiClient &client, OV2640 &cam);
|
||||||
|
|
||||||
|
WiFiClient wifi_client;
|
||||||
|
// Streamer for UDP/TCP based RTP transport
|
||||||
|
std::shared_ptr<CStreamer> streamer;
|
||||||
|
// RTSP session and state
|
||||||
|
std::shared_ptr<CRtspSession> session;
|
||||||
|
};
|
||||||
|
|
||||||
|
OV2640 cam_;
|
||||||
|
std::list<std::unique_ptr<rtsp_client>> clients_;
|
||||||
|
uintptr_t task_;
|
||||||
|
Timer<> timer_;
|
||||||
|
|
||||||
|
static bool client_handler(void *);
|
||||||
|
};
|
||||||
26
platformio.ini
Normal file
26
platformio.ini
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:esp32cam]
|
||||||
|
platform = espressif32
|
||||||
|
board = esp32cam
|
||||||
|
framework = arduino
|
||||||
|
|
||||||
|
monitor_speed = 115200
|
||||||
|
monitor_filters = log2file, time, default
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
-O2
|
||||||
|
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
|
||||||
|
-D LED_BUILTIN=4
|
||||||
|
|
||||||
|
lib_deps =
|
||||||
|
prampec/IotWebConf @ ^3.2.1
|
||||||
|
geeksville/Micro-RTSP @ ^0.1.6
|
||||||
160
src/main.cpp
Normal file
160
src/main.cpp
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include <soc/rtc_cntl_reg.h>
|
||||||
|
|
||||||
|
#include <IotWebConf.h>
|
||||||
|
#include <IotWebConfTParameter.h>
|
||||||
|
|
||||||
|
#include <OV2640.h>
|
||||||
|
|
||||||
|
#include <rtsp_server.h>
|
||||||
|
|
||||||
|
#include <frame_size.h>
|
||||||
|
|
||||||
|
#include <esp32cam.h>
|
||||||
|
|
||||||
|
char frame_rate_val[6];
|
||||||
|
char frame_size_val[sizeof(frame_size_entry_t)];
|
||||||
|
|
||||||
|
auto config_group_stream_settings = iotwebconf::ParameterGroup("settings", "Streaming settings");
|
||||||
|
auto config_frame_rate = iotwebconf::NumberParameter("Frame rate (ms)", "fr", frame_rate_val, sizeof(frame_rate_val), DEFAULT_FRAMERATE);
|
||||||
|
auto config_frame_size = iotwebconf::SelectParameter("Frame size", "fs", frame_size_val, sizeof(frame_size_val), (const char *)frame_sizes, (const char *)frame_sizes, sizeof(frame_sizes) / sizeof(frame_sizes[0]), sizeof(frame_sizes[0]), DEFAULT_FRAMESIZE);
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
OV2640 cam;
|
||||||
|
|
||||||
|
// DNS Server
|
||||||
|
DNSServer dnsServer;
|
||||||
|
|
||||||
|
// RTSP Server
|
||||||
|
std::unique_ptr<rtsp_server> camera_server;
|
||||||
|
|
||||||
|
// Web server
|
||||||
|
WebServer web_server(80);
|
||||||
|
IotWebConf iotWebConf(WIFI_SSID, &dnsServer, &web_server, WIFI_PASSWORD, CONFIG_VERSION);
|
||||||
|
|
||||||
|
void handleRoot()
|
||||||
|
{
|
||||||
|
log_v("Handle root");
|
||||||
|
// Let IotWebConf test and handle captive portal requests.
|
||||||
|
if (iotWebConf.handleCaptivePortal())
|
||||||
|
return;
|
||||||
|
|
||||||
|
String html;
|
||||||
|
html += "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>";
|
||||||
|
html += "<title>" APP_TITLE " v" APP_VERSION "</title></head>";
|
||||||
|
html += "<body>";
|
||||||
|
html += "<h2>Status page for " + String(iotWebConf.getThingName()) + "</h2><hr />";
|
||||||
|
html += "<h3>ESP32</h3>";
|
||||||
|
html += "<ul>";
|
||||||
|
html += "<li>CPU model: " + String(ESP.getChipModel()) + "</li>";
|
||||||
|
html += "<li>CPU speed: " + String(ESP.getCpuFreqMHz()) + "Mhz</li>";
|
||||||
|
html += "<li>Mac address: " + WiFi.macAddress() + "</li>";
|
||||||
|
html += "</ul>";
|
||||||
|
html += "<h3>Settings</h3>";
|
||||||
|
html += "<ul>";
|
||||||
|
html += "<li>Frame size: " + String(frame_size_val) + "</li>";
|
||||||
|
html += "<li>Frame rate: " + String(frame_rate_val) + "</li>";
|
||||||
|
html += "</ul>";
|
||||||
|
html += "<br/>Go to <a href=\"config\">configure page</a> to change settings.";
|
||||||
|
html += "</body></html>";
|
||||||
|
web_server.send(200, "text/html", html);
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize_camera()
|
||||||
|
{
|
||||||
|
log_v("Initialize camera");
|
||||||
|
log_i("Frame size: %s", frame_size_val);
|
||||||
|
auto frame_size = lookup_frame_size(frame_size_val);
|
||||||
|
// ESP32CAM
|
||||||
|
log_d("Looking for ESP32CAM");
|
||||||
|
esp32cam_config.frame_size = frame_size;
|
||||||
|
if (cam.init(esp32cam_aithinker_config) == ESP_OK)
|
||||||
|
{
|
||||||
|
log_i("Found ESP32CAM");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI Thinker
|
||||||
|
log_d("Looking for AI Thinker");
|
||||||
|
esp32cam_aithinker_config.frame_size = frame_size;
|
||||||
|
if (cam.init(esp32cam_aithinker_config) == ESP_OK)
|
||||||
|
{
|
||||||
|
log_i("Found AI Thinker");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TTGO T-Cam
|
||||||
|
log_d("Looking for TTGO T-Cam");
|
||||||
|
esp32cam_ttgo_t_config.frame_size = frame_size;
|
||||||
|
if (cam.init(esp32cam_ttgo_t_config) == ESP_OK)
|
||||||
|
{
|
||||||
|
log_i("Found TTGO T-Cam");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_e("No camera found");
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_rtsp_server()
|
||||||
|
{
|
||||||
|
log_v("start_rtsp_server");
|
||||||
|
initialize_camera();
|
||||||
|
auto frame_rate = atol(frame_rate_val);
|
||||||
|
camera_server = std::unique_ptr<rtsp_server>(new rtsp_server(cam, frame_rate, RTSP_PORT));
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_config_saved()
|
||||||
|
{
|
||||||
|
log_v("on_config_saved");
|
||||||
|
start_rtsp_server();
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_connected()
|
||||||
|
{
|
||||||
|
log_v("on_connected");
|
||||||
|
start_rtsp_server();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
// Disable brownout
|
||||||
|
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
|
||||||
|
|
||||||
|
pinMode(LED_BUILTIN, OUTPUT);
|
||||||
|
digitalWrite(LED_BUILTIN, false);
|
||||||
|
|
||||||
|
#ifdef CORE_DEBUG_LEVEL
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.setDebugOutput(true);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
log_i("CPU Freq = %d Mhz", getCpuFrequencyMhz());
|
||||||
|
log_i("Free heap: %d bytes", ESP.getFreeHeap());
|
||||||
|
log_i("Starting " APP_TITLE "...");
|
||||||
|
|
||||||
|
config_group_stream_settings.addItem(&config_frame_rate);
|
||||||
|
config_group_stream_settings.addItem(&config_frame_size);
|
||||||
|
iotWebConf.addParameterGroup(&config_group_stream_settings);
|
||||||
|
iotWebConf.getApTimeoutParameter()->visible = true;
|
||||||
|
iotWebConf.setConfigSavedCallback(on_config_saved);
|
||||||
|
iotWebConf.setWifiConnectionCallback(on_connected);
|
||||||
|
|
||||||
|
iotWebConf.init();
|
||||||
|
|
||||||
|
// Set up required URL handlers on the web server
|
||||||
|
web_server.on("/", HTTP_GET, handleRoot);
|
||||||
|
web_server.on("/config", []
|
||||||
|
{ iotWebConf.handleConfig(); });
|
||||||
|
web_server.onNotFound([]()
|
||||||
|
{ iotWebConf.handleNotFound(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
iotWebConf.doLoop();
|
||||||
|
|
||||||
|
if (camera_server)
|
||||||
|
camera_server->doLoop();
|
||||||
|
}
|
||||||
11
test/README
Normal file
11
test/README
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||||
Reference in New Issue
Block a user