forked from external-repos/esp32cam-rtsp
Compare commits
19 Commits
develop
...
106-create
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12332fd7eb | ||
|
|
4dc36fb1e8 | ||
|
|
4b50b52ae0 | ||
|
|
b8abcdcdee | ||
|
|
5117f0d21a | ||
|
|
f59fb8bb9a | ||
|
|
c599657882 | ||
|
|
3e688ddcfb | ||
|
|
c2dcd8b3d4 | ||
|
|
86ae9af574 | ||
|
|
2eb7a58e1c | ||
|
|
6119020e7d | ||
|
|
152c068f68 | ||
|
|
f05932b896 | ||
|
|
4fbba6fc96 | ||
|
|
4fb0f61e8e | ||
|
|
03582b83cb | ||
|
|
26d1af2f28 | ||
|
|
97fc4ceb33 |
@@ -97,7 +97,7 @@ There are a lot of boards available that are all called ESP32-CAM.
|
||||
However, there are differences in CPU (type/speed/cores), how the camera is connected, presence of PSRAM or not...
|
||||
To select the right board use the table below and use the configuration that is listed below for your board:
|
||||
|
||||
| Board | Image | CPU | SRAM | Flash | PSRAM | Camera | | Site |
|
||||
| Board | Image | CPU | SRAM | Flash | PSRAM | Camera | Extras | Manufacturer site |
|
||||
|--- |--- |--- |--- |--- | --- |--- |--- |--- |
|
||||
| Espressif ESP32-Wrover CAM |  | ESP32 | 520KB | 4Mb | 4MB | OV2640 | | |
|
||||
| AI-Thinker ESP32-CAM |   | ESP32-S | 520KB | 4Mb | 4MB | OV2640 | | [https://docs.ai-thinker.com/esp32-cam](https://docs.ai-thinker.com/esp32-cam) |
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino":{
|
||||
"arduino": {
|
||||
"ldscript": "esp32_out.ld",
|
||||
"partitions": "huge_app.csv"
|
||||
},
|
||||
@@ -32,7 +32,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=1'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino":{
|
||||
"arduino": {
|
||||
"ldscript": "esp32_out.ld",
|
||||
"partitions": "huge_app.csv"
|
||||
},
|
||||
@@ -28,7 +28,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=1'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_DRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D GROVE_SDA=13'",
|
||||
"'-D GROVE_SCL=4'"
|
||||
],
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D GROVE_SDA=13'",
|
||||
"'-D GROVE_SCL=4'"
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino":{
|
||||
"arduino": {
|
||||
"ldscript": "esp32_out.ld",
|
||||
"partitions": "huge_app.csv"
|
||||
},
|
||||
@@ -32,7 +32,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D MICROPHONE_GPIO=32'",
|
||||
"'-D GROVE_SDA=13'",
|
||||
"'-D GROVE_SCL=4'"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino":{
|
||||
"arduino": {
|
||||
"ldscript": "esp32_out.ld",
|
||||
"partitions": "huge_app.csv"
|
||||
},
|
||||
@@ -30,7 +30,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=1'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_DRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_DRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D I2C_MEMS_SDA=17'",
|
||||
"'-D I2C_MEMS_SCL=41'",
|
||||
"'-D TF_CS=9'",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino":{
|
||||
"arduino": {
|
||||
"ldscript": "esp32_out.ld",
|
||||
"partitions": "huge_app.csv"
|
||||
},
|
||||
@@ -30,7 +30,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=2'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D I2C_MEMS_SDA=41'",
|
||||
"'-D I2C_MEMS_SCL=42'",
|
||||
"'-D TF_CS=21'",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=1'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_DRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'",
|
||||
"'-D LCD_SSD1306_PIN_SDA=21'",
|
||||
"'-D LCD_SSD1306_PIN_SCL=22'",
|
||||
"'-D BUTTON_RIGHT_PIN=34'",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'",
|
||||
"'-D CAMERA_CONFIG_FB_COUNT=1'",
|
||||
"'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_DRAM'",
|
||||
"'-D SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
"'-D CAMERA_CONFIG_SCCB_I2C_PORT=I2C_NUM_0'"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
|
||||
1
dotnet_riscv
Submodule
1
dotnet_riscv
Submodule
Submodule dotnet_riscv added at 70e3cb657b
@@ -9,6 +9,12 @@
|
||||
|
||||
#define OTA_PASSWORD "ESP32CAM-RTSP"
|
||||
|
||||
// Time servers
|
||||
#define NTP_SERVER_1 "nl.pool.ntp.org"
|
||||
#define NTP_SERVER_2 "europe.pool.ntp.org"
|
||||
#define NTP_SERVER_3 "time.nist.gov"
|
||||
#define NTP_SERVERS NTP_SERVER_1, NTP_SERVER_2, NTP_SERVER_3
|
||||
|
||||
#define RTSP_PORT 554
|
||||
|
||||
#define DEFAULT_FRAME_DURATION 200
|
||||
|
||||
18
lib/micro-jpg/include/jpg.h
Normal file
18
lib/micro-jpg/include/jpg.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include "jpg_section.h"
|
||||
|
||||
class jpg
|
||||
{
|
||||
public:
|
||||
bool decode(const uint8_t *jpg, size_t size);
|
||||
|
||||
const jpg_section_dqt_t *quantization_table_luminance_;
|
||||
const jpg_section_dqt_t *quantization_table_chrominance_;
|
||||
|
||||
const uint8_t *jpeg_data_start;
|
||||
const uint8_t *jpeg_data_end;
|
||||
|
||||
private:
|
||||
static const jpg_section_t *find_jpg_section(const uint8_t **ptr, const uint8_t *end, jpg_section_t::jpg_section_flag flag);
|
||||
};
|
||||
107
lib/micro-jpg/include/jpg_section.h
Normal file
107
lib/micro-jpg/include/jpg_section.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// http://www.ietf.org/rfc/rfc2345.txt Each table is an array of 64 values given in zig-zag order, identical to the format used in a JFIF DQT marker segment.
|
||||
constexpr size_t jpeg_quantization_table_length = 64;
|
||||
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
enum jpg_section_flag : uint8_t
|
||||
{
|
||||
DATA = 0x00,
|
||||
SOF0 = 0xc0,
|
||||
SOF1 = 0xc1,
|
||||
SOF2 = 0xc2,
|
||||
SOF3 = 0xc3,
|
||||
DHT = 0xc4,
|
||||
SOF5 = 0xc5,
|
||||
SOF6 = 0xc6,
|
||||
SOF7 = 0xc7,
|
||||
JPG = 0xc8,
|
||||
SOF9 = 0xc9,
|
||||
SOF10 = 0xca,
|
||||
SOF11 = 0xcb,
|
||||
DAC = 0xcc,
|
||||
SOF13 = 0xcd,
|
||||
SOF14 = 0xce,
|
||||
SOF15 = 0xcf,
|
||||
RST0 = 0xd0,
|
||||
RST1 = 0xd1,
|
||||
RST2 = 0xd2,
|
||||
RST3 = 0xd3,
|
||||
RST4 = 0xd4,
|
||||
RST5 = 0xd5,
|
||||
RST6 = 0xd6,
|
||||
RST7 = 0xd7,
|
||||
SOI = 0xd8,
|
||||
EOI = 0xd9,
|
||||
SOS = 0xda,
|
||||
DQT = 0xdb,
|
||||
DNL = 0xdc,
|
||||
DRI = 0xdd,
|
||||
DHP = 0xde,
|
||||
EXP = 0xdf,
|
||||
APP0 = 0xe0,
|
||||
APP1 = 0xe1,
|
||||
APP2 = 0xe2,
|
||||
APP3 = 0xe3,
|
||||
APP4 = 0xe4,
|
||||
APP5 = 0xe5,
|
||||
APP6 = 0xe6,
|
||||
APP7 = 0xe7,
|
||||
APP8 = 0xe8,
|
||||
APP9 = 0xe9,
|
||||
APP10 = 0xea,
|
||||
APP11 = 0xeb,
|
||||
APP12 = 0xec,
|
||||
APP13 = 0xed,
|
||||
APP14 = 0xee,
|
||||
APP15 = 0xef,
|
||||
JPG0 = 0xf0,
|
||||
JPG1 = 0xf1,
|
||||
JPG2 = 0xf2,
|
||||
JPG3 = 0xf3,
|
||||
JPG4 = 0xf4,
|
||||
JPG5 = 0xf5,
|
||||
JPG6 = 0xf6,
|
||||
JPG7 = 0xf7,
|
||||
JPG8 = 0xf8,
|
||||
JPG9 = 0xf9,
|
||||
COM = 0xfe,
|
||||
JPG10 = 0xfa,
|
||||
JPG11 = 0xfb,
|
||||
JPG12 = 0xfc,
|
||||
JPG13 = 0xfd
|
||||
};
|
||||
|
||||
const uint8_t framing; // 0xff
|
||||
const jpg_section_flag flag;
|
||||
const uint8_t length_msb;
|
||||
const uint8_t length_lsb;
|
||||
const uint8_t data[];
|
||||
|
||||
static bool is_valid_flag(const jpg_section_flag flag);
|
||||
static const char *flag_name(const jpg_section_flag flag);
|
||||
uint16_t data_length() const;
|
||||
uint16_t section_length() const;
|
||||
} jpg_section_t;
|
||||
|
||||
typedef struct __attribute__((packed)) // 0xffe0
|
||||
{
|
||||
char identifier[5] = {'J', 'F', 'I', 'F', 0}; // JFIF identifier, zero-terminated
|
||||
uint8_t version_major = 1;
|
||||
uint8_t version_minor = 1; // JFIF version 1.1
|
||||
uint8_t density_units = 0; // no density units specified
|
||||
uint16_t density_hor = 1;
|
||||
uint16_t density_ver = 1; // density: 1 pixel "per pixel" horizontally and vertically
|
||||
uint8_t thumbnail_hor = 0;
|
||||
uint8_t thumbnail_ver = 0; // no thumbnail (size 0 x 0)
|
||||
} jpg_section_app0_t;
|
||||
|
||||
typedef struct __attribute__((packed)) // 0xffdb
|
||||
{
|
||||
uint8_t id; // 0= quantLuminance, 1= quantChrominance
|
||||
uint8_t data[jpeg_quantization_table_length];
|
||||
} jpg_section_dqt_t;
|
||||
14
lib/micro-jpg/library.json
Normal file
14
lib/micro-jpg/library.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "micro-jpg",
|
||||
"version": "1.0.0",
|
||||
"description": "JPEG library",
|
||||
"keywords": "",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rzeldent/"
|
||||
},
|
||||
"build": {
|
||||
"srcDir": "src/",
|
||||
"includeDir": "include/"
|
||||
}
|
||||
}
|
||||
111
lib/micro-jpg/src/jpg.cpp
Normal file
111
lib/micro-jpg/src/jpg.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include <esp32-hal-log.h>
|
||||
#include "jpg.h"
|
||||
|
||||
const jpg_section_t *jpg::find_jpg_section(const uint8_t **ptr, const uint8_t *end, jpg_section_t::jpg_section_flag flag)
|
||||
{
|
||||
log_d("find_jpeg_section 0x%02x (%s)", flag, jpg_section_t::flag_name(flag));
|
||||
while (*ptr < end)
|
||||
{
|
||||
// flag, len MSB, len LSB
|
||||
auto section = reinterpret_cast<const jpg_section_t *>((*ptr));
|
||||
if (section->framing != 0xff)
|
||||
{
|
||||
log_e("Expected framing 0xff but found: 0x%02x", section->framing);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!jpg_section_t::is_valid_flag(section->flag))
|
||||
{
|
||||
log_d("Unknown section 0x%02x", flag);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Advance pointer section has a length, so not SOI (0xd8) and EOI (0xd9)
|
||||
*ptr += section->section_length();
|
||||
if (section->flag == flag)
|
||||
{
|
||||
log_d("Found section 0x%02x (%s), %d bytes", flag, jpg_section_t::flag_name(section->flag), section->section_length());
|
||||
return section;
|
||||
}
|
||||
|
||||
log_d("Skipping section: 0x%02x (%s), %d bytes", section->flag, jpg_section_t::flag_name(section->flag), section->section_length());
|
||||
}
|
||||
|
||||
// Not found
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// See https://create.stephan-brumme.com/toojpeg/
|
||||
bool jpg::decode(const uint8_t *data, size_t size)
|
||||
{
|
||||
log_d("decode_jpeg");
|
||||
// Look for start jpeg file (0xd8)
|
||||
auto ptr = data;
|
||||
auto end = ptr + size;
|
||||
|
||||
// Check for SOI (start of image) 0xff, 0xd8
|
||||
if (!find_jpg_section(&ptr, end, jpg_section_t::jpg_section_flag::SOI))
|
||||
{
|
||||
log_e("No valid start of image marker found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// First quantization table (Luminance - black & white images)
|
||||
const jpg_section_t *quantization_table_section;
|
||||
if (!(quantization_table_section = find_jpg_section(&ptr, end, jpg_section_t::jpg_section_flag::DQT)))
|
||||
{
|
||||
log_e("No quantization_table_luminance section found");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (quantization_table_section->data_length() != sizeof(jpg_section_dqt_t))
|
||||
{
|
||||
log_w("Invalid length of quantization_table_luminance section. Expected %d but read %d", sizeof(jpg_section_dqt_t), quantization_table_section->data_length());
|
||||
return false;
|
||||
}
|
||||
|
||||
quantization_table_luminance_ = reinterpret_cast<const jpg_section_dqt_t *>(quantization_table_section->data);
|
||||
|
||||
// Second quantization table (Chrominance - color images)
|
||||
if (!(quantization_table_section = find_jpg_section(&ptr, end, jpg_section_t::jpg_section_flag::DQT)))
|
||||
{
|
||||
log_w("No quantization_table_chrominance section found");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (quantization_table_section->data_length() != sizeof(jpg_section_dqt_t))
|
||||
{
|
||||
log_w("Invalid length of quantization_table_chrominance section. Expected %d but read %d", sizeof(jpg_section_dqt_t), quantization_table_section->data_length());
|
||||
return false;
|
||||
}
|
||||
|
||||
quantization_table_chrominance_ = reinterpret_cast<const jpg_section_dqt_t *>(quantization_table_section->data);
|
||||
|
||||
// Start of scan
|
||||
if (!find_jpg_section(&ptr, end, jpg_section_t::jpg_section_flag::SOS))
|
||||
{
|
||||
log_e("No start of scan section found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start of the data sections
|
||||
jpeg_data_start = ptr;
|
||||
|
||||
log_d("Skipping over data sections");
|
||||
// Scan over all the sections. 0xff followed by not zero, is a new section
|
||||
while (ptr < end - 1 && (ptr[0] != 0xff || ptr[1] == 0))
|
||||
ptr++;
|
||||
|
||||
// Check if marker is an end of image marker
|
||||
if (!find_jpg_section(&ptr, end, jpg_section_t::jpg_section_flag::EOI))
|
||||
{
|
||||
log_e("No end of image marker found");
|
||||
return false;
|
||||
}
|
||||
|
||||
jpeg_data_end = ptr;
|
||||
|
||||
log_d("Total jpeg data: %d bytes", jpeg_data_end - jpeg_data_start);
|
||||
|
||||
return true;
|
||||
}
|
||||
154
lib/micro-jpg/src/jpg_section.cpp
Normal file
154
lib/micro-jpg/src/jpg_section.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "jpg_section.h"
|
||||
|
||||
uint16_t jpg_section_t::data_length() const
|
||||
{
|
||||
return (length_msb << 8) + length_lsb - sizeof(jpg_section_t::length_msb)- sizeof(jpg_section_t::length_lsb);
|
||||
}
|
||||
|
||||
uint16_t jpg_section_t::section_length() const
|
||||
{
|
||||
return flag == SOI || flag == EOI ? sizeof(jpg_section_t::framing) + sizeof(jpg_section_t::flag) : sizeof(jpg_section_t::framing) + sizeof(jpg_section_t::flag) + (length_msb << 8) + length_lsb;
|
||||
}
|
||||
|
||||
bool jpg_section_t::is_valid_flag(const jpg_section_flag flag)
|
||||
{
|
||||
return flag >= SOF0 && flag <= COM;
|
||||
}
|
||||
|
||||
// from: https://www.disktuna.com/list-of-jpeg-markers/
|
||||
const char *jpg_section_t::flag_name(const jpg_section_flag flag)
|
||||
{
|
||||
switch (flag)
|
||||
{
|
||||
case DATA:
|
||||
return "DATA"; // DATA
|
||||
case SOF0:
|
||||
return "SOF0"; // Start of Frame 0 Baseline DCT
|
||||
case SOF1:
|
||||
return "SOF1"; // Start of Frame 1 Extended Sequential DCT
|
||||
case SOF2:
|
||||
return "SOF2"; // Start of Frame 2 Progressive DCT
|
||||
case SOF3:
|
||||
return "SOF3"; // Start of Frame 3 Lossless (sequential)
|
||||
case DHT:
|
||||
return "DHT"; // Define Huffman Table
|
||||
case SOF5:
|
||||
return "SOF5"; // Start of Frame 5 Differential sequential DCT
|
||||
case SOF6:
|
||||
return "SOF6"; // Start of Frame 6 Differential progressive DCT
|
||||
case SOF7:
|
||||
return "SOF7"; // Start of Frame 7 Differential lossless (sequential)
|
||||
case JPG:
|
||||
return "JPG"; // JPEG Extensions
|
||||
case SOF9:
|
||||
return "SOF9"; // Start of Frame 9 Extended sequential DCT, Arithmetic coding
|
||||
case SOF10:
|
||||
return "SOF10"; // Start of Frame 10 Progressive DCT, Arithmetic coding
|
||||
case SOF11:
|
||||
return "SOF11"; // Start of Frame 11 Lossless (sequential), Arithmetic coding
|
||||
case DAC:
|
||||
return "DAC"; // Define Arithmetic Coding
|
||||
case SOF13:
|
||||
return "SOF13"; // Start of Frame 13 Differential sequential DCT, Arithmetic coding
|
||||
case SOF14:
|
||||
return "SOF14"; // Start of Frame 14 Differential progressive DCT, Arithmetic coding
|
||||
case SOF15:
|
||||
return "SOF15"; // Start of Frame 15 Differential lossless (sequential), Arithmetic coding
|
||||
case RST0:
|
||||
return "RST0"; // Restart Marker 0
|
||||
case RST1:
|
||||
return "RST1"; // Restart Marker 1
|
||||
case RST2:
|
||||
return "RST2"; // Restart Marker 2
|
||||
case RST3:
|
||||
return "RST3"; // Restart Marker 3
|
||||
case RST4:
|
||||
return "RST4"; // Restart Marker 4
|
||||
case RST5:
|
||||
return "RST5"; // Restart Marker 5
|
||||
case RST6:
|
||||
return "RST6"; // Restart Marker 6
|
||||
case RST7:
|
||||
return "RST7"; // Restart Marker 7
|
||||
case SOI:
|
||||
return "SOI"; // Start of Image
|
||||
case EOI:
|
||||
return "EOI"; // End of Image
|
||||
case SOS:
|
||||
return "SOS"; // Start of Scan
|
||||
case DQT:
|
||||
return "DQT"; // Define Quantization Table
|
||||
case DNL:
|
||||
return "DNL"; // Define Number of Lines (Not common)
|
||||
case DRI:
|
||||
return "DRI"; // Define Restart Interval
|
||||
case DHP:
|
||||
return "DHP"; // Define Hierarchical Progression (Not common)
|
||||
case EXP:
|
||||
return "EXP"; // Expand Reference Component (Not common)
|
||||
case APP0:
|
||||
return "APP0"; // Application Segment 0 JFIF – JFIF JPEG image, AVI1 – Motion JPEG (MJPG)
|
||||
case APP1:
|
||||
return "APP1"; // Application Segment 1 EXIF Metadata, TIFF IFD format, JPEG Thumbnail (160×120) Adobe XMP
|
||||
case APP2:
|
||||
return "APP2"; // Application Segment 2 ICC color profile, FlashPix
|
||||
case APP3:
|
||||
return "APP3"; // Application Segment 3 (Not common) JPS Tag for Stereoscopic JPEG images
|
||||
case APP4:
|
||||
return "APP4"; // Application Segment 4 (Not common)
|
||||
case APP5:
|
||||
return "APP5"; // Application Segment 5 (Not common)
|
||||
case APP6:
|
||||
return "APP6"; // Application Segment 6 (Not common) NITF Lossles profile
|
||||
case APP7:
|
||||
return "APP7"; // Application Segment 7 (Not common)
|
||||
case APP8:
|
||||
return "APP8"; // Application Segment 8 (Not common)
|
||||
case APP9:
|
||||
return "APP9"; // Application Segment 9 (Not common)
|
||||
case APP10:
|
||||
return "APP10"; // Application Segment 10 PhoTags (Not common) ActiveObject (multimedia messages / captions)
|
||||
case APP11:
|
||||
return "APP11"; // Application Segment 11 (Not common) HELIOS JPEG Resources (OPI Postscript)
|
||||
case APP12:
|
||||
return "APP12"; // Application Segment 12 Picture Info (older digicams), Photoshop Save for Web: Ducky
|
||||
case APP13:
|
||||
return "APP13"; // Application Segment 13 Photoshop Save As: IRB, 8BIM, IPTC
|
||||
case APP14:
|
||||
return "APP14"; // Application Segment 14 (Not common)
|
||||
case APP15:
|
||||
return "APP15"; // Application Segment 15 (Not common)
|
||||
case JPG0:
|
||||
return "JPG0"; // JPEG Extension 0
|
||||
case JPG1:
|
||||
return "JPG1"; // JPEG Extension 1
|
||||
case JPG2:
|
||||
return "JPG2"; // JPEG Extension 2
|
||||
case JPG3:
|
||||
return "JPG3"; // JPEG Extension 3
|
||||
case JPG4:
|
||||
return "JPG4"; // JPEG Extension 4
|
||||
case JPG5:
|
||||
return "JPG5"; // JPEG Extension 5
|
||||
case JPG6:
|
||||
return "JPG6"; // JPEG Extension 6
|
||||
case JPG7:
|
||||
return "JPG7"; // SOF48 JPEG Extension 7 JPEG-LS Lossless JPEG
|
||||
case JPG8:
|
||||
return "JPG8"; // LSE JPEG Extension 8 JPEG-LS Extension Lossless JPEG Extension Parameters
|
||||
case JPG9:
|
||||
return "JPG9"; // JPEG Extension 9 (Not common)
|
||||
case JPG10:
|
||||
return "JPG10"; // JPEG Extension 10 (Not common)
|
||||
case JPG11:
|
||||
return "JPG11"; // JPEG Extension 11 (Not common)
|
||||
case JPG12:
|
||||
return "JPG12"; // JPEG Extension 12 (Not common)
|
||||
case JPG13:
|
||||
return "JPG13"; // JPEG Extension 13 (Not common)
|
||||
case COM:
|
||||
return "COM"; // Comment
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
25
lib/micro-rtsp-server/include/micro_rtsp_camera.h
Normal file
25
lib/micro-rtsp-server/include/micro_rtsp_camera.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <micro_rtsp_source.h>
|
||||
#include <esp_camera.h>
|
||||
|
||||
class micro_rtsp_camera : public micro_rtsp_source
|
||||
{
|
||||
public:
|
||||
micro_rtsp_camera();
|
||||
virtual ~micro_rtsp_camera();
|
||||
|
||||
esp_err_t initialize(camera_config_t *camera_config);
|
||||
esp_err_t deinitialize();
|
||||
|
||||
virtual void update_frame();
|
||||
|
||||
virtual uint8_t *data() const { return fb_->buf; }
|
||||
virtual size_t width() const { return fb_->width; }
|
||||
virtual size_t height() const { return fb_->height; }
|
||||
virtual size_t size() const { return fb_->len; }
|
||||
|
||||
private:
|
||||
esp_err_t init_result_;
|
||||
camera_fb_t *fb_;
|
||||
};
|
||||
56
lib/micro-rtsp-server/include/micro_rtsp_requests.h
Normal file
56
lib/micro-rtsp-server/include/micro_rtsp_requests.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class micro_rtsp_requests
|
||||
{
|
||||
public:
|
||||
std::string process_request(const std::string& request);
|
||||
bool active() const { return stream_active_; }
|
||||
|
||||
private:
|
||||
// enum rtsp_command
|
||||
// {
|
||||
// rtsp_command_unknown,
|
||||
// rtsp_command_options, // OPTIONS
|
||||
// rtsp_command_describe, // DESCRIBE
|
||||
// rtsp_command_setup, // SETUP
|
||||
// rtsp_command_play, // PLAY
|
||||
// rtsp_command_teardown // TEARDOWN
|
||||
// };
|
||||
|
||||
static const std::string available_stream_name_;
|
||||
|
||||
//rtsp_command parse_command(const std::string &request);
|
||||
//static bool parse_cseq(const std::string &line, unsigned long &cseq);
|
||||
bool parse_client_port(const std::string &request);
|
||||
//bool parse_stream_url(const std::string &request);
|
||||
|
||||
//static std::string date_header();
|
||||
static std::string handle_rtsp_error(unsigned long cseq, unsigned short code, const std::string &message);
|
||||
|
||||
static std::string handle_options(unsigned long cseq);
|
||||
static std::string handle_describe(unsigned long cseq, const std::string &request);
|
||||
std::string handle_setup(unsigned long cseq, const std::map<std::string, std::string> &request);
|
||||
std::string handle_play(unsigned long cseq);
|
||||
std::string handle_teardown(unsigned long cseq);
|
||||
|
||||
//unsigned long cseq_;
|
||||
|
||||
// std::string host_url_;
|
||||
// unsigned short host_port_;
|
||||
// std::string stream_name_;
|
||||
|
||||
bool tcp_transport_;
|
||||
unsigned short start_client_port_;
|
||||
unsigned short end_client_port_;
|
||||
|
||||
unsigned short rtp_streamer_port_;
|
||||
unsigned short rtcp_streamer_port_;
|
||||
|
||||
unsigned long rtsp_session_id_;
|
||||
|
||||
bool stream_active_;
|
||||
bool stream_stopped_;
|
||||
};
|
||||
45
lib/micro-rtsp-server/include/micro_rtsp_server.h
Normal file
45
lib/micro-rtsp-server/include/micro_rtsp_server.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFiServer.h>
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include "micro_rtsp_camera.h"
|
||||
#include "micro_rtsp_requests.h"
|
||||
#include "micro_rtsp_streamer.h"
|
||||
|
||||
class micro_rtsp_server : WiFiServer
|
||||
{
|
||||
public:
|
||||
micro_rtsp_server(micro_rtsp_source &source);
|
||||
~micro_rtsp_server();
|
||||
|
||||
void begin(unsigned short port = 554);
|
||||
void end();
|
||||
|
||||
unsigned get_frame_interval() const { return frame_interval_; }
|
||||
unsigned set_frame_interval(unsigned value) { return frame_interval_ = value; }
|
||||
|
||||
void loop();
|
||||
|
||||
size_t clients() const { return clients_.size(); }
|
||||
|
||||
class rtsp_client : public WiFiClient, public micro_rtsp_requests
|
||||
{
|
||||
public:
|
||||
rtsp_client(const WiFiClient &client);
|
||||
~rtsp_client();
|
||||
|
||||
void handle_request();
|
||||
};
|
||||
|
||||
private:
|
||||
micro_rtsp_source &source_;
|
||||
unsigned frame_interval_;
|
||||
unsigned long next_frame_update_;
|
||||
unsigned long next_check_client_;
|
||||
micro_rtsp_streamer streamer_;
|
||||
std::list<rtsp_client> clients_;
|
||||
};
|
||||
16
lib/micro-rtsp-server/include/micro_rtsp_source.h
Normal file
16
lib/micro-rtsp-server/include/micro_rtsp_source.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Interface for a video source
|
||||
class micro_rtsp_source
|
||||
{
|
||||
public:
|
||||
virtual void update_frame() = 0;
|
||||
|
||||
virtual uint8_t *data() const = 0;
|
||||
virtual size_t width() const = 0;
|
||||
virtual size_t height() const = 0;
|
||||
virtual size_t size() const = 0;
|
||||
};
|
||||
43
lib/micro-rtsp-server/include/micro_rtsp_streamer.h
Normal file
43
lib/micro-rtsp-server/include/micro_rtsp_streamer.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <jpg_section.h>
|
||||
#include <micro_rtsp_camera.h> // Add this line to include the definition of micro_rtsp_camera
|
||||
#include <micro_rtsp_structs.h>
|
||||
|
||||
// https://en.wikipedia.org/wiki/Maximum_transmission_unit
|
||||
constexpr size_t max_wifi_mtu = 2304;
|
||||
// Payload JPG - https://www.ietf.org/rfc/rfc1890.txt
|
||||
constexpr uint8_t RTP_PAYLOAD_JPG = 26;
|
||||
|
||||
// One of the types below will be returned, the jpeg_packet_with_quantization_t for the first packet, then the jpeg_packet_t
|
||||
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
rtp_over_tcp_hdr_t rtp_over_tcp_hdr;
|
||||
rtp_hdr_t rtp_hdr;
|
||||
jpeg_hdr_t jpeg_hdr;
|
||||
jpeg_hdr_qtable_t jpeg_hdr_qtable;
|
||||
uint8_t quantization_table_luminance[jpeg_quantization_table_length];
|
||||
uint8_t quantization_table_chrominance[jpeg_quantization_table_length];
|
||||
uint8_t jpeg_data[];
|
||||
} jpeg_packet_with_quantization_t;
|
||||
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
rtp_over_tcp_hdr_t rtp_over_tcp_hdr;
|
||||
rtp_hdr_t rtp_hdr;
|
||||
jpeg_hdr_t jpeg_hdr;
|
||||
uint8_t jpeg_data[];
|
||||
} jpeg_packet_t;
|
||||
|
||||
class micro_rtsp_streamer
|
||||
{
|
||||
public:
|
||||
micro_rtsp_streamer(const micro_rtsp_source& source);
|
||||
rtp_over_tcp_hdr_t *create_jpg_packet(const uint8_t *jpg_scan, const uint8_t *jpg_scan_end, uint8_t **jpg_offset, const uint32_t timestamp, const uint8_t *quantization_table_luminance, const uint8_t *quantization_table_chrominance);
|
||||
|
||||
private:
|
||||
const micro_rtsp_source& source_;
|
||||
uint32_t ssrc_;
|
||||
uint16_t sequence_number_;
|
||||
};
|
||||
51
lib/micro-rtsp-server/include/micro_rtsp_structs.h
Normal file
51
lib/micro-rtsp-server/include/micro_rtsp_structs.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// https://www.ietf.org/rfc/rfc2326#section-10.12
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
char magic = '$'; // Magic encapsulation ASCII dollar sign (24 hexadecimal)
|
||||
uint8_t channel; // Channel identifier
|
||||
uint16_t length; // Network order
|
||||
} rtp_over_tcp_hdr_t;
|
||||
|
||||
// RTP data header - http://www.ietf.org/rfc/rfc3550.txt
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
uint16_t version : 2; // protocol version
|
||||
uint16_t padding : 1; // padding flag
|
||||
uint16_t extension : 1; // header extension flag
|
||||
uint16_t cc : 4; // CSRC count
|
||||
uint16_t marker : 1; // marker bit
|
||||
uint16_t pt : 7; // payload type
|
||||
uint16_t seq : 16; // sequence number
|
||||
uint32_t ts; // timestamp
|
||||
uint32_t ssrc; // synchronization source
|
||||
} rtp_hdr_t;
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc2435
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
uint32_t tspec : 8; // type-specific field
|
||||
uint32_t off : 24; // fragment byte offset
|
||||
uint8_t type; // id of jpeg decoder params
|
||||
uint8_t q; // Q values 0-127 indicate the quantization tables. JPEG types 0 and 1 (and their corresponding types 64 and 65)
|
||||
uint8_t width; // frame width in 8 pixel blocks
|
||||
uint8_t height; // frame height in 8 pixel blocks
|
||||
} jpeg_hdr_t;
|
||||
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
uint16_t dri;
|
||||
uint16_t f : 1;
|
||||
uint16_t l : 1;
|
||||
uint16_t count : 14;
|
||||
} jpeg_hdr_rst_t;
|
||||
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
uint8_t mbz;
|
||||
uint8_t precision;
|
||||
uint16_t length;
|
||||
} jpeg_hdr_qtable_t;
|
||||
20
lib/micro-rtsp-server/library.json
Normal file
20
lib/micro-rtsp-server/library.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "micro-rtsp-streamer",
|
||||
"version": "1.0.0",
|
||||
"description": "RTSP Server",
|
||||
"keywords": "",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rzeldent/micro-rtsp-streamer"
|
||||
},
|
||||
|
||||
"build": {
|
||||
"srcDir": "src/",
|
||||
"includeDir": "include/"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"micro-jpg": "^1.0.0",
|
||||
"espressif/esp32-camera": "^2.0.4"
|
||||
}
|
||||
}
|
||||
39
lib/micro-rtsp-server/src/micro_rtsp_camera.cpp
Normal file
39
lib/micro-rtsp-server/src/micro_rtsp_camera.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include <esp32-hal-log.h>
|
||||
|
||||
#include "micro_rtsp_camera.h"
|
||||
|
||||
micro_rtsp_camera::micro_rtsp_camera()
|
||||
{
|
||||
init_result_ == ESP_FAIL;
|
||||
}
|
||||
|
||||
micro_rtsp_camera::~micro_rtsp_camera()
|
||||
{
|
||||
deinitialize();
|
||||
}
|
||||
|
||||
esp_err_t micro_rtsp_camera::initialize(camera_config_t *camera_config)
|
||||
{
|
||||
log_v("camera_config={.pin_pwdn:%u,.pin_reset:%u,.pin_xclk:%u,.pin_sccb_sda:%u,.pin_sccb_scl:%u,.pin_d7:%u,.pin_d6:%u,.pin_d5:%u,.pin_d4:%u,.pin_d3:%u,.pin_d2:%u,.pin_d1:%u,.pin_d0:%u,.pin_vsync:%u,.pin_href:%u,.pin_pclk:%u,.xclk_freq_hz:%d,.ledc_timer:%u,ledc_channel:%u,.pixel_format:%d,.frame_size:%d,.jpeg_quality:%d,.fb_count:%d,.fb_location%d,.grab_mode:%d,sccb_i2c_port:%d}", camera_config->pin_pwdn, camera_config->pin_reset, camera_config->pin_xclk, camera_config->pin_sccb_sda, camera_config->pin_sccb_scl, camera_config->pin_d7, camera_config->pin_d6, camera_config->pin_d5, camera_config->pin_d4, camera_config->pin_d3, camera_config->pin_d2, camera_config->pin_d1, camera_config->pin_d0, camera_config->pin_vsync, camera_config->pin_href, camera_config->pin_pclk, camera_config->xclk_freq_hz, camera_config->ledc_timer, camera_config->ledc_channel, camera_config->pixel_format, camera_config->frame_size, camera_config->jpeg_quality, camera_config->fb_count, camera_config->fb_location, camera_config->grab_mode, camera_config->sccb_i2c_port);
|
||||
|
||||
init_result_ = esp_camera_init(camera_config);
|
||||
if (init_result_ == ESP_OK)
|
||||
update_frame();
|
||||
else
|
||||
log_e("Camera initialization failed: 0x%02x", init_result_);
|
||||
|
||||
return init_result_;
|
||||
}
|
||||
|
||||
esp_err_t micro_rtsp_camera::deinitialize()
|
||||
{
|
||||
return init_result_ == ESP_OK ? esp_camera_deinit() : ESP_OK;
|
||||
}
|
||||
|
||||
void micro_rtsp_camera::update_frame()
|
||||
{
|
||||
if (fb_)
|
||||
esp_camera_fb_return(fb_);
|
||||
|
||||
fb_ = esp_camera_fb_get();
|
||||
}
|
||||
217
lib/micro-rtsp-server/src/micro_rtsp_requests.cpp
Normal file
217
lib/micro-rtsp-server/src/micro_rtsp_requests.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include <esp32-hal-log.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <unordered_map>
|
||||
#include <regex>
|
||||
#include "micro_rtsp_requests.h"
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc2326
|
||||
|
||||
const std::string micro_rtsp_requests::available_stream_name_ = "/mjpeg/1";
|
||||
|
||||
bool micro_rtsp_requests::parse_client_port(const std::string &request)
|
||||
{
|
||||
log_v("request: %s", request.c_str());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
start_client_port_ = std::stoi(match[1].str());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string micro_rtsp_requests::handle_rtsp_error(unsigned long cseq, unsigned short code, const std::string &message)
|
||||
{
|
||||
log_e("code: %d, message: %s", code, message.c_str());
|
||||
auto now = time(nullptr);
|
||||
std::ostringstream oss;
|
||||
oss << "RTSP/1.0 " << code << " " << message << "\r\n"
|
||||
<< "CSeq: " << cseq << "\r\n"
|
||||
<< std::put_time(std::gmtime(&now), "Date: %a, %b %d %Y %H:%M:%S GMT") << "\r\n";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// OPTIONS rtsp://192.168.178.247:554/mjpeg/1 RTSP/1.0
|
||||
// CSeq: 2
|
||||
// User-Agent: LibVLC/3.0.20 (LIVE555 Streaming Media v2016.11.28)
|
||||
std::string micro_rtsp_requests::handle_options(unsigned long cseq)
|
||||
{
|
||||
auto now = time(nullptr);
|
||||
std::ostringstream oss;
|
||||
oss << "RTSP/1.0 200 OK\r\n"
|
||||
<< "CSeq: " << cseq << "\r\n"
|
||||
<< std::put_time(std::gmtime(&now), "Date: %a, %b %d %Y %H:%M:%S GMT") << "\r\n"
|
||||
<< "Content-Length: 0\r\n"
|
||||
<< "Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n"
|
||||
<< "\r\n";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// DESCRIBE rtsp://192.168.178.247:554/mjpeg/1 RTSP/1.0
|
||||
// CSeq: 3
|
||||
// User-Agent: LibVLC/3.0.20 (LIVE555 Streaming Media v2016.11.28)
|
||||
// Accept: application/sdp
|
||||
std::string micro_rtsp_requests::handle_describe(unsigned long cseq, const std::string &request)
|
||||
{
|
||||
// Parse the url
|
||||
static const std::regex regex_url("rtsp:\\/\\/([^\\/:]+)(?::(\\d+))?(\\/.*)?\\s+RTSP\\/1\\.0", std::regex_constants::icase);
|
||||
std::smatch match;
|
||||
if (!std::regex_search(request, match, regex_url))
|
||||
return handle_rtsp_error(cseq, 400, "Invalid URL");
|
||||
|
||||
auto host = match[1].str();
|
||||
auto port = match[2].str().length() > 0 ? std::stoi(match[2].str()) : 554;
|
||||
auto path = match[3].str();
|
||||
log_i("host: %s, port: %d, path: %s", host.c_str(), port, path.c_str());
|
||||
|
||||
if (path != available_stream_name_)
|
||||
return handle_rtsp_error(cseq, 404, "Stream Not Found");
|
||||
|
||||
std::ostringstream osbody;
|
||||
osbody << "v=0\r\n"
|
||||
<< "o=- " << std::rand() << " 1 IN IP4 " << host << "\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 body = osbody.str();
|
||||
|
||||
auto now = time(nullptr);
|
||||
std::ostringstream oss;
|
||||
oss << "RTSP/1.0 200 OK\r\n"
|
||||
<< "CSeq: " << cseq << "\r\n"
|
||||
<< std::put_time(std::gmtime(&now), "Date: %a, %b %d %Y %H:%M:%S GMT") << "\r\n"
|
||||
<< "Content-Base: rtsp://" << host << ":" << port << path << "/" << "\r\n"
|
||||
<< "Content-Type: application/sdp\r\n"
|
||||
<< "Content-Length: " << body.size() << "\r\n"
|
||||
<< "\r\n"
|
||||
<< body;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// SETUP rtsp://192.168.178.247:554/mjpeg/1 RTSP/1.0
|
||||
// CSeq: 0
|
||||
// Transport: RTP/AVP;unicast;client_port=9058-9059
|
||||
std::string micro_rtsp_requests::handle_setup(unsigned long cseq, const std::map<std::string, std::string> &lines)
|
||||
{
|
||||
log_v("request: %s", request.c_str());
|
||||
|
||||
auto it = lines.find("Transport");
|
||||
if (it == lines.end())
|
||||
return handle_rtsp_error(cseq, 400, "No Transport Header Found");
|
||||
|
||||
static const std::regex regex_transport("\\s+RTP\\/AVP(\\/TCP)?;unicast;client_port=(\\d+)-(\\d+)", std::regex_constants::icase);
|
||||
std::smatch match;
|
||||
if (!std::regex_search(it->second, match, regex_transport))
|
||||
return handle_rtsp_error(cseq, 400, "Could Not Parse Transport");
|
||||
|
||||
tcp_transport_ = match[1].str().length() > 0;
|
||||
start_client_port_ = std::stoi(match[2].str());
|
||||
end_client_port_ = std::stoi(match[3].str());
|
||||
log_i("tcp_transport: %d, start_client_port: %d, end_client_port: %d", tcp_transport_, start_client_port_, end_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=" << start_client_port_ << "-" << end_client_port_ + 1 << ";server_port=" << rtp_streamer_port_ << "-" << rtp_streamer_port_/*rtcp_streamer_port_*/;
|
||||
|
||||
auto now = time(nullptr);
|
||||
std::ostringstream oss;
|
||||
oss << "RTSP/1.0 200 OK\r\n"
|
||||
<< "CSeq: " << cseq << "\r\n"
|
||||
<< std::put_time(std::gmtime(&now), "Date: %a, %b %d %Y %H:%M:%S GMT") << "\r\n"
|
||||
<< "Transport: " << ostransport.str() << "\r\n"
|
||||
<< "Session: " << rtsp_session_id_<< "\r\n";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string micro_rtsp_requests::handle_play(unsigned long cseq)
|
||||
{
|
||||
log_v("request: %s", request.c_str());
|
||||
|
||||
stream_active_ = true;
|
||||
|
||||
auto now = time(nullptr);
|
||||
std::ostringstream oss;
|
||||
oss << "RTSP/1.0 200 OK\r\n"
|
||||
<< "CSeq: " << cseq << "\r\n"
|
||||
<< std::put_time(std::gmtime(&now), "Date: %a, %b %d %Y %H:%M:%S GMT") << "\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(unsigned long cseq)
|
||||
{
|
||||
log_v("request: %s", request.c_str());
|
||||
|
||||
stream_stopped_ = true;
|
||||
|
||||
auto now = time(nullptr);
|
||||
std::ostringstream oss;
|
||||
oss << "RTSP/1.0 200 OK\r\n"
|
||||
<< "CSeq: " << cseq << "\r\n"
|
||||
<< std::put_time(std::gmtime(&now), "Date: %a, %b %d %Y %H:%M:%S GMT") << "\r\n"
|
||||
<< "\r\n";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// Parse a request e.g.
|
||||
// Request: OPTIONS rtsp://192.168.178.247:554/mjpeg/1 RTSP/1.0
|
||||
// CSeq: 2
|
||||
// User-Agent: LibVLC/3.0.20 (LIVE555 Streaming Media v2016.11.28)
|
||||
|
||||
std::string micro_rtsp_requests::process_request(const std::string &request)
|
||||
{
|
||||
log_v("request: %s", request.c_str());
|
||||
|
||||
std::stringstream ss(request);
|
||||
// Get the request line
|
||||
std::string request_line;
|
||||
if (!std::getline(ss, request_line))
|
||||
return handle_rtsp_error(0, 400, "No Request Found");
|
||||
|
||||
// Create a map with headers
|
||||
std::string line;
|
||||
std::map<std::string, std::string> headers;
|
||||
std::size_t pos;
|
||||
while (std::getline(ss, line))
|
||||
{
|
||||
if ((pos = line.find(':')) != std::string::npos)
|
||||
headers[line.substr(0, pos)] = line.substr(pos + 1);
|
||||
// else
|
||||
// log_e("No : found for header: %s", line.c_str());
|
||||
}
|
||||
|
||||
log_i("request_line: %s", request_line.c_str());
|
||||
for (const auto &header : headers)
|
||||
log_i("header: %s: %s", header.first.c_str(), header.second.c_str());
|
||||
|
||||
// Check for CSeq
|
||||
const auto cseq_it = headers.find("CSeq");
|
||||
if (cseq_it == headers.end())
|
||||
return handle_rtsp_error(0, 400, "No Sequence Found");
|
||||
|
||||
auto cseq = std::stoul(cseq_it->second);
|
||||
|
||||
if (request_line.find("OPTIONS") == 0)
|
||||
return handle_options(cseq);
|
||||
if (request_line.find("DESCRIBE") == 0)
|
||||
return handle_describe(cseq, request_line);
|
||||
if (request_line.find("SETUP") == 0)
|
||||
return handle_setup(cseq, headers);
|
||||
if (request_line.find("PLAY") == 0)
|
||||
return handle_play(cseq);
|
||||
if (request_line.find("TEARDOWN") == 0)
|
||||
return handle_teardown(cseq);
|
||||
|
||||
return handle_rtsp_error(cseq, 400, "Unknown Command or malformed request");
|
||||
}
|
||||
108
lib/micro-rtsp-server/src/micro_rtsp_server.cpp
Normal file
108
lib/micro-rtsp-server/src/micro_rtsp_server.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <micro_rtsp_server.h>
|
||||
#include <jpg.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// Check client connections every 100 milliseconds
|
||||
#define CHECK_CLIENT_INTERVAL 10
|
||||
|
||||
micro_rtsp_server::micro_rtsp_server(micro_rtsp_source &source)
|
||||
: source_(source), streamer_(source)
|
||||
{
|
||||
}
|
||||
|
||||
micro_rtsp_server::~micro_rtsp_server()
|
||||
{
|
||||
end();
|
||||
}
|
||||
|
||||
void micro_rtsp_server::begin(unsigned short port /*= 554*/)
|
||||
{
|
||||
WiFiServer::begin(port);
|
||||
}
|
||||
|
||||
void micro_rtsp_server::end()
|
||||
{
|
||||
WiFiServer::end();
|
||||
}
|
||||
|
||||
void micro_rtsp_server::loop()
|
||||
{
|
||||
auto now = millis();
|
||||
|
||||
if (next_check_client_ < now)
|
||||
{
|
||||
log_v("Check for new client");
|
||||
next_check_client_ = now + CHECK_CLIENT_INTERVAL;
|
||||
|
||||
// Check if a client wants to connect
|
||||
auto client = accept();
|
||||
if (client)
|
||||
clients_.push_back(rtsp_client(client));
|
||||
|
||||
// Check for idle clients
|
||||
clients_.remove_if([](rtsp_client &c)
|
||||
{ return !c.connected(); });
|
||||
|
||||
for (auto client : clients_)
|
||||
client.handle_request();
|
||||
}
|
||||
|
||||
if (next_frame_update_ < now)
|
||||
{
|
||||
log_v("Stream frame t=%d", next_frame_update_);
|
||||
next_frame_update_ = now + frame_interval_;
|
||||
|
||||
auto ts = time(nullptr);
|
||||
// Get next jpg frame
|
||||
source_.update_frame();
|
||||
// Decode to get quantitation- and scan data
|
||||
jpg jpg;
|
||||
auto jpg_data = source_.data();
|
||||
auto jpg_size = source_.size();
|
||||
assert(jpg.decode(jpg_data, jpg_size));
|
||||
|
||||
auto jpg_scan_current = (uint8_t *)jpg.jpeg_data_start;
|
||||
while (jpg_scan_current < jpg.jpeg_data_end)
|
||||
{
|
||||
auto packet = streamer_.create_jpg_packet(jpg.jpeg_data_start, jpg.jpeg_data_end, &jpg_scan_current, ts, jpg.quantization_table_luminance_->data, jpg.quantization_table_chrominance_->data);
|
||||
for (auto client : clients_)
|
||||
{
|
||||
log_i("Stream frame to client: 0x%08x", client);
|
||||
// RTP over TCP encapsulates in a $
|
||||
client.write((const uint8_t *)packet, packet->length + sizeof(rtp_over_tcp_hdr_t));
|
||||
// TODO: UDP
|
||||
}
|
||||
free(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
micro_rtsp_server::rtsp_client::rtsp_client(const WiFiClient &wifi_client)
|
||||
: WiFiClient(wifi_client)
|
||||
{
|
||||
}
|
||||
|
||||
micro_rtsp_server::rtsp_client::~rtsp_client()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void micro_rtsp_server::rtsp_client::handle_request()
|
||||
{
|
||||
// Read if data available
|
||||
auto bytes_available = available();
|
||||
if (bytes_available > 0)
|
||||
{
|
||||
std::string request(bytes_available, '\0');
|
||||
if (read((uint8_t *)&request[0], bytes_available) == bytes_available)
|
||||
{
|
||||
request.resize(bytes_available);
|
||||
log_i("Request: %s", request.c_str());
|
||||
auto response = process_request(request);
|
||||
log_i("Response: %s", response.c_str());
|
||||
println(response.c_str());
|
||||
println();
|
||||
}
|
||||
}
|
||||
}
|
||||
82
lib/micro-rtsp-server/src/micro_rtsp_streamer.cpp
Normal file
82
lib/micro-rtsp-server/src/micro_rtsp_streamer.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include <stddef.h>
|
||||
#include <memory.h>
|
||||
#include <esp32-hal-log.h>
|
||||
|
||||
#include "micro_rtsp_streamer.h"
|
||||
#include "esp_random.h"
|
||||
|
||||
micro_rtsp_streamer::micro_rtsp_streamer(const micro_rtsp_source &source)
|
||||
: source_(source)
|
||||
{
|
||||
// Random number
|
||||
ssrc_ = esp_random();
|
||||
sequence_number_ = 0;
|
||||
}
|
||||
|
||||
rtp_over_tcp_hdr_t *micro_rtsp_streamer::create_jpg_packet(const uint8_t *jpg_scan, const uint8_t *jpg_scan_end, uint8_t **jpg_offset, const uint32_t timestamp, const uint8_t *quantization_table_luminance, const uint8_t *quantization_table_chrominance)
|
||||
{
|
||||
log_v("jpg_scan:0x%08x, jpg_scan_end:0x%08x, jpg_offset:0x%08x, timestamp:%d, quantization_table_luminance:0x%08x, quantization_table_chrominance:0x%08x", jpg_scan, jpg_scan_end, jpg_offset, timestamp, quantization_table_luminance, quantization_table_chrominance);
|
||||
|
||||
// The MTU of wireless networks is 2,312 bytes. This size includes the packet headers.
|
||||
const auto isFirstFragment = jpg_scan == *jpg_offset;
|
||||
const auto include_quantization_tables = isFirstFragment && quantization_table_luminance != nullptr && quantization_table_chrominance != nullptr;
|
||||
// Quantization tables musty be included in the first packet
|
||||
const auto headers_size = include_quantization_tables ? sizeof(jpeg_packet_with_quantization_t) : sizeof(jpeg_packet_t);
|
||||
const auto payload_size = max_wifi_mtu - headers_size;
|
||||
|
||||
const auto jpg_bytes_left = jpg_scan_end - *jpg_offset;
|
||||
const bool isLastFragment = jpg_bytes_left <= payload_size;
|
||||
const auto jpg_bytes = isLastFragment ? jpg_bytes_left : payload_size;
|
||||
const uint16_t packet_size = headers_size + jpg_bytes;
|
||||
|
||||
const auto packet = static_cast<jpeg_packet_t *>(calloc(1, packet_size));
|
||||
|
||||
// 4 bytes RTP over TCP header
|
||||
packet->rtp_over_tcp_hdr.channel = 0;
|
||||
packet->rtp_over_tcp_hdr.length = packet_size;
|
||||
log_v("rtp_over_tcp_hdr_t={.magic=%c,.channel=%u,.length=%u}", packet->rtp_over_tcp_hdr.magic, packet->rtp_over_tcp_hdr.channel, packet->rtp_over_tcp_hdr.length);
|
||||
|
||||
// 12 bytes RTP header
|
||||
packet->rtp_hdr.version = 2;
|
||||
packet->rtp_hdr.marker = isLastFragment;
|
||||
packet->rtp_hdr.pt = RTP_PAYLOAD_JPG;
|
||||
packet->rtp_hdr.seq = sequence_number_;
|
||||
packet->rtp_hdr.ts = timestamp;
|
||||
packet->rtp_hdr.ssrc = ssrc_;
|
||||
log_v("rtp_hdr={.version:%u,.padding:%u,.extension:%u,.cc:%u,.marker:%u,.pt:%u,.seq:%u,.ts:%u,.ssrc:%u}", packet->rtp_hdr.version, packet->rtp_hdr.padding, packet->rtp_hdr.extension, packet->rtp_hdr.cc, packet->rtp_hdr.marker, packet->rtp_hdr.pt, packet->rtp_hdr.seq, packet->rtp_hdr.ts, packet->rtp_hdr.ssrc);
|
||||
|
||||
// 8 bytes JPEG payload header
|
||||
packet->jpeg_hdr.tspec = 0; // type-specific field
|
||||
packet->jpeg_hdr.off = (uint32_t)(*jpg_offset - jpg_scan); // fragment byte offset (24 bits in jpg)
|
||||
packet->jpeg_hdr.type = 0; // id of jpeg decoder params
|
||||
packet->jpeg_hdr.q = (uint8_t)(include_quantization_tables ? 0x80 : 0x5e); // quantization factor (or table id) 5eh=94d
|
||||
packet->jpeg_hdr.width = (uint8_t)(source_.width() >> 3); // frame width in 8 pixel blocks
|
||||
packet->jpeg_hdr.height = (uint8_t)(source_.height() >> 3); // frame height in 8 pixel blocks
|
||||
log_v("jpeg_hdr={.tspec:%u,.off:0x%6x,.type:0x2%x,.q:%u,.width:%u.height:%u}", packet->jpeg_hdr.tspec, packet->jpeg_hdr.off, packet->jpeg_hdr.type, packet->jpeg_hdr.q, packet->jpeg_hdr.width, packet->jpeg_hdr.height);
|
||||
|
||||
// Only in first packet of the frame
|
||||
if (include_quantization_tables)
|
||||
{
|
||||
auto packet_with_quantization = reinterpret_cast<jpeg_packet_with_quantization_t *>(packet);
|
||||
packet_with_quantization->jpeg_hdr_qtable.mbz = 0;
|
||||
packet_with_quantization->jpeg_hdr_qtable.precision = 0; // 8 bit precision
|
||||
packet_with_quantization->jpeg_hdr_qtable.length = jpeg_quantization_table_length + jpeg_quantization_table_length;
|
||||
log_v("jpeg_hdr_qtable={.mbz:%u,.precision:%u,.length:%u}", packet_with_quantization->jpeg_hdr_qtable.mbz, packet_with_quantization->jpeg_hdr_qtable.precision, packet_with_quantization->jpeg_hdr_qtable.length);
|
||||
memcpy(packet_with_quantization->quantization_table_luminance, quantization_table_luminance, jpeg_quantization_table_length);
|
||||
memcpy(packet_with_quantization->quantization_table_chrominance, quantization_table_chrominance, jpeg_quantization_table_length);
|
||||
// Copy JPG data
|
||||
memcpy(packet_with_quantization->jpeg_data, *jpg_offset, jpg_bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy JPG data
|
||||
memcpy(packet->jpeg_data, *jpg_offset, jpg_bytes);
|
||||
}
|
||||
|
||||
// Update JPG offset
|
||||
*jpg_offset += jpg_bytes;
|
||||
// Update sequence number
|
||||
sequence_number_++;
|
||||
|
||||
return (rtp_over_tcp_hdr_t *)packet;
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
###############################################################################
|
||||
[platformio]
|
||||
#default_envs = esp32cam_ai_thinker
|
||||
default_envs = esp32cam_ai_thinker
|
||||
#default_envs = esp32cam_espressif_esp_eye
|
||||
#default_envs = esp32cam_espressif_esp32s2_cam_board
|
||||
#default_envs = esp32cam_espressif_esp32s2_cam_header
|
||||
@@ -31,23 +31,25 @@
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
test_framework = unity
|
||||
|
||||
#upload_protocol = espota
|
||||
#upload_port = 192.168.178.223
|
||||
#upload_flags = --auth='ESP32CAM-RTSP'
|
||||
|
||||
# Partition scheme for OTA
|
||||
board_build.partitions = min_spiffs.csv
|
||||
#board_build.partitions = max_spiffs.csv
|
||||
|
||||
monitor_speed = 115200
|
||||
monitor_rts = 0
|
||||
monitor_dtr = 0
|
||||
monitor_filters = log2file, time, default, esp32_exception_decoder
|
||||
#monitor_filters = log2file, time, default, esp32_exception_decoder
|
||||
monitor_filters = esp32_exception_decoder
|
||||
|
||||
build_flags =
|
||||
-Ofast
|
||||
-D 'BOARD_NAME="${this.board}"'
|
||||
-D 'CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE'
|
||||
-D 'CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO'
|
||||
-D 'IOTWEBCONF_PASSWORD_LEN=64'
|
||||
|
||||
board_build.embed_txtfiles =
|
||||
@@ -56,7 +58,8 @@ board_build.embed_txtfiles =
|
||||
lib_deps =
|
||||
prampec/IotWebConf@^3.2.1
|
||||
geeksville/Micro-RTSP@^0.1.6
|
||||
rzeldent/micro-moustache@^1.0.1
|
||||
rzeldent/micro-moustache
|
||||
rzeldent/micro-timezonedb
|
||||
|
||||
[env:esp32cam_ai_thinker]
|
||||
board = esp32cam_ai_thinker
|
||||
|
||||
56
src/main.cpp
56
src/main.cpp
@@ -16,6 +16,9 @@
|
||||
#include <moustache.h>
|
||||
#include <settings.h>
|
||||
|
||||
#include <micro_rtsp_camera.h>
|
||||
#include <micro_rtsp_server.h>
|
||||
|
||||
// HTML files
|
||||
extern const char index_html_min_start[] asm("_binary_html_index_min_html_start");
|
||||
|
||||
@@ -47,11 +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();
|
||||
|
||||
// Camera
|
||||
OV2640 cam;
|
||||
// OV2640 cam;
|
||||
// DNS Server
|
||||
DNSServer dnsServer;
|
||||
// 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);
|
||||
|
||||
// Web server
|
||||
WebServer web_server(80);
|
||||
|
||||
@@ -100,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()},
|
||||
@@ -162,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");
|
||||
@@ -175,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"
|
||||
@@ -197,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");
|
||||
@@ -218,7 +225,11 @@ esp_err_t initialize_camera()
|
||||
log_i("JPEG quality: %d", param_jpg_quality.value());
|
||||
auto jpeg_quality = param_jpg_quality.value();
|
||||
log_i("Frame duration: %d ms", param_frame_duration.value());
|
||||
const camera_config_t camera_config = {
|
||||
|
||||
// Set frame duration
|
||||
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
|
||||
.pin_reset = CAMERA_CONFIG_PIN_RESET, // GPIO pin for camera reset line
|
||||
.pin_xclk = CAMERA_CONFIG_PIN_XCLK, // GPIO pin for camera XCLK line
|
||||
@@ -247,10 +258,12 @@ esp_err_t initialize_camera()
|
||||
#if CONFIG_CAMERA_CONVERTER_ENABLED
|
||||
conv_mode = CONV_DISABLE, // RGB<->YUV Conversion mode
|
||||
#endif
|
||||
.sccb_i2c_port = 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 cam.init(camera_config);
|
||||
return camera.initialize(&camera_config);
|
||||
|
||||
// return cam.init(camera_config);
|
||||
}
|
||||
|
||||
void update_camera_settings()
|
||||
@@ -289,7 +302,8 @@ void update_camera_settings()
|
||||
void 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));
|
||||
|
||||
server.begin(RTSP_PORT);
|
||||
// Add RTSP service to mDNS
|
||||
// HTTP is already set by iotWebConf
|
||||
MDNS.addService("rtsp", "tcp", RTSP_PORT);
|
||||
@@ -374,21 +388,24 @@ void setup()
|
||||
#endif
|
||||
iotWebConf.init();
|
||||
|
||||
// Set the time servers
|
||||
configTime(0, 0, NTP_SERVERS);
|
||||
|
||||
// Try to initialize 3 times
|
||||
for (auto i = 0; i < 3; i++)
|
||||
{
|
||||
log_i("Initializing camera...");
|
||||
camera_init_result = initialize_camera();
|
||||
if (camera_init_result == ESP_OK)
|
||||
{
|
||||
update_camera_settings();
|
||||
break;
|
||||
}
|
||||
|
||||
esp_camera_deinit();
|
||||
log_e("Failed to initialize camera. Error: 0x%0x. Frame size: %s, frame rate: %d ms, jpeg quality: %d", camera_init_result, param_frame_size.value(), param_frame_duration.value(), param_jpg_quality.value());
|
||||
log_e("Failed to initialize camera. Error: 0x%04x. Frame size: %s, frame rate: %d ms, jpeg quality: %d", camera_init_result, param_frame_size.value(), param_frame_duration.value(), param_jpg_quality.value());
|
||||
delay(500);
|
||||
}
|
||||
|
||||
update_camera_settings();
|
||||
|
||||
// Set up required URL handlers on the web server
|
||||
web_server.on("/", HTTP_GET, handle_root);
|
||||
web_server.on("/config", []
|
||||
@@ -406,6 +423,5 @@ void loop()
|
||||
{
|
||||
iotWebConf.doLoop();
|
||||
|
||||
if (camera_server)
|
||||
camera_server->doLoop();
|
||||
server.loop();
|
||||
}
|
||||
127
test/test_main.cpp
Normal file
127
test/test_main.cpp
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user