ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
rtsp_session.cpp
Go to the documentation of this file.
1#include "rtsp_session.h"
2
3#ifdef USE_CAMERA
4
5#include <algorithm>
6#include <cerrno>
7#include <cinttypes>
8#include <cstdarg>
9#include <cstdio>
10#include <cstring>
11#include <utility>
12
14#include "esphome/core/log.h"
15
16namespace esphome::rtsp_server {
17
18ESPHOME_LOG_TAG(TAG, "rtsp_server");
19
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"
28 "s=ESPHome RTSP\r\n"
29 "t=0 0\r\n"
30 "m=video 0 RTP/AVP 26\r\n"
31 "a=control:trackID=0\r\n";
32
33namespace {
34
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')) {
39 start++;
40 len--;
41 }
42 while (len > 0 && (start[len - 1] == ' ' || start[len - 1] == '\t' || start[len - 1] == '\r')) {
43 len--;
44 }
45 return {start, len};
46}
47
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)
51 return buf + i;
52 }
53 return nullptr;
54}
55
57bool contains(const StringRef &hay, const StringRef &needle) {
58 if (needle.size() > hay.size())
59 return false;
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)
62 return true;
63 }
64 return false;
65}
66
67} // namespace
68
73bool parse_jpeg_info(const uint8_t *data, size_t len, JpegInfo &out) {
74 if (len < 4 || data[0] != 0xFF || data[1] != 0xD8)
75 return false; // no SOI
76
77 size_t pos = 2;
78 while (pos + 2 <= len) {
79 if (data[pos] != 0xFF) {
80 pos++; // resync on a stray non-marker byte
81 continue;
82 }
83 uint8_t marker = data[pos + 1];
84 if (marker == 0xFF) {
85 pos++; // fill byte before the real marker
86 continue;
87 }
88 if (marker == 0x01 || (marker >= 0xD0 && marker <= 0xD9)) {
89 pos += 2; // standalone marker, no length field (TEM, RSTn, SOI, EOI)
90 continue;
91 }
92 if (pos + 4 > len)
93 break;
94
95 uint16_t seg_len = encode_uint16(data[pos + 2], data[pos + 3]);
96 size_t seg_start = pos + 4;
97 if (seg_len < 2 || seg_start + (seg_len - 2) > len)
98 return false;
99 size_t seg_end = seg_start + (seg_len - 2);
100
101 if (marker == 0xC0 || marker == 0xC1) { // SOF0 / SOF1
102 if (seg_len < 8 + 3 * 3 || data[seg_start + 5] != 3)
103 return false; // not three components
104 out.height = encode_uint16(data[seg_start + 1], data[seg_start + 2]);
105 out.width = encode_uint16(data[seg_start + 3], data[seg_start + 4]);
106 const uint8_t sampling = data[seg_start + 7]; // Y component H/V sampling factors
107 if (sampling == 0x21) {
108 out.type = 0; // 4:2:2
109 } else if (sampling == 0x22) {
110 out.type = 1; // 4:2:0
111 } else {
112 return false;
113 }
114 } else if (marker == 0xDD) { // DRI
115 return false;
116 } else if (marker == 0xDB) { // DQT, one or more tables back to back
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)
123 break;
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];
128 }
129 p += 1 + table_len;
130 }
131 } else if (marker == 0xDA) { // SOS, scan data follows
132 if (out.width == 0 || out.height == 0 || out.qtable0 == nullptr)
133 return false;
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)
139 out.scan_len -= 2; // the RFC 2435 payload is the entropy-coded data only
140 return true;
141 }
142
143 pos = seg_end;
144 }
145 return false;
146}
147
148void RTSPSession::open(std::unique_ptr<socket::Socket> socket, uint32_t now) {
149 this->socket_ = std::move(socket);
150 this->socket_->setblocking(false);
151 int enable = 1;
152 this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable));
153 this->should_close_ = false;
154 this->close_after_send_ = false;
155 this->read_pending_ = false;
156 this->request_len_ = 0;
157 this->skip_len_ = 0;
158 this->filler_len_ = 0;
159 this->cseq_ = 0;
160 this->last_frame_ms_ = 0;
161 this->last_activity_ms_ = now;
162 this->ssrc_ = random_uint32();
163 this->sequence_number_ = static_cast<uint16_t>(random_uint32());
166}
167
169 this->frame_.reset();
170 this->socket_.reset();
171 this->state_ = State::INIT;
172 this->iov_pos_ = 0;
173 this->iov_count_ = 0;
174 this->filler_len_ = 0;
175}
176
177void RTSPSession::loop(uint32_t now, uint16_t port) {
178 if (this->socket_ == nullptr)
179 return;
180 if (now - this->last_activity_ms_ > SESSION_TIMEOUT_MS) {
181 ESP_LOGW(TAG, "Session %s timed out", this->session_id_);
182 this->should_close_ = true;
183 } else {
184 this->service_(now, port);
185 }
186 if (this->should_close_)
187 this->close_();
188}
189
190void RTSPSession::service_(uint32_t now, uint16_t port) {
191 if (this->frame_ != nullptr && now - this->frame_progress_ms_ > FRAME_STALL_MS) {
192 ESP_LOGV(TAG, "Session %s stalled, dropping frame", this->session_id_);
193 this->drop_frame_();
194 }
195 if (!this->flush_(now))
196 return;
197 if (!this->close_after_send_)
198 this->read_requests_(now, port);
199 // Drain the current frame until done or the socket backs up; one packet per loop would cap the frame rate.
200 while (this->flush_(now)) {
201 if (this->close_after_send_) {
202 this->should_close_ = true;
203 return;
204 }
205 if (!this->queue_next_packet_())
206 return;
207 }
208}
209
210void RTSPSession::read_requests_(uint32_t now, uint16_t port) {
211 // A pipelined request may already be buffered; it is handled once the previous response is written.
212 if (this->process_input_(port))
213 return;
214 if (!this->read_pending_ && !this->socket_->ready())
215 return;
216 this->read_pending_ = false;
217
218 while (true) {
219 if (this->request_len_ >= this->request_buf_.size()) {
220 ESP_LOGW(TAG, "Request too large, closing session");
221 this->should_close_ = true;
222 return;
223 }
224 ssize_t n = this->socket_->read(this->request_buf_.data() + this->request_len_,
225 this->request_buf_.size() - this->request_len_);
226 if (n > 0) {
227 this->request_len_ += static_cast<uint16_t>(n);
228 this->last_activity_ms_ = now;
229 if (this->process_input_(port)) {
230 this->read_pending_ = true; // stopped before EAGAIN, so ready() may not report what is left
231 return;
232 }
233 continue;
234 }
235 if (n == 0) {
236 this->should_close_ = true;
237 return;
238 }
239 if (errno != EAGAIN && errno != EWOULDBLOCK) {
240 ESP_LOGW(TAG, "Read failed: errno %d", errno);
241 this->should_close_ = true;
242 }
243 return;
244 }
245}
246
247bool RTSPSession::process_input_(uint16_t port) {
248 while (this->request_len_ > 0) {
249 if (this->skip_len_ > 0) {
250 const size_t len = std::min<size_t>(this->skip_len_, this->request_len_);
251 this->consume_input_(len);
252 this->skip_len_ -= len;
253 continue;
254 }
255 char *buf = this->request_buf_.data();
256 if (buf[0] == '$') { // interleaved packet: '$', channel, 16-bit length, payload
257 if (this->request_len_ < INTERLEAVED_HEADER_LEN)
258 return false;
259 this->skip_len_ =
260 INTERLEAVED_HEADER_LEN + encode_uint16(static_cast<uint8_t>(buf[2]), static_cast<uint8_t>(buf[3]));
261 continue;
262 }
263 const char *end = find_request_end(buf, this->request_len_);
264 if (end == nullptr)
265 return false; // wait for the rest of the request
266 const size_t len = end - buf;
267 buf[len] = '\0'; // over the terminator's first CR
268 this->handle_request_(StringRef(buf, len), port);
269 this->consume_input_(len + REQUEST_END_LEN);
270 return true;
271 }
272 return false;
273}
274
276 this->request_len_ -= len;
277 std::memmove(this->request_buf_.data(), this->request_buf_.data() + len, this->request_len_);
278}
279
280void RTSPSession::handle_request_(StringRef request, uint16_t port) {
281 size_t line_end = request.find("\r\n");
282 StringRef first_line = line_end == std::string::npos ? request : StringRef(request.c_str(), line_end);
283
284 this->cseq_ = 0;
285 StringRef transport;
286 int64_t content_length = 0;
287
288 // Headers first: even a 400 reply has to echo the request's CSeq
289 size_t pos = line_end == std::string::npos ? request.size() : line_end + 2;
290 while (pos < request.size()) {
291 size_t next = request.find("\r\n", pos);
292 size_t header_end = next == std::string::npos ? request.size() : next;
293 StringRef header(request.c_str() + pos, header_end - pos);
294
295 size_t colon = header.find(':');
296 if (colon != std::string::npos) {
297 StringRef key = trim(StringRef(header.c_str(), colon));
298 StringRef value = trim(StringRef(header.c_str() + colon + 1, header.size() - colon - 1));
300 this->cseq_ = static_cast<uint32_t>(stol(value));
301 } else if (str_equals_case_insensitive(key, StringRef::from_lit("Transport"))) {
302 transport = value;
303 } else if (str_equals_case_insensitive(key, StringRef::from_lit("Content-Length"))) {
304 content_length = stol(value);
305 }
306 }
307
308 if (next == std::string::npos)
309 break;
310 pos = next + 2;
311 }
312
313 if (content_length < 0 || content_length > static_cast<int64_t>(this->request_buf_.size())) {
314 this->queue_response_(400, "Bad Request");
315 this->close_after_send_ = true;
316 return;
317 }
318 // The body is ignored; it is discarded as it arrives, like an interleaved packet.
319 this->skip_len_ = static_cast<uint32_t>(content_length);
320
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) {
324 this->queue_response_(400, "Bad Request");
325 return;
326 }
327 StringRef method(first_line.c_str(), sp1);
328
329 if (str_equals_case_insensitive(method, StringRef::from_lit("OPTIONS"))) {
330 this->queue_response_(200, "OK", "Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN, GET_PARAMETER\r\n");
331 } else if (str_equals_case_insensitive(method, StringRef::from_lit("DESCRIBE"))) {
332 char addr_buf[socket::SOCKADDR_STR_LEN];
333 this->socket_->getsockname_to(addr_buf);
334 const bool ipv6 = std::strchr(addr_buf, ':') != nullptr;
335 this->queue_response_(200, "OK",
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};
342 } else if (str_equals_case_insensitive(method, StringRef::from_lit("SETUP"))) {
343 this->handle_setup_(transport);
344 } else if (str_equals_case_insensitive(method, StringRef::from_lit("PLAY"))) {
345 this->handle_play_();
346 } else if (str_equals_case_insensitive(method, StringRef::from_lit("TEARDOWN"))) {
347 this->stop_playing_();
348 this->queue_response_(200, "OK");
349 this->close_after_send_ = true;
350 } else if (str_equals_case_insensitive(method, StringRef::from_lit("GET_PARAMETER"))) {
351 this->queue_response_(200, "OK");
352 } else {
353 this->queue_response_(501, "Not Implemented");
354 }
355}
356
357void RTSPSession::queue_response_(uint16_t code, const char *reason, const char *extra_headers_fmt, ...) {
358 char *buf = this->send_buf_.data();
359 const size_t size = this->send_buf_.size();
360 size_t pos = buf_append_printf(buf, size, 0, "RTSP/1.0 %u %s\r\nCSeq: %" PRIu32 "\r\n", code, reason, this->cseq_);
361 if (this->state_ != State::INIT)
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) {
364 va_list args;
365 va_start(args, extra_headers_fmt);
366 int written = vsnprintf(buf + pos, size - pos, extra_headers_fmt, args);
367 va_end(args);
368 if (written > 0)
369 pos = std::min(pos + static_cast<size_t>(written), size);
370 }
371 pos = buf_append_str(buf, size, pos, "\r\n");
372 this->iov_[0] = {buf, std::min(pos, size - 1)};
373 this->iov_pos_ = 0;
374 this->iov_count_ = 1;
375 this->sending_packet_ = false;
376}
377
378void RTSPSession::handle_setup_(StringRef transport_header) {
379 if (!contains(transport_header, StringRef::from_lit("RTP/AVP/TCP"))) {
380 this->queue_response_(461, "Unsupported Transport");
381 return;
382 }
383 // A SETUP while streaming stops playback, ending in READY like a first SETUP.
384 this->stop_playing_();
385 // The single video track always uses interleaved channels 0 (RTP) and 1 (RTCP, unused).
386 this->queue_response_(200, "OK", "Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n");
387}
388
390 if (this->state_ == State::INIT) {
391 this->queue_response_(455, "Method Not Valid In This State");
392 return;
393 }
394 this->state_ = State::PLAYING;
395 this->frame_.reset(); // a repeated PLAY may arrive mid-frame
396 this->queue_response_(200, "OK", "Range: npt=0.000-\r\n");
397}
398
400 this->state_ = State::READY;
401 this->frame_.reset();
402}
403
404void RTSPSession::on_new_frame(const std::shared_ptr<camera::CameraImage> &image, const JpegInfo &jpeg, uint32_t now) {
405 // Pending output means a frame is in flight or a stalled one is still being padded out.
406 if (this->state_ != State::PLAYING || this->frame_ != nullptr || this->iov_pos_ < this->iov_count_ ||
407 this->filler_len_ > 0)
408 return;
409 this->frame_ = image;
410 this->jpeg_ = jpeg;
411 this->frame_sent_ = 0;
412 this->frame_progress_ms_ = now;
413
414 uint32_t elapsed_ms = this->last_frame_ms_ == 0 ? 0 : now - this->last_frame_ms_;
415 this->rtp_timestamp_ += static_cast<uint32_t>((static_cast<uint64_t>(elapsed_ms) * RTP_CLOCK_RATE) / 1000);
416 this->last_frame_ms_ = now;
417}
418
420 if (this->frame_ == nullptr)
421 return false;
422 if (this->frame_sent_ >= this->jpeg_.scan_len) {
423 this->frame_.reset(); // its last packet is written
424 return false;
425 }
426
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);
433 const bool last_fragment = this->frame_sent_ + payload_len >= this->jpeg_.scan_len;
434 const size_t rtp_len = header_len - INTERLEAVED_HEADER_LEN + qtables_len + payload_len;
435
436 auto *buf = reinterpret_cast<uint8_t *>(this->send_buf_.data());
437 size_t pos = 0;
438 buf[pos++] = '$';
439 buf[pos++] = 0; // interleaved channel 0
440 buf[pos++] = static_cast<uint8_t>(rtp_len >> 8);
441 buf[pos++] = static_cast<uint8_t>(rtp_len);
442
443 buf[pos++] = 0x80; // V=2, P=0, X=0, CC=0
444 buf[pos++] = static_cast<uint8_t>((last_fragment ? 0x80 : 0x00) | RTP_PAYLOAD_TYPE_JPEG);
445 buf[pos++] = static_cast<uint8_t>(this->sequence_number_ >> 8);
446 buf[pos++] = static_cast<uint8_t>(this->sequence_number_);
447 this->sequence_number_++;
448 buf[pos++] = static_cast<uint8_t>(this->rtp_timestamp_ >> 24);
449 buf[pos++] = static_cast<uint8_t>(this->rtp_timestamp_ >> 16);
450 buf[pos++] = static_cast<uint8_t>(this->rtp_timestamp_ >> 8);
451 buf[pos++] = static_cast<uint8_t>(this->rtp_timestamp_);
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_);
456
457 // RFC 2435 main JPEG header
458 buf[pos++] = 0; // type-specific
459 buf[pos++] = static_cast<uint8_t>(this->frame_sent_ >> 16);
460 buf[pos++] = static_cast<uint8_t>(this->frame_sent_ >> 8);
461 buf[pos++] = static_cast<uint8_t>(this->frame_sent_);
462 buf[pos++] = this->jpeg_.type;
463 buf[pos++] = 255; // Q >= 128: the quantization tables follow in the first packet
464 buf[pos++] = static_cast<uint8_t>(this->jpeg_.width / 8);
465 buf[pos++] = static_cast<uint8_t>(this->jpeg_.height / 8);
466
467 uint8_t count = 0;
468 if (first_fragment) {
469 buf[pos++] = 0; // MBZ
470 buf[pos++] = 0; // 8-bit precision
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};
474 this->iov_[1] = {const_cast<uint8_t *>(this->jpeg_.qtable0), QTABLE_LEN};
475 this->iov_[2] = {const_cast<uint8_t *>(this->jpeg_.qtable1), QTABLE_LEN};
476 count = 3;
477 } else {
478 this->iov_[0] = {buf, header_len};
479 count = 1;
480 }
481 this->iov_[count++] = {const_cast<uint8_t *>(this->jpeg_.scan_data + this->frame_sent_), payload_len};
482 this->iov_pos_ = 0;
483 this->iov_count_ = count;
484 this->sending_packet_ = true;
485 this->frame_sent_ += payload_len;
486 return true;
487}
488
490 this->frame_.reset();
491 if (!this->sending_packet_ || this->iov_pos_ >= this->iov_count_)
492 return;
493 // Even an unwritten packet is kept, so output stays pending and on_new_frame() takes nothing until the
494 // socket drains. Entries after the header point into the frame.
495 for (uint8_t i = std::max<uint8_t>(this->iov_pos_, 1); i < this->iov_count_; i++)
496 this->filler_len_ += this->iov_[i].iov_len;
497 this->iov_count_ = std::max<uint8_t>(this->iov_pos_, 1);
498 this->sending_packet_ = false;
499}
500
502 if (this->should_close_)
503 return false;
504 while (true) {
505 if (this->iov_pos_ >= this->iov_count_) {
506 if (this->filler_len_ == 0)
507 return true;
508 this->iov_pos_ = 0;
509 this->iov_count_ = 0;
510 while (this->filler_len_ > 0 && this->iov_count_ < this->iov_.size()) {
511 const size_t len = std::min<size_t>(this->filler_len_, sizeof(ZERO_FILL));
512 this->iov_[this->iov_count_++] = {const_cast<uint8_t *>(ZERO_FILL), len};
513 this->filler_len_ -= len;
514 }
515 }
516 ssize_t written = this->socket_->writev(&this->iov_[this->iov_pos_], this->iov_count_ - this->iov_pos_);
517 if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
518 return false; // retry next loop
519 if (written <= 0) {
520 if (written < 0) {
521 ESP_LOGW(TAG, "Write failed: errno %d", errno);
522 }
523 this->should_close_ = true;
524 return false;
525 }
526 this->last_activity_ms_ = now;
527 this->frame_progress_ms_ = now;
528 // Step over what was written; a partial write leaves the rest of the current entry pending.
529 auto remaining = static_cast<size_t>(written);
530 while (remaining > 0) {
531 struct iovec &entry = this->iov_[this->iov_pos_];
532 if (remaining < entry.iov_len) {
533 entry.iov_base = static_cast<uint8_t *>(entry.iov_base) + remaining;
534 entry.iov_len -= remaining;
535 break;
536 }
537 remaining -= entry.iov_len;
538 this->iov_pos_++;
539 }
540 }
541}
542
543} // namespace esphome::rtsp_server
544
545#endif
StringRef is a reference to a string owned by something else.
Definition string_ref.h:31
constexpr const char * c_str() const
Definition string_ref.h:78
size_type find(const char *s, size_type pos=0) const
Find first occurrence of substring, returns std::string::npos if not found.
Definition string_ref.h:122
constexpr size_type size() const
Definition string_ref.h:79
static constexpr StringRef from_lit(const CharT(&s)[N])
Definition string_ref.h:55
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)
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)
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_
void open(std::unique_ptr< socket::Socket > socket, uint32_t now)
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.
const char * key
NameCopy * next
std::vector< uint8_t > & out
Definition gif.cpp:49
unsigned count
Definition gif.cpp:51
__int64 ssize_t
Definition httplib.h:178
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).
Definition helpers.h:1304
va_end(args)
const void size_t len
Definition hal.h:68
uint16_t size
Definition helpers.cpp:34
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition helpers.cpp:12
size_t size_t pos
Definition helpers.h:1255
bool str_equals_case_insensitive(const std::string &a, const std::string &b)
Compare strings for equality in case-insensitive manner.
Definition helpers.cpp:210
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.
Definition helpers.h:998
int written
Definition helpers.h:1262
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).
Definition helpers.cpp:428
long stol(const StringRef &str, size_t *pos=nullptr, int base=10)
Definition string_ref.h:327
static void uint32_t
The fields of a JPEG frame that an RFC 2435 RTP/JPEG payload needs. Pointers refer into the frame.
void * iov_base
Definition headers.h:113
size_t iov_len
Definition headers.h:114
uint8_t end[39]
Definition sun_gtil2.cpp:17