forked from external-repos/esp32cam-rtsp
WIP
This commit is contained in:
@@ -7,12 +7,12 @@ class jpg
|
|||||||
public:
|
public:
|
||||||
bool decode(const uint8_t *jpg, size_t size);
|
bool decode(const uint8_t *jpg, size_t size);
|
||||||
|
|
||||||
const struct jpg_section *quantization_table_0_;
|
const jpg_section_t *quantization_table_luminance_;
|
||||||
const struct jpg_section *quantization_table_1_;
|
const jpg_section_t *quantization_table_chrominance_;
|
||||||
|
|
||||||
const uint8_t *jpeg_data_start;
|
const uint8_t *jpeg_data_start;
|
||||||
const uint8_t *jpeg_data_end;
|
const uint8_t *jpeg_data_end;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const jpg_section *find_jpg_section(const uint8_t **ptr, const uint8_t *end, jpg_section::jpg_section_flag flag);
|
static const jpg_section_t *find_jpg_section(const uint8_t **ptr, const uint8_t *end, jpg_section_t::jpg_section_flag flag);
|
||||||
};
|
};
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
struct __attribute__((packed)) jpg_section
|
typedef struct __attribute__((packed))
|
||||||
{
|
{
|
||||||
enum jpg_section_flag : uint8_t
|
enum jpg_section_flag : uint8_t
|
||||||
{
|
{
|
||||||
@@ -82,9 +82,9 @@ struct __attribute__((packed)) jpg_section
|
|||||||
static const char *flag_name(const jpg_section_flag flag);
|
static const char *flag_name(const jpg_section_flag flag);
|
||||||
uint16_t data_length() const;
|
uint16_t data_length() const;
|
||||||
uint16_t section_length() const;
|
uint16_t section_length() const;
|
||||||
};
|
} jpg_section_t;
|
||||||
|
|
||||||
struct __attribute__((packed)) jpg_section_app0 // 0xffe0
|
typedef struct __attribute__((packed)) // 0xffe0
|
||||||
{
|
{
|
||||||
char identifier[5] = {'J', 'F', 'I', 'F', 0}; // JFIF identifier, zero-terminated
|
char identifier[5] = {'J', 'F', 'I', 'F', 0}; // JFIF identifier, zero-terminated
|
||||||
uint8_t version_major = 1;
|
uint8_t version_major = 1;
|
||||||
@@ -94,10 +94,10 @@ struct __attribute__((packed)) jpg_section_app0 // 0xffe0
|
|||||||
uint16_t density_ver = 1; // density: 1 pixel "per pixel" horizontally and vertically
|
uint16_t density_ver = 1; // density: 1 pixel "per pixel" horizontally and vertically
|
||||||
uint8_t thumbnail_hor = 0;
|
uint8_t thumbnail_hor = 0;
|
||||||
uint8_t thumbnail_ver = 0; // no thumbnail (size 0 x 0)
|
uint8_t thumbnail_ver = 0; // no thumbnail (size 0 x 0)
|
||||||
};
|
} jpg_section_app0_t;
|
||||||
|
|
||||||
struct __attribute__((packed)) jpg_section_dqt // 0xffdb
|
typedef struct __attribute__((packed)) // 0xffdb
|
||||||
{
|
{
|
||||||
uint8_t id; // 0= quantLuminance, 1= quantChrominance
|
uint8_t id; // 0= quantLuminance, 1= quantChrominance
|
||||||
uint8_t data[64];
|
uint8_t data[64];
|
||||||
};
|
} jpg_section_dqt_t;
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
#include <esp32-hal-log.h>
|
#include <esp32-hal-log.h>
|
||||||
#include "jpg.h"
|
#include "jpg.h"
|
||||||
|
|
||||||
const jpg_section *jpg::find_jpg_section(const uint8_t **ptr, const uint8_t *end, jpg_section::jpg_section_flag flag)
|
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::flag_name(flag));
|
log_d("find_jpeg_section 0x%02x (%s)", flag, jpg_section_t::flag_name(flag));
|
||||||
while (*ptr < end)
|
while (*ptr < end)
|
||||||
{
|
{
|
||||||
// flag, len MSB, len LSB
|
// flag, len MSB, len LSB
|
||||||
auto section = reinterpret_cast<const jpg_section *>((*ptr));
|
auto section = reinterpret_cast<const jpg_section_t *>((*ptr));
|
||||||
if (section->framing != 0xff)
|
if (section->framing != 0xff)
|
||||||
{
|
{
|
||||||
log_e("Expected framing 0xff but found: 0x%02x", section->framing);
|
log_e("Expected framing 0xff but found: 0x%02x", section->framing);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!jpg_section::is_valid_flag(section->flag))
|
if (!jpg_section_t::is_valid_flag(section->flag))
|
||||||
{
|
{
|
||||||
log_d("Unknown section 0x%02x", flag);
|
log_d("Unknown section 0x%02x", flag);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -24,11 +24,11 @@ const jpg_section *jpg::find_jpg_section(const uint8_t **ptr, const uint8_t *end
|
|||||||
*ptr += section->section_length();
|
*ptr += section->section_length();
|
||||||
if (section->flag == flag)
|
if (section->flag == flag)
|
||||||
{
|
{
|
||||||
log_d("Found section 0x%02x (%s), %d bytes", flag, jpg_section::flag_name(section->flag), section->section_length());
|
log_d("Found section 0x%02x (%s), %d bytes", flag, jpg_section_t::flag_name(section->flag), section->section_length());
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_d("Skipping section: 0x%02x (%s), %d bytes", section->flag, jpg_section::flag_name(section->flag), section->section_length());
|
log_d("Skipping section: 0x%02x (%s), %d bytes", section->flag, jpg_section_t::flag_name(section->flag), section->section_length());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not found
|
// Not found
|
||||||
@@ -44,25 +44,25 @@ bool jpg::decode(const uint8_t *data, size_t size)
|
|||||||
auto end = ptr + size;
|
auto end = ptr + size;
|
||||||
|
|
||||||
// Check for SOI (start of image) 0xff, 0xd8
|
// Check for SOI (start of image) 0xff, 0xd8
|
||||||
if (!find_jpg_section(&ptr, end, jpg_section::jpg_section_flag::SOI))
|
if (!find_jpg_section(&ptr, end, jpg_section_t::jpg_section_flag::SOI))
|
||||||
{
|
{
|
||||||
log_e("No valid start of image marker found");
|
log_e("No valid start of image marker found");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First quantization table (Luminance - black & white images)
|
// First quantization table (Luminance - black & white images)
|
||||||
if (!(quantization_table_0_ = find_jpg_section(&ptr, end, jpg_section::jpg_section_flag::DQT)))
|
if (!(quantization_table_luminance_ = find_jpg_section(&ptr, end, jpg_section_t::jpg_section_flag::DQT)))
|
||||||
{
|
{
|
||||||
log_e("No quantization table 0 section found");
|
log_e("No quantization table 0 section found");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second quantization table (Chrominance - color images)
|
// Second quantization table (Chrominance - color images)
|
||||||
if (!(quantization_table_1_ = find_jpg_section(&ptr, end, jpg_section::jpg_section_flag::DQT)))
|
if (!(quantization_table_chrominance_ = find_jpg_section(&ptr, end, jpg_section_t::jpg_section_flag::DQT)))
|
||||||
log_w("No quantization table 1 section found");
|
log_w("No quantization table 1 section found");
|
||||||
|
|
||||||
// Start of scan
|
// Start of scan
|
||||||
if (!find_jpg_section(&ptr, end, jpg_section::jpg_section_flag::SOS))
|
if (!find_jpg_section(&ptr, end, jpg_section_t::jpg_section_flag::SOS))
|
||||||
{
|
{
|
||||||
log_e("No start of scan section found");
|
log_e("No start of scan section found");
|
||||||
return false;
|
return false;
|
||||||
@@ -77,7 +77,7 @@ bool jpg::decode(const uint8_t *data, size_t size)
|
|||||||
ptr++;
|
ptr++;
|
||||||
|
|
||||||
// Check if marker is an end of image marker
|
// Check if marker is an end of image marker
|
||||||
if (!find_jpg_section(&ptr, end, jpg_section::jpg_section_flag::EOI))
|
if (!find_jpg_section(&ptr, end, jpg_section_t::jpg_section_flag::EOI))
|
||||||
{
|
{
|
||||||
log_e("No end of image marker found");
|
log_e("No end of image marker found");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
#include "jpg_section.h"
|
#include "jpg_section.h"
|
||||||
|
|
||||||
uint16_t jpg_section::data_length() const
|
uint16_t jpg_section_t::data_length() const
|
||||||
{
|
{
|
||||||
return (length_msb << 8) + length_lsb - sizeof(jpg_section::length_msb)- sizeof(jpg_section::length_lsb);
|
return (length_msb << 8) + length_lsb - sizeof(jpg_section_t::length_msb)- sizeof(jpg_section_t::length_lsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t jpg_section::section_length() const
|
uint16_t jpg_section_t::section_length() const
|
||||||
{
|
{
|
||||||
return flag == SOI || flag == EOI ? sizeof(jpg_section::framing) + sizeof(jpg_section::flag) : sizeof(jpg_section::framing) + sizeof(jpg_section::flag) + (length_msb << 8) + length_lsb;
|
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::is_valid_flag(const jpg_section_flag flag)
|
bool jpg_section_t::is_valid_flag(const jpg_section_flag flag)
|
||||||
{
|
{
|
||||||
return flag >= SOF0 && flag <= COM;
|
return flag >= SOF0 && flag <= COM;
|
||||||
}
|
}
|
||||||
|
|
||||||
// from: https://www.disktuna.com/list-of-jpeg-markers/
|
// from: https://www.disktuna.com/list-of-jpeg-markers/
|
||||||
const char *jpg_section::flag_name(const jpg_section_flag flag)
|
const char *jpg_section_t::flag_name(const jpg_section_flag flag)
|
||||||
{
|
{
|
||||||
switch (flag)
|
switch (flag)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,11 +8,12 @@
|
|||||||
|
|
||||||
#include "micro_rtsp_camera.h"
|
#include "micro_rtsp_camera.h"
|
||||||
#include "micro_rtsp_requests.h"
|
#include "micro_rtsp_requests.h"
|
||||||
|
#include "micro_rtsp_streamer.h"
|
||||||
|
|
||||||
class micro_rtsp_server : WiFiServer
|
class micro_rtsp_server : WiFiServer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
micro_rtsp_server(const micro_rtsp_camera& source, unsigned frame_interval = 100);
|
micro_rtsp_server(micro_rtsp_camera &source, unsigned frame_interval = 100);
|
||||||
~micro_rtsp_server();
|
~micro_rtsp_server();
|
||||||
|
|
||||||
void begin(unsigned short port = 554);
|
void begin(unsigned short port = 554);
|
||||||
@@ -35,12 +36,10 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const micro_rtsp_camera &source_;
|
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_;
|
||||||
|
micro_rtsp_streamer streamer_;
|
||||||
std::list<rtsp_client> clients_;
|
std::list<rtsp_client> clients_;
|
||||||
};
|
};
|
||||||
@@ -1,15 +1,90 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <jpg.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;
|
||||||
|
// 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_luminance_table_length = 64;
|
||||||
|
constexpr size_t jpeg_chrominance_table_length = 64;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
class micro_rtsp_streamer
|
class micro_rtsp_streamer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
micro_rtsp_streamer(const uint16_t width, const uint16_t height);
|
micro_rtsp_streamer(const uint16_t width, const uint16_t height);
|
||||||
size_t create_jpg_packet(const uint8_t *jpg, uint8_t **jpg_current, const uint8_t *jpg_end, const uint32_t timestamp);
|
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:
|
private:
|
||||||
uint16_t width_, height_;
|
uint16_t width_, height_;
|
||||||
uint32_t ssrc_;
|
uint32_t ssrc_;
|
||||||
uint16_t sequenceNumber_;
|
uint16_t sequence_number_;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// 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_luminance_table_length];
|
||||||
|
uint8_t quantization_table_chrominance[jpeg_chrominance_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;
|
||||||
};
|
};
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
#include <micro_rtsp_server.h>
|
#include <micro_rtsp_server.h>
|
||||||
|
#include <jpg.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
// Check client connections every 100 milliseconds
|
// Check client connections every 100 milliseconds
|
||||||
#define CHECK_CLIENT_INTERVAL 10
|
#define CHECK_CLIENT_INTERVAL 10
|
||||||
|
|
||||||
micro_rtsp_server::micro_rtsp_server(const micro_rtsp_camera &source, unsigned frame_interval /*= 100*/)
|
micro_rtsp_server::micro_rtsp_server(micro_rtsp_camera &source, unsigned frame_interval /*= 100*/)
|
||||||
: source_(source)
|
: source_(source), streamer_(source.width(), source.height())
|
||||||
{
|
{
|
||||||
log_i("starting RTSP server");
|
log_i("starting RTSP server");
|
||||||
frame_interval_ = frame_interval;
|
frame_interval_ = frame_interval;
|
||||||
@@ -51,11 +52,27 @@ void micro_rtsp_server::loop()
|
|||||||
if (next_frame_update_ < now)
|
if (next_frame_update_ < now)
|
||||||
{
|
{
|
||||||
next_frame_update_ = now + frame_interval_;
|
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_)
|
for (auto client : clients_)
|
||||||
{
|
{
|
||||||
;
|
;
|
||||||
// client->session->broadcastCurrentFrame(now);
|
// client->session->broadcastCurrentFrame(now);
|
||||||
}
|
}
|
||||||
|
free(packet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,164 +1,89 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
|
#include <esp32-hal-log.h>
|
||||||
|
|
||||||
#include "micro_rtsp_streamer.h"
|
#include "micro_rtsp_streamer.h"
|
||||||
#include "esp_random.h"
|
#include "esp_random.h"
|
||||||
|
|
||||||
// https://github.com/txgcwm/Linux-C-Examples/blob/master/h264/h264dec/rtcp.h
|
|
||||||
|
|
||||||
#define RTP_PAYLOAD_JPG 26
|
|
||||||
|
|
||||||
// RTP data header (http://www.ietf.org/rfc/rfc3550.txt)
|
|
||||||
struct rtp_hdr
|
|
||||||
{
|
|
||||||
uint16_t version : 2; // protocol version
|
|
||||||
uint16_t p : 1; // padding flag
|
|
||||||
uint16_t x : 1; // header extension flag
|
|
||||||
uint16_t cc : 4; // CSRC count
|
|
||||||
uint16_t m : 1; // marker bit
|
|
||||||
uint16_t pt : 7; // payload type
|
|
||||||
uint16_t seq : 16; // sequence number
|
|
||||||
uint32_t ts; // timestamp
|
|
||||||
uint32_t ssrc; // synchronization source
|
|
||||||
uint32_t csrc[]; // optional CSRC list
|
|
||||||
} rtp_hdr;
|
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc2435
|
|
||||||
|
|
||||||
// The following definition is from RFC1890
|
|
||||||
#define RTP_PT_JPEG 26
|
|
||||||
|
|
||||||
struct jpeghdr
|
|
||||||
{
|
|
||||||
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; // quantization factor (or table id)
|
|
||||||
uint8_t width; // frame width in 8 pixel blocks
|
|
||||||
uint8_t height; // frame height in 8 pixel blocks
|
|
||||||
};
|
|
||||||
|
|
||||||
struct jpeghdr_rst
|
|
||||||
{
|
|
||||||
uint16_t dri;
|
|
||||||
uint16_t f : 1;
|
|
||||||
uint16_t l : 1;
|
|
||||||
uint16_t count : 14;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct jpeghdr_qtable
|
|
||||||
{
|
|
||||||
uint8_t mbz;
|
|
||||||
uint8_t precision;
|
|
||||||
uint16_t length;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define RTP_JPEG_RESTART 0x40
|
|
||||||
|
|
||||||
micro_rtsp_streamer::micro_rtsp_streamer(const uint16_t width, const uint16_t height)
|
micro_rtsp_streamer::micro_rtsp_streamer(const uint16_t width, const uint16_t height)
|
||||||
{
|
{
|
||||||
width_ = width;
|
width_ = width;
|
||||||
height_ = height;
|
height_ = height;
|
||||||
// Random number
|
// Random number
|
||||||
ssrc_ = esp_random();
|
ssrc_ = esp_random();
|
||||||
sequenceNumber_ = 0;
|
sequence_number_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MAX_ESP32_MTU 1440
|
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)
|
||||||
|
|
||||||
size_t micro_rtsp_streamer::create_jpg_packet(const uint8_t *jpg, uint8_t **jpg_current, const uint8_t *jpg_end, const uint32_t timestamp)
|
|
||||||
{
|
{
|
||||||
const int MAX_FRAGMENT_SIZE = 1100; // FIXME, pick more carefully
|
// The MTU of wireless networks is 2,312 bytes. This size includes the packet headers.
|
||||||
int fragmentLen = MAX_FRAGMENT_SIZE;
|
const auto isFirstFragment = jpg_scan == *jpg_offset;
|
||||||
auto jpegLen = jpg_end - *jpg_current;
|
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;
|
||||||
|
|
||||||
auto offset = *jpg_current - jpg;
|
const auto jpg_bytes_left = jpg_scan_end - *jpg_offset;
|
||||||
if (fragmentLen + offset > jpegLen) // Shrink last fragment if needed
|
const bool isLastFragment = jpg_bytes_left <= payload_size;
|
||||||
fragmentLen = jpegLen - offset;
|
const auto jpg_bytes = isLastFragment ? jpg_bytes_left : payload_size;
|
||||||
|
const uint16_t packet_size = headers_size + jpg_bytes;
|
||||||
|
|
||||||
// bool isLastFragment = (fragmentOffset + fragmentLen) == jpegLen;
|
const auto packet = (jpeg_packet_t *)calloc(1, packet_size);
|
||||||
|
|
||||||
struct rtp_hdr header = {
|
// 4 bytes RTP over TCP header
|
||||||
.version = 2,
|
assert(4 == sizeof(rtp_over_tcp_hdr_t));
|
||||||
.m = 1, // TODO = 1 if last fragfment
|
packet->rtp_over_tcp_hdr.magic = '$'; // encapsulation
|
||||||
.pt = RTP_PAYLOAD_JPG,
|
packet->rtp_over_tcp_hdr.channel = 0; // number of multiplexed sub-channel on RTPS connection - here the RTP channel
|
||||||
.seq = sequenceNumber_,
|
packet->rtp_over_tcp_hdr.length = packet_size;
|
||||||
.ts = timestamp,
|
log_v("rtp_over_tcp_hdr_t={.magic=%c,.channel=%i,.length=%i}", packet->rtp_over_tcp_hdr.magic, packet->rtp_over_tcp_hdr.channel, packet->rtp_over_tcp_hdr.length);
|
||||||
.ssrc = ssrc_};
|
|
||||||
|
|
||||||
struct jpeghdr jpghdr = {
|
// 12 bytes RTP header
|
||||||
.tspec = 0, // type-specific field
|
assert(12 == sizeof(rtp_hdr_t));
|
||||||
.off = offset, // fragment byte offset
|
packet->rtp_hdr.version = 2;
|
||||||
.type = 0, // id of jpeg decoder params
|
packet->rtp_hdr.marker = isLastFragment;
|
||||||
.q = 0x5e, // quantization factor (or table id)
|
packet->rtp_hdr.pt = RTP_PAYLOAD_JPG;
|
||||||
.width = width_ >> 3, // frame width in 8 pixel blocks
|
packet->rtp_hdr.seq = sequence_number_;
|
||||||
.height = height_ >> 3 // frame height in 8 pixel blocks
|
packet->rtp_hdr.ts = timestamp;
|
||||||
};
|
packet->rtp_hdr.ssrc = ssrc_;
|
||||||
|
log_v("rtp_hdr={.version:%i,.padding:%i,.extension:%i,.cc:%i,.marker:%i,.pt:%i,.seq:%i,.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);
|
||||||
|
|
||||||
uint8_t rtp_buffer[0x800];
|
// 8 bytes JPEG payload header
|
||||||
// memset(RtpBuf, 0x00, sizeof(RtpBuf));
|
assert(8 == sizeof(jpeg_hdr_t));
|
||||||
// Prepare the first 4 byte of the packet. This is the Rtp over Rtsp header in case of TCP based transport
|
packet->jpeg_hdr.tspec = 0; // type-specific field
|
||||||
rtp_buffer[0] = (uint8_t)'$'; // magic number
|
packet->jpeg_hdr.off = (uint32_t)(*jpg_offset - jpg_scan); // fragment byte offset (24 bits in jpg)
|
||||||
rtp_buffer[1] = 0; // number of multiplexed subchannel on RTPS connection - here the RTP channel
|
packet->jpeg_hdr.type = 0; // id of jpeg decoder params
|
||||||
// rtp_buffer[2] = (RtpPacketSize & 0xFF00) >> 8;
|
packet->jpeg_hdr.q = (uint8_t)(include_quantization_tables ? 0x80 : 0x5e); // quantization factor (or table id) 5eh=94d
|
||||||
// rtp_buffer[3] = (RtpPacketSize & 0x00FF);
|
packet->jpeg_hdr.width = (uint8_t)(width_ >> 3); // frame width in 8 pixel blocks
|
||||||
// Prepare the 12 byte RTP header
|
packet->jpeg_hdr.height = (uint8_t)(height_ >> 3); // frame height in 8 pixel blocks
|
||||||
// RtpBuf[4] = 0x80; // RTP version
|
log_v("jpeg_hdr={.tspec:%i,.off:0x%6x,.type:0x2%x,.q:%i,.width:%i.height:%i}", packet->jpeg_hdr.tspec, packet->jpeg_hdr.off, packet->jpeg_hdr.type, packet->jpeg_hdr.q, packet->jpeg_hdr.width, packet->jpeg_hdr.height);
|
||||||
// RtpBuf[5] = 0x1a | (isLastFragment ? 0x80 : 0x00); // JPEG payload (26) and marker bit
|
|
||||||
// RtpBuf[7] = m_SequenceNumber & 0x0FF; // each packet is counted with a sequence counter
|
|
||||||
// RtpBuf[6] = m_SequenceNumber >> 8;
|
|
||||||
// RtpBuf[8] = (m_Timestamp & 0xFF000000) >> 24; // each image gets a timestamp
|
|
||||||
// RtpBuf[9] = (m_Timestamp & 0x00FF0000) >> 16;
|
|
||||||
// RtpBuf[10] = (m_Timestamp & 0x0000FF00) >> 8;
|
|
||||||
// RtpBuf[11] = (m_Timestamp & 0x000000FF);
|
|
||||||
// RtpBuf[12] = 0x13; // 4 byte SSRC (sychronization source identifier)
|
|
||||||
// RtpBuf[13] = 0xf9; // we just an arbitrary number here to keep it simple
|
|
||||||
// RtpBuf[14] = 0x7e;
|
|
||||||
// RtpBuf[15] = 0x67;
|
|
||||||
|
|
||||||
// Prepare the 8 byte payload JPEG header
|
// length so far should be 24 bytes
|
||||||
// RtpBuf[16] = 0x00; // type specific
|
assert(24 == sizeof(jpeg_packet_t));
|
||||||
// RtpBuf[17] = (fragmentOffset & 0x00FF0000) >> 16; // 3 byte fragmentation offset for fragmented images
|
|
||||||
// RtpBuf[18] = (fragmentOffset & 0x0000FF00) >> 8;
|
|
||||||
// RtpBuf[19] = (fragmentOffset & 0x000000FF);
|
|
||||||
|
|
||||||
// /* These sampling factors indicate that the chrominance components of
|
if (include_quantization_tables)
|
||||||
// type 0 video is downsampled horizontally by 2 (often called 4:2:2)
|
{
|
||||||
// while the chrominance components of type 1 video are downsampled both
|
const auto packet_with_quantization = (jpeg_packet_with_quantization_t *)packet;
|
||||||
// horizontally and vertically by 2 (often called 4:2:0). */
|
assert(4 == sizeof(jpeg_hdr_qtable_t));
|
||||||
// RtpBuf[20] = 0x00; // type (fixme might be wrong for camera data) https://tools.ietf.org/html/rfc2435
|
// Only in first packet of the frame
|
||||||
// RtpBuf[21] = q; // quality scale factor was 0x5e
|
packet_with_quantization->jpeg_hdr_qtable.mbz = 0;
|
||||||
// RtpBuf[22] = width_ / 8; // width / 8
|
packet_with_quantization->jpeg_hdr_qtable.precision = 0; // 8 bit precision
|
||||||
// RtpBuf[23] = height_ / 8; // height / 8
|
packet_with_quantization->jpeg_hdr_qtable.length = jpeg_luminance_table_length + jpeg_chrominance_table_length;
|
||||||
|
log_v("jpeg_hdr_qtable={.mbz:%i,.precision:%i,.length:%d}", packet_with_quantization->jpeg_hdr_qtable.mbz, packet_with_quantization->jpeg_hdr_qtable.precision, packet_with_quantization->jpeg_hdr_qtable.length);
|
||||||
int headerLen = 24; // Including jpeg header but not qant table header
|
memcpy(packet_with_quantization->quantization_table_luminance, quantization_table_luminance, jpeg_luminance_table_length);
|
||||||
if (includeQuantTbl)
|
memcpy(packet_with_quantization->quantization_table_chrominance, quantization_table_chrominance, jpeg_chrominance_table_length);
|
||||||
{ // we need a quant header - but only in first packet of the frame
|
// Copy JPG data
|
||||||
// printf("inserting quanttbl\n");
|
memcpy(packet_with_quantization->jpeg_data, *jpg_offset, jpg_bytes);
|
||||||
RtpBuf[24] = 0; // MBZ
|
}
|
||||||
RtpBuf[25] = 0; // 8 bit precision
|
else
|
||||||
RtpBuf[26] = 0; // MSB of lentgh
|
{
|
||||||
|
// Copy JPG data
|
||||||
int numQantBytes = 64; // Two 64 byte tables
|
memcpy(packet->jpeg_data, *jpg_offset, jpg_bytes);
|
||||||
RtpBuf[27] = 2 * numQantBytes; // LSB of length
|
|
||||||
|
|
||||||
headerLen += 4;
|
|
||||||
|
|
||||||
memcpy(RtpBuf + headerLen, quant0tbl, numQantBytes);
|
|
||||||
headerLen += numQantBytes;
|
|
||||||
|
|
||||||
memcpy(RtpBuf + headerLen, quant1tbl, numQantBytes);
|
|
||||||
headerLen += numQantBytes;
|
|
||||||
}
|
}
|
||||||
// printf("Sending timestamp %d, seq %d, fragoff %d, fraglen %d, jpegLen %d\n", m_Timestamp, m_SequenceNumber, fragmentOffset, fragmentLen, jpegLen);
|
|
||||||
|
|
||||||
// append the JPEG scan data to the RTP buffer
|
// Update JPG offset
|
||||||
memcpy(RtpBuf + headerLen, jpeg + fragmentOffset, fragmentLen);
|
*jpg_offset += jpg_bytes;
|
||||||
fragmentOffset += fragmentLen;
|
// Update sequence number
|
||||||
|
sequence_number_++;
|
||||||
|
|
||||||
m_SequenceNumber++; // prepare the packet counter for the next packet
|
return (rtp_over_tcp_hdr_t*)packet;
|
||||||
|
|
||||||
IPADDRESS otherip;
|
|
||||||
IPPORT otherport;
|
|
||||||
socketpeeraddr(m_Client, &otherip, &otherport);
|
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
[platformio]
|
[platformio]
|
||||||
#default_envs = esp32cam_ai_thinker
|
default_envs = esp32cam_ai_thinker
|
||||||
#default_envs = esp32cam_espressif_esp_eye
|
#default_envs = esp32cam_espressif_esp_eye
|
||||||
#default_envs = esp32cam_espressif_esp32s2_cam_board
|
#default_envs = esp32cam_espressif_esp32s2_cam_board
|
||||||
#default_envs = esp32cam_espressif_esp32s2_cam_header
|
#default_envs = esp32cam_espressif_esp32s2_cam_header
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ void test_jpg_decode()
|
|||||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(jpeg_data, mr_jpeg.jpeg_data_start, sizeof(jpeg_data));
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(jpeg_data, mr_jpeg.jpeg_data_start, sizeof(jpeg_data));
|
||||||
|
|
||||||
// Id is not stored
|
// Id is not stored
|
||||||
TEST_ASSERT_EQUAL_INT32(sizeof(jpg_section_dqt::id) + sizeof(jpeg_qtable0), mr_jpeg.quantization_table_0_->data_length());
|
TEST_ASSERT_EQUAL_INT32(sizeof(jpg_section_dqt_t::id) + sizeof(jpeg_qtable0), mr_jpeg.quantization_table_luminance_->data_length());
|
||||||
auto jpg_section_dqt_luminance = reinterpret_cast<const struct jpg_section_dqt *>(mr_jpeg.quantization_table_0_->data);
|
auto jpg_section_dqt_luminance = reinterpret_cast<const jpg_section_dqt_t *>(mr_jpeg.quantization_table_luminance_->data);
|
||||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(jpeg_qtable0, jpg_section_dqt_luminance->data, sizeof(jpeg_qtable0));
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(jpeg_qtable0, jpg_section_dqt_luminance->data, sizeof(jpeg_qtable0));
|
||||||
// Id is not stored
|
// Id is not stored
|
||||||
TEST_ASSERT_EQUAL_INT32(sizeof(jpg_section_dqt::id) + sizeof(jpeg_qtable1), mr_jpeg.quantization_table_1_->data_length());
|
TEST_ASSERT_EQUAL_INT32(sizeof(jpg_section_dqt_t::id) + sizeof(jpeg_qtable1), mr_jpeg.quantization_table_chrominance_->data_length());
|
||||||
auto jpg_section_dqt_chrominance = reinterpret_cast<const struct jpg_section_dqt *>(mr_jpeg.quantization_table_1_->data);
|
auto jpg_section_dqt_chrominance = reinterpret_cast<const jpg_section_dqt_t *>(mr_jpeg.quantization_table_chrominance_->data);
|
||||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(jpeg_qtable1, jpg_section_dqt_chrominance->data, sizeof(jpeg_qtable1));
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(jpeg_qtable1, jpg_section_dqt_chrominance->data, sizeof(jpeg_qtable1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user