diff --git a/lib/micro-jpg/include/jpg.h b/lib/micro-jpg/include/jpg.h index b0a5b95..0e4c6b2 100644 --- a/lib/micro-jpg/include/jpg.h +++ b/lib/micro-jpg/include/jpg.h @@ -7,12 +7,12 @@ class jpg public: bool decode(const uint8_t *jpg, size_t size); - const struct jpg_section *quantization_table_0_; - const struct jpg_section *quantization_table_1_; + const jpg_section_t *quantization_table_luminance_; + const jpg_section_t *quantization_table_chrominance_; const uint8_t *jpeg_data_start; const uint8_t *jpeg_data_end; 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); }; \ No newline at end of file diff --git a/lib/micro-jpg/include/jpg_section.h b/lib/micro-jpg/include/jpg_section.h index 2f10a0e..380e9a7 100644 --- a/lib/micro-jpg/include/jpg_section.h +++ b/lib/micro-jpg/include/jpg_section.h @@ -2,7 +2,7 @@ #include -struct __attribute__((packed)) jpg_section +typedef struct __attribute__((packed)) { 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); uint16_t data_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 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 uint8_t thumbnail_hor = 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 data[64]; -}; \ No newline at end of file +} jpg_section_dqt_t; \ No newline at end of file diff --git a/lib/micro-jpg/src/jpg.cpp b/lib/micro-jpg/src/jpg.cpp index cb58f2d..f2bc309 100644 --- a/lib/micro-jpg/src/jpg.cpp +++ b/lib/micro-jpg/src/jpg.cpp @@ -1,20 +1,20 @@ #include #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) { // flag, len MSB, len LSB - auto section = reinterpret_cast((*ptr)); + auto section = reinterpret_cast((*ptr)); if (section->framing != 0xff) { log_e("Expected framing 0xff but found: 0x%02x", section->framing); 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); 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(); 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; } - 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 @@ -44,25 +44,25 @@ bool jpg::decode(const uint8_t *data, size_t size) auto end = ptr + size; // 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"); return false; } // 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"); return false; } // 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"); // 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"); return false; @@ -77,7 +77,7 @@ bool jpg::decode(const uint8_t *data, size_t size) ptr++; // 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"); return false; diff --git a/lib/micro-jpg/src/jpg_section.cpp b/lib/micro-jpg/src/jpg_section.cpp index 882bbde..ba908bf 100644 --- a/lib/micro-jpg/src/jpg_section.cpp +++ b/lib/micro-jpg/src/jpg_section.cpp @@ -1,22 +1,22 @@ #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; } // 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) { diff --git a/lib/micro-rtsp-server/include/micro_rtsp_server.h b/lib/micro-rtsp-server/include/micro_rtsp_server.h index 3b63053..f2d8d34 100644 --- a/lib/micro-rtsp-server/include/micro_rtsp_server.h +++ b/lib/micro-rtsp-server/include/micro_rtsp_server.h @@ -8,18 +8,19 @@ #include "micro_rtsp_camera.h" #include "micro_rtsp_requests.h" +#include "micro_rtsp_streamer.h" class micro_rtsp_server : WiFiServer { 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(); void begin(unsigned short port = 554); void end(); - unsigned get_frame_interval() { return frame_interval_; } - unsigned set_frame_interval(unsigned value) { return frame_interval_ = value; } + unsigned get_frame_interval() { return frame_interval_; } + unsigned set_frame_interval(unsigned value) { return frame_interval_ = value; } void loop(); @@ -35,12 +36,10 @@ class micro_rtsp_server : WiFiServer }; private: - const micro_rtsp_camera &source_; - - unsigned frame_interval_; - + micro_rtsp_camera &source_; + unsigned frame_interval_; unsigned long next_frame_update_; - unsigned long next_check_client_; + micro_rtsp_streamer streamer_; std::list clients_; }; \ No newline at end of file diff --git a/lib/micro-rtsp-server/include/micro_rtsp_streamer.h b/lib/micro-rtsp-server/include/micro_rtsp_streamer.h index ecb25f3..cc5b8ab 100644 --- a/lib/micro-rtsp-server/include/micro_rtsp_streamer.h +++ b/lib/micro-rtsp-server/include/micro_rtsp_streamer.h @@ -1,15 +1,90 @@ #pragma once -#include +// 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 { public: 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: uint16_t width_, height_; 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; }; \ No newline at end of file diff --git a/lib/micro-rtsp-server/src/micro_rtsp_server.cpp b/lib/micro-rtsp-server/src/micro_rtsp_server.cpp index ec80c4c..b1262a8 100644 --- a/lib/micro-rtsp-server/src/micro_rtsp_server.cpp +++ b/lib/micro-rtsp-server/src/micro_rtsp_server.cpp @@ -1,12 +1,13 @@ #include +#include #include #include // Check client connections every 100 milliseconds #define CHECK_CLIENT_INTERVAL 10 -micro_rtsp_server::micro_rtsp_server(const micro_rtsp_camera &source, unsigned frame_interval /*= 100*/) - : source_(source) +micro_rtsp_server::micro_rtsp_server(micro_rtsp_camera &source, unsigned frame_interval /*= 100*/) + : source_(source), streamer_(source.width(), source.height()) { log_i("starting RTSP server"); frame_interval_ = frame_interval; @@ -51,10 +52,26 @@ void micro_rtsp_server::loop() if (next_frame_update_ < now) { next_frame_update_ = now + frame_interval_; - for (auto client : clients_) + + 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_) + { + ; // client->session->broadcastCurrentFrame(now); + } + free(packet); } } } diff --git a/lib/micro-rtsp-server/src/micro_rtsp_streamer.cpp b/lib/micro-rtsp-server/src/micro_rtsp_streamer.cpp index 9083681..0bd19fd 100644 --- a/lib/micro-rtsp-server/src/micro_rtsp_streamer.cpp +++ b/lib/micro-rtsp-server/src/micro_rtsp_streamer.cpp @@ -1,164 +1,89 @@ #include #include +#include #include "micro_rtsp_streamer.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) { width_ = width; height_ = height; // Random number ssrc_ = esp_random(); - sequenceNumber_ = 0; + sequence_number_ = 0; } -#define MAX_ESP32_MTU 1440 - -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) +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) { - const int MAX_FRAGMENT_SIZE = 1100; // FIXME, pick more carefully - int fragmentLen = MAX_FRAGMENT_SIZE; - auto jpegLen = jpg_end - *jpg_current; - - auto offset = *jpg_current - jpg; - if (fragmentLen + offset > jpegLen) // Shrink last fragment if needed - fragmentLen = jpegLen - offset; - - // bool isLastFragment = (fragmentOffset + fragmentLen) == jpegLen; - - struct rtp_hdr header = { - .version = 2, - .m = 1, // TODO = 1 if last fragfment - .pt = RTP_PAYLOAD_JPG, - .seq = sequenceNumber_, - .ts = timestamp, - .ssrc = ssrc_}; - - struct jpeghdr jpghdr = { - .tspec = 0, // type-specific field - .off = offset, // fragment byte offset - .type = 0, // id of jpeg decoder params - .q = 0x5e, // quantization factor (or table id) - .width = width_ >> 3, // frame width in 8 pixel blocks - .height = height_ >> 3 // frame height in 8 pixel blocks - }; - - uint8_t rtp_buffer[0x800]; - // memset(RtpBuf, 0x00, sizeof(RtpBuf)); - // Prepare the first 4 byte of the packet. This is the Rtp over Rtsp header in case of TCP based transport - rtp_buffer[0] = (uint8_t)'$'; // magic number - rtp_buffer[1] = 0; // number of multiplexed subchannel on RTPS connection - here the RTP channel - // rtp_buffer[2] = (RtpPacketSize & 0xFF00) >> 8; - // rtp_buffer[3] = (RtpPacketSize & 0x00FF); - // Prepare the 12 byte RTP header - // RtpBuf[4] = 0x80; // RTP version - // 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 - // RtpBuf[16] = 0x00; // type specific - // 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 - // type 0 video is downsampled horizontally by 2 (often called 4:2:2) - // while the chrominance components of type 1 video are downsampled both - // horizontally and vertically by 2 (often called 4:2:0). */ - // RtpBuf[20] = 0x00; // type (fixme might be wrong for camera data) https://tools.ietf.org/html/rfc2435 - // RtpBuf[21] = q; // quality scale factor was 0x5e - // RtpBuf[22] = width_ / 8; // width / 8 - // RtpBuf[23] = height_ / 8; // height / 8 - - int headerLen = 24; // Including jpeg header but not qant table header - if (includeQuantTbl) - { // we need a quant header - but only in first packet of the frame - // printf("inserting quanttbl\n"); - RtpBuf[24] = 0; // MBZ - RtpBuf[25] = 0; // 8 bit precision - RtpBuf[26] = 0; // MSB of lentgh - - int numQantBytes = 64; // Two 64 byte tables - RtpBuf[27] = 2 * numQantBytes; // LSB of length - - headerLen += 4; - - memcpy(RtpBuf + headerLen, quant0tbl, numQantBytes); - headerLen += numQantBytes; - - memcpy(RtpBuf + headerLen, quant1tbl, numQantBytes); - headerLen += numQantBytes; + // 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 = (jpeg_packet_t *)calloc(1, packet_size); + + // 4 bytes RTP over TCP header + assert(4 == sizeof(rtp_over_tcp_hdr_t)); + packet->rtp_over_tcp_hdr.magic = '$'; // encapsulation + packet->rtp_over_tcp_hdr.channel = 0; // number of multiplexed sub-channel on RTPS connection - here the RTP channel + packet->rtp_over_tcp_hdr.length = packet_size; + 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); + + // 12 bytes RTP header + assert(12 == sizeof(rtp_hdr_t)); + 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:%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); + + // 8 bytes JPEG payload header + assert(8 == sizeof(jpeg_hdr_t)); + 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)(width_ >> 3); // frame width in 8 pixel blocks + packet->jpeg_hdr.height = (uint8_t)(height_ >> 3); // frame height in 8 pixel blocks + 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); + + // length so far should be 24 bytes + assert(24 == sizeof(jpeg_packet_t)); + + if (include_quantization_tables) + { + const auto packet_with_quantization = (jpeg_packet_with_quantization_t *)packet; + assert(4 == sizeof(jpeg_hdr_qtable_t)); + // Only in first packet of the frame + 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_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); + memcpy(packet_with_quantization->quantization_table_luminance, quantization_table_luminance, jpeg_luminance_table_length); + memcpy(packet_with_quantization->quantization_table_chrominance, quantization_table_chrominance, jpeg_chrominance_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); } - // 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 - memcpy(RtpBuf + headerLen, jpeg + fragmentOffset, fragmentLen); - fragmentOffset += fragmentLen; - m_SequenceNumber++; // prepare the packet counter for the next packet + // Update JPG offset + *jpg_offset += jpg_bytes; + // Update sequence number + sequence_number_++; - IPADDRESS otherip; - IPPORT otherport; - socketpeeraddr(m_Client, &otherip, &otherport); + return (rtp_over_tcp_hdr_t*)packet; } \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index ee327c3..7a4992b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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 diff --git a/test/test_main.cpp b/test/test_main.cpp index 7faadff..3cb72e7 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -71,12 +71,12 @@ void test_jpg_decode() TEST_ASSERT_EQUAL_UINT8_ARRAY(jpeg_data, mr_jpeg.jpeg_data_start, sizeof(jpeg_data)); // Id is not stored - TEST_ASSERT_EQUAL_INT32(sizeof(jpg_section_dqt::id) + sizeof(jpeg_qtable0), mr_jpeg.quantization_table_0_->data_length()); - auto jpg_section_dqt_luminance = reinterpret_cast(mr_jpeg.quantization_table_0_->data); + 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(mr_jpeg.quantization_table_luminance_->data); TEST_ASSERT_EQUAL_UINT8_ARRAY(jpeg_qtable0, jpg_section_dqt_luminance->data, sizeof(jpeg_qtable0)); // Id is not stored - TEST_ASSERT_EQUAL_INT32(sizeof(jpg_section_dqt::id) + sizeof(jpeg_qtable1), mr_jpeg.quantization_table_1_->data_length()); - auto jpg_section_dqt_chrominance = reinterpret_cast(mr_jpeg.quantization_table_1_->data); + 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(mr_jpeg.quantization_table_chrominance_->data); TEST_ASSERT_EQUAL_UINT8_ARRAY(jpeg_qtable1, jpg_section_dqt_chrominance->data, sizeof(jpeg_qtable1)); }