20static constexpr uint8_t RTP_PAYLOAD_TYPE_JPEG = 26;
21static constexpr uint32_t RTP_CLOCK_RATE = 90000;
22static constexpr uint32_t SESSION_TIMEOUT_MS = 60000;
23static constexpr char REQUEST_END[] =
"\r\n\r\n";
24static constexpr size_t REQUEST_END_LEN =
sizeof(REQUEST_END) - 1;
25static constexpr uint8_t ZERO_FILL[64]{};
26static constexpr char SDP_BODY[] =
"v=0\r\n"
27 "o=- 0 0 IN IP4 0.0.0.0\r\n"
30 "m=video 0 RTP/AVP 26\r\n"
31 "a=control:trackID=0\r\n";
35StringRef trim(
const StringRef &s) {
36 const char *start = s.c_str();
37 size_t len = s.size();
38 while (
len > 0 && (*start ==
' ' || *start ==
'\t')) {
42 while (
len > 0 && (start[
len - 1] ==
' ' || start[
len - 1] ==
'\t' || start[
len - 1] ==
'\r')) {
48const char *find_request_end(
const char *buf,
size_t len) {
49 for (
size_t i = 0; i + REQUEST_END_LEN <=
len; i++) {
50 if (std::memcmp(buf + i, REQUEST_END, REQUEST_END_LEN) == 0)
57bool contains(
const StringRef &hay,
const StringRef &needle) {
58 if (needle.size() > hay.size())
60 for (
size_t i = 0; i + needle.size() <= hay.size(); i++) {
61 if (std::memcmp(hay.c_str() + i, needle.c_str(), needle.size()) == 0)
74 if (
len < 4 || data[0] != 0xFF || data[1] != 0xD8)
79 if (data[
pos] != 0xFF) {
83 uint8_t marker = data[
pos + 1];
88 if (marker == 0x01 || (marker >= 0xD0 && marker <= 0xD9)) {
96 size_t seg_start =
pos + 4;
97 if (seg_len < 2 || seg_start + (seg_len - 2) >
len)
99 size_t seg_end = seg_start + (seg_len - 2);
101 if (marker == 0xC0 || marker == 0xC1) {
102 if (seg_len < 8 + 3 * 3 || data[seg_start + 5] != 3)
106 const uint8_t sampling = data[seg_start + 7];
107 if (sampling == 0x21) {
109 }
else if (sampling == 0x22) {
114 }
else if (marker == 0xDD) {
116 }
else if (marker == 0xDB) {
117 size_t p = seg_start;
118 while (p < seg_end) {
119 uint8_t precision = data[p] >> 4;
120 uint8_t table_id = data[p] & 0x0F;
121 size_t table_len = precision == 0 ? QTABLE_LEN : 2 * QTABLE_LEN;
122 if (p + 1 + table_len > seg_end)
124 if (precision == 0 && table_id == 0) {
125 out.qtable0 = &data[p + 1];
126 }
else if (precision == 0 && table_id == 1) {
127 out.qtable1 = &data[p + 1];
131 }
else if (marker == 0xDA) {
132 if (
out.width == 0 ||
out.height == 0 ||
out.qtable0 ==
nullptr)
134 if (
out.qtable1 ==
nullptr)
135 out.qtable1 =
out.qtable0;
136 out.scan_data = data + seg_end;
137 out.scan_len =
len - seg_end;
138 if (
out.scan_len >= 2 &&
out.scan_data[
out.scan_len - 2] == 0xFF &&
out.scan_data[
out.scan_len - 1] == 0xD9)
149 this->
socket_ = std::move(socket);
150 this->
socket_->setblocking(
false);
152 this->
socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable,
sizeof(enable));
181 ESP_LOGW(TAG,
"Session %s timed out", this->
session_id_);
192 ESP_LOGV(TAG,
"Session %s stalled, dropping frame", this->
session_id_);
200 while (this->
flush_(now)) {
220 ESP_LOGW(TAG,
"Request too large, closing session");
225 this->request_buf_.size() - this->request_len_);
239 if (errno != EAGAIN && errno != EWOULDBLOCK) {
240 ESP_LOGW(TAG,
"Read failed: errno %d", errno);
260 INTERLEAVED_HEADER_LEN +
encode_uint16(
static_cast<uint8_t
>(buf[2]),
static_cast<uint8_t
>(buf[3]));
266 const size_t len =
end - buf;
277 std::memmove(this->
request_buf_.data(), this->request_buf_.data() +
len, this->request_len_);
281 size_t line_end = request.
find(
"\r\n");
286 int64_t content_length = 0;
289 size_t pos = line_end == std::string::npos ? request.
size() : line_end + 2;
292 size_t header_end =
next == std::string::npos ? request.
size() :
next;
295 size_t colon = header.
find(
':');
296 if (colon != std::string::npos) {
304 content_length =
stol(value);
308 if (
next == std::string::npos)
313 if (content_length < 0 || content_length >
static_cast<int64_t
>(this->
request_buf_.size())) {
321 size_t sp1 = first_line.
find(
' ');
322 size_t sp2 = sp1 == std::string::npos ? std::string::npos : first_line.
find(
' ', sp1 + 1);
323 if (sp1 == std::string::npos || sp2 == std::string::npos) {
330 this->
queue_response_(200,
"OK",
"Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN, GET_PARAMETER\r\n");
332 char addr_buf[socket::SOCKADDR_STR_LEN];
333 this->
socket_->getsockname_to(addr_buf);
334 const bool ipv6 = std::strchr(addr_buf,
':') !=
nullptr;
336 "Content-Base: rtsp://%s%s%s:%u/\r\n"
337 "Content-Type: application/sdp\r\n"
338 "Content-Length: %u\r\n",
339 ipv6 ?
"[" :
"", addr_buf, ipv6 ?
"]" :
"", port,
340 static_cast<unsigned>(
sizeof(SDP_BODY) - 1));
341 this->
iov_[this->
iov_count_++] = {
const_cast<char *
>(SDP_BODY),
sizeof(SDP_BODY) - 1};
360 size_t pos = buf_append_printf(buf,
size, 0,
"RTSP/1.0 %u %s\r\nCSeq: %" PRIu32
"\r\n", code, reason, this->
cseq_);
362 pos = buf_append_printf(buf,
size,
pos,
"Session: %s;timeout=60\r\n", this->
session_id_);
363 if (extra_headers_fmt !=
nullptr &&
pos <
size) {
386 this->
queue_response_(200,
"OK",
"Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n");
415 this->
rtp_timestamp_ +=
static_cast<uint32_t>((
static_cast<uint64_t
>(elapsed_ms) * RTP_CLOCK_RATE) / 1000);
416 this->last_frame_ms_ = now;
420 if (this->
frame_ ==
nullptr)
427 const bool first_fragment = this->
frame_sent_ == 0;
428 const size_t header_len =
429 INTERLEAVED_HEADER_LEN + RTP_HEADER_LEN + JPEG_HEADER_LEN + (first_fragment ? QUANT_HEADER_LEN : 0);
430 const size_t qtables_len = first_fragment ? 2 * QTABLE_LEN : 0;
431 const size_t payload_len =
432 std::min(this->
jpeg_.
scan_len - this->frame_sent_, MAX_PACKET_SIZE - header_len - qtables_len);
434 const size_t rtp_len = header_len - INTERLEAVED_HEADER_LEN + qtables_len + payload_len;
436 auto *buf =
reinterpret_cast<uint8_t *
>(this->
send_buf_.data());
440 buf[
pos++] =
static_cast<uint8_t
>(rtp_len >> 8);
441 buf[
pos++] =
static_cast<uint8_t
>(rtp_len);
444 buf[
pos++] =
static_cast<uint8_t
>((last_fragment ? 0x80 : 0x00) | RTP_PAYLOAD_TYPE_JPEG);
452 buf[
pos++] =
static_cast<uint8_t
>(this->
ssrc_ >> 24);
453 buf[
pos++] =
static_cast<uint8_t
>(this->
ssrc_ >> 16);
454 buf[
pos++] =
static_cast<uint8_t
>(this->
ssrc_ >> 8);
455 buf[
pos++] =
static_cast<uint8_t
>(this->
ssrc_);
468 if (first_fragment) {
471 buf[
pos++] =
static_cast<uint8_t
>(qtables_len >> 8);
472 buf[
pos++] =
static_cast<uint8_t
>(qtables_len);
473 this->
iov_[0] = {buf, header_len};
478 this->
iov_[0] = {buf, header_len};
497 this->iov_count_ = std::max<uint8_t>(this->
iov_pos_, 1);
511 const size_t len = std::min<size_t>(this->
filler_len_,
sizeof(ZERO_FILL));
517 if (
written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
521 ESP_LOGW(TAG,
"Write failed: errno %d", errno);
529 auto remaining =
static_cast<size_t>(
written);
530 while (remaining > 0) {
532 if (remaining < entry.
iov_len) {
StringRef is a reference to a string owned by something else.
constexpr const char * c_str() const
size_type find(const char *s, size_type pos=0) const
Find first occurrence of substring, returns std::string::npos if not found.
constexpr size_type size() const
static constexpr StringRef from_lit(const CharT(&s)[N])
void void stop_playing_()
void drop_frame_()
Releases the frame; the frame-backed rest of the current packet is sent as zeros to keep the framing.
bool flush_(uint32_t now)
Writes what is queued; true once all of it is written, false while backlogged or after a failure.
void service_(uint32_t now, uint16_t port)
uint32_t last_activity_ms_
std::unique_ptr< socket::Socket > socket_
std::array< char, 512 > request_buf_
void loop(uint32_t now, uint16_t port)
void handle_setup_(StringRef transport_header)
bool queue_next_packet_()
std::array< char, 256 > send_buf_
std::shared_ptr< camera::CameraImage > frame_
void read_requests_(uint32_t now, uint16_t port)
std::array< struct iovec, 4 > iov_
uint32_t frame_progress_ms_
void open(std::unique_ptr< socket::Socket > socket, uint32_t now)
void consume_input_(size_t len)
bool process_input_(uint16_t port)
Consumes buffered input up to and including the next request; true once a request was handled.
void handle_request_(StringRef request, uint16_t port)
void queue_response_(uint16_t code, const char *reason, const char *extra_headers_fmt=nullptr,...) __attribute__((format(printf
Queues "RTSP/1.0 <code> <reason>", CSeq, Session once set up, the extra headers and the blank line.
void on_new_frame(const std::shared_ptr< camera::CameraImage > &image, const JpegInfo &jpeg, uint32_t now)
Ignored unless playing with no output pending; packets of the frame being sent point into that frame.
uint16_t sequence_number_
std::vector< uint8_t > & out
ESPHOME_LOG_TAG(TAG, "rtsp_server")
bool parse_jpeg_info(const uint8_t *data, size_t len, JpegInfo &out)
Scans JPEG marker segments (SOF0/1, DQT, SOS) for the dimensions, quantization tables and the start o...
size_t buf_append_str(char *buf, size_t size, size_t pos, const char *str)
Safely append a string to buffer, returning new position (capped at size).
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
bool str_equals_case_insensitive(const std::string &a, const std::string &b)
Compare strings for equality in case-insensitive manner.
size_t size_t const char va_start(args, fmt)
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
char * format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length)
Format byte array as lowercase hex to buffer (base implementation).
long stol(const StringRef &str, size_t *pos=nullptr, int base=10)
The fields of a JPEG frame that an RFC 2435 RTP/JPEG payload needs. Pointers refer into the frame.
const uint8_t * scan_data