arduino-audio-tools
Loading...
Searching...
No Matches
RTSPSession.h
1/*
2 * Author: Phil Schatzmann
3 *
4 * Based on Micro-RTSP library:
5 * https://github.com/Tomp0801/Micro-RTSP-Audio
6 * https://github.com/geeksville/Micro-RTSP
7 *
8 */
9
10#pragma once
11
12#include <stdio.h>
13
14#include <ctime>
15
16#include "AudioTools/CoreAudio/AudioBasic/Collections/Vector.h"
17#include "RTSPAudioStreamer.h"
18#include "RTSPPlatform.h"
19
21#define RTSP_BUFFER_SIZE 10000
23#define RTSP_PARAM_STRING_MAX 100
25#define MAX_HOSTNAME_LEN 256
27#define RTSP_RESPONSE_BUFFER_SIZE 2251
29#define RTSP_SDP_BUFFER_SIZE 1024
31#define RTSP_URL_BUFFER_SIZE 1024
33#define RTSP_SMALL_BUFFER_SIZE 256
34
35namespace audio_tools {
36
39 RTSP_OPTIONS,
40 RTSP_DESCRIBE,
41 RTSP_SETUP,
42 RTSP_PLAY,
43 RTSP_PAUSE,
44 RTSP_TEARDOWN,
45 RTSP_UNKNOWN
46};
47
76template <typename Platform>
78 public:
97 RtspSession(typename Platform::TcpClientType& aClient,
99 : m_Client(aClient), m_Streamer(&aStreamer) {
100 m_RtspClient = &m_Client;
101 m_RtspSessionID = random(65536); // create a session ID
102 LOGI("RTSP session created");
103 }
104
114 LOGI("RTSP session destructor");
115
116 // Ensure streaming is stopped and resources are released
117 if (m_streaming && m_Streamer) {
118 LOGI("Final cleanup: stopping streamer in destructor");
119 m_Streamer->stop();
120 m_Streamer->releaseUdpTransport();
121 m_streaming = false;
122 }
123
124 // Close the client socket (check connection using client method directly)
125 if (m_RtspClient && m_RtspClient->connected()) {
126 Platform::closeSocket(m_RtspClient);
127 }
128
129 LOGI("RTSP session cleanup completed");
130 }
131
148 bool handleRequests(uint32_t readTimeoutMs) {
149 LOGD("handleRequests");
150 // initlaize buffers and state if not already done
151 init();
152
153 if (!m_sessionOpen) {
154 delay(100); // give some time to close down
155 return false; // Already closed down
156 }
157
158 memset(mRecvBuf.data(), 0x00, RTSP_BUFFER_SIZE);
159 int res = readSocket(m_RtspClient, mRecvBuf.data(), RTSP_BUFFER_SIZE,
160 readTimeoutMs);
161 if (res > 0) {
162 // we filter away everything which seems not to be an RTSP command:
163 // O-ption, D-escribe, S-etup, P-lay, T-eardown
164 if ((mRecvBuf[0] == 'O') || (mRecvBuf[0] == 'D') ||
165 (mRecvBuf[0] == 'S') || (mRecvBuf[0] == 'P') ||
166 (mRecvBuf[0] == 'T')) {
167 RTSP_CMD_TYPES C = handleRtspRequest(mRecvBuf.data(), res);
168 if (!m_sessionOpen) {
169 // Session was aborted (e.g., rejected by callback); end quickly
170 return false;
171 }
172 // TODO this should go in the handling functions
173 if (C == RTSP_PLAY) {
174 m_streaming = true;
175 } else if (C == RTSP_PAUSE) {
176 m_streaming = false;
177 } else if (C == RTSP_TEARDOWN) {
178 m_sessionOpen = false; // Session ended by TEARDOWN
179
180 // Properly cleanup streaming on TEARDOWN command
181 if (m_streaming && m_Streamer) {
182 LOGI("Stopping streamer due to TEARDOWN");
183 m_Streamer->stop();
184 m_Streamer->releaseUdpTransport();
185 m_streaming = false;
186 }
187 }
188 }
189 return true;
190 } else if (res == 0) {
191 LOGW("client closed socket, exiting");
192 m_sessionOpen = false; // Session ended by client disconnect
193
194 // CRITICAL: Properly cleanup streaming when client disconnects
195 if (m_streaming && m_Streamer) {
196 LOGI("Stopping streamer due to client disconnect");
197 m_Streamer->stop();
198 m_Streamer->releaseUdpTransport();
199 m_streaming = false;
200 }
201
202 return false; // Return false to indicate session should end
203 } else {
204 // Timeout on read
205 // printf("RTSP read timeout\n");
206 return false;
207 }
208 }
209
210 bool isSessionOpen() { return m_sessionOpen; }
211
212 bool isStreaming() { return m_streaming; }
213
225 void setOnSessionPath(bool (*cb)(const char* path, void* ref), void* ref = nullptr) {
226 m_onSessionPath = cb;
227 m_onSessionPathRef = ref;
228 }
229
233 void closeSession() { m_sessionOpen = false; }
234
235
236
237 protected:
238 const char* STD_URL_PRE_SUFFIX = "trackID";
239
240 // global session state parameters
241 int m_RtspSessionID;
242 typename Platform::TcpClientType m_Client;
243 typename Platform::TcpClientType* m_RtspClient =
244 nullptr; // RTSP socket of session
245 int m_StreamID = -1; // number of simulated stream of that session
246 uint16_t m_ClientRTPPort; // client port for UDP based RTP transport
247 uint16_t m_ClientRTCPPort; // client port for UDP based RTCP transport
249 nullptr; // the UDP streamer of that session
250
251 // parameters of the last received RTSP request
252 RTSP_CMD_TYPES m_RtspCmdType; // command type (if any) of the current request
253 audio_tools::Vector<char> m_URLPreSuffix; // stream name pre suffix
254 audio_tools::Vector<char> m_URLSuffix; // stream name suffix
255 audio_tools::Vector<char> m_CSeq; // RTSP command sequence number
256 audio_tools::Vector<char> m_URLHostPort; // host:port part of the URL
257 audio_tools::Vector<char> m_URLPath; // full RTSP path (starting with '/')
258 unsigned m_ContentLength; // SDP string size
259 uint16_t m_RtpClientPort =
260 0; // RTP receiver port on client (in host byte order!)
261 uint16_t m_RtcpClientPort =
262 0; // RTCP receiver port on client (in host byte order!)
263 // Transport parsing (TCP interleaved)
264 bool m_TransportIsTcp = false;
265 int m_InterleavedRtp = -1;
266 int m_InterleavedRtcp = -1;
268 m_Response; // Note: we assume single threaded, this large buf we
269 // keep off of the tiny stack
275 audio_tools::Vector<char> mCurRequest;
277 bool m_is_init = false;
278 bool m_streaming = false;
279 volatile bool m_sessionOpen = true;
280 bool m_pathNotified = false;
281 bool (*m_onSessionPath)(const char* path, void* ref) = nullptr;
282 void* m_onSessionPathRef = nullptr;
283
291 void init() {
292 if (m_is_init) return;
293 LOGD("init");
294
295 // Reset session state for clean initialization
296 m_streaming = false;
297 m_sessionOpen = true;
298
299 // initialize buffers if not already done
300 if (mRecvBuf.size() == 0) {
301 mRecvBuf.resize(RTSP_BUFFER_SIZE);
302 }
303 if (mCurRequest.size() == 0) {
304 mCurRequest.resize(RTSP_BUFFER_SIZE);
305 }
306 if (m_URLPreSuffix.size() == 0) {
307 m_URLPreSuffix.resize(RTSP_PARAM_STRING_MAX);
308 }
309 if (m_URLSuffix.size() == 0) {
310 m_URLSuffix.resize(RTSP_PARAM_STRING_MAX);
311 }
312 if (m_CSeq.size() == 0) {
313 m_CSeq.resize(RTSP_PARAM_STRING_MAX);
314 }
315 if (m_URLHostPort.size() == 0) {
316 m_URLHostPort.resize(MAX_HOSTNAME_LEN);
317 }
318 if (m_URLPath.size() == 0) {
319 m_URLPath.resize(RTSP_URL_BUFFER_SIZE);
320 }
321 if (m_Response.size() == 0) {
322 m_Response.resize(RTSP_RESPONSE_BUFFER_SIZE);
323 }
324 if (m_SDPBuf.size() == 0) {
325 m_SDPBuf.resize(RTSP_SDP_BUFFER_SIZE);
326 }
327 if (m_URLBuf.size() == 0) {
328 m_URLBuf.resize(RTSP_URL_BUFFER_SIZE);
329 }
330 if (m_Buf1.size() == 0) {
331 m_Buf1.resize(RTSP_SMALL_BUFFER_SIZE);
332 }
333 if (m_Buf2.size() == 0) {
334 m_Buf2.resize(RTSP_SMALL_BUFFER_SIZE);
335 }
336 if (m_CmdName.size() == 0) {
337 m_CmdName.resize(RTSP_PARAM_STRING_MAX);
338 }
339
340 m_RtspCmdType = RTSP_UNKNOWN;
341 memset(m_URLPreSuffix.data(), 0x00, m_URLPreSuffix.size());
342 memset(m_URLSuffix.data(), 0x00, m_URLSuffix.size());
343 memset(m_CSeq.data(), 0x00, m_CSeq.size());
344 memset(m_URLHostPort.data(), 0x00, m_URLHostPort.size());
345 if (m_URLPath.size() > 0) memset(m_URLPath.data(), 0x00, m_URLPath.size());
346 m_ContentLength = 0;
347 m_TransportIsTcp = false;
348 m_InterleavedRtp = -1;
349 m_InterleavedRtcp = -1;
350 m_is_init = true;
351 m_pathNotified = false;
352 }
353
361 RTSP_CMD_TYPES handleRtspRequest(char const* aRequest,
362 unsigned aRequestSize) {
363 if (parseRtspRequest(aRequest, aRequestSize)) {
364 switch (m_RtspCmdType) {
365 case RTSP_OPTIONS: {
367 break;
368 }
369 case RTSP_DESCRIBE: {
371 break;
372 }
373 case RTSP_SETUP: {
375 break;
376 }
377 case RTSP_PLAY: {
379 break;
380 }
381 case RTSP_PAUSE: {
383 break;
384 }
385 case RTSP_TEARDOWN: {
387 break;
388 }
389 default: {
390 }
391 }
392 }
393 return m_RtspCmdType;
394 }
395
403 bool parseRtspRequest(char const* aRequest, unsigned aRequestSize) {
404 LOGI("aRequest: ------------------------\n%s\n-------------------------",
405 aRequest);
406
407 const unsigned CurRequestSize = aRequestSize;
408 memcpy(mCurRequest.data(), aRequest, aRequestSize);
409
410 // 1) Ports and transport
411 parseClientPorts(mCurRequest.data());
412 parseTransportHeader(mCurRequest.data());
413
414 // 2) Command + URL host/parts
415 unsigned idxAfterCmd = 0;
416 if (!parseCommandName(mCurRequest.data(), CurRequestSize, idxAfterCmd))
417 return false;
418 determineCommandType();
419 parseUrlHostPortAndSuffix(mCurRequest.data(), CurRequestSize, idxAfterCmd);
420 if (!m_sessionOpen) {
421 // Aborted by callback during URL parse; don't proceed further
422 return false;
423 }
424
425 // 3) CSeq and Content-Length
426 if (!parseCSeq(mCurRequest.data(), CurRequestSize, idxAfterCmd))
427 return false;
428 parseContentLength(mCurRequest.data(), CurRequestSize, idxAfterCmd);
429
430 // 4) Client preference toggle (User-Agent / URL)
431 detectClientHeaderPreference(mCurRequest.data());
432
433 return true;
434 }
435
436 // ---- Parsing helpers ----
437 void parseClientPorts(char* req) {
438 char* ClientPortPtr = strstr(req, "client_port");
439 if (!ClientPortPtr) return;
440 char* lineEnd = strstr(ClientPortPtr, "\r\n");
441 if (!lineEnd) return;
442 char* CP = m_Response.data();
443 memset(CP, 0, m_Response.size());
444 char saved = lineEnd[0];
445 lineEnd[0] = '\0';
446 strcpy(CP, ClientPortPtr);
447 char* eq = strstr(CP, "=");
448 if (eq) {
449 ++eq;
450 strcpy(CP, eq);
451 char* dash = strstr(CP, "-");
452 if (dash) {
453 dash[0] = '\0';
454 m_ClientRTPPort = atoi(CP);
455 m_ClientRTCPPort = m_ClientRTPPort + 1;
456 }
457 }
458 lineEnd[0] = saved;
459 }
460
461 void parseTransportHeader(char* req) {
462 char* TransportPtr = strstr(req, "Transport:");
463 if (!TransportPtr) return;
464 char* lineEnd = strstr(TransportPtr, "\r\n");
465 if (!lineEnd) return;
466 char* CP = m_Response.data();
467 memset(CP, 0, m_Response.size());
468 char saved = lineEnd[0];
469 lineEnd[0] = '\0';
470 strncpy(CP, TransportPtr, m_Response.size() - 1);
471 CP[m_Response.size() - 1] = '\0';
472 if (strstr(CP, "RTP/AVP/TCP") || strstr(CP, "/TCP")) m_TransportIsTcp = true;
473 char* inter = strstr(CP, "interleaved=");
474 if (inter) {
475 inter += strlen("interleaved=");
476 int a = -1, b = -1;
477 if (sscanf(inter, "%d-%d", &a, &b) == 2) {
478 m_InterleavedRtp = a;
479 m_InterleavedRtcp = b;
480 } else if (sscanf(inter, "%d,%d", &a, &b) == 2) {
481 m_InterleavedRtp = a;
482 m_InterleavedRtcp = b;
483 } else if (sscanf(inter, "%d", &a) == 1) {
484 m_InterleavedRtp = a;
485 m_InterleavedRtcp = a + 1;
486 }
487 }
488 lineEnd[0] = saved;
489 }
490
491 bool parseCommandName(char* req, unsigned reqSize, unsigned& outIdx) {
492 bool ok = false;
493 unsigned i;
494 for (i = 0; i < m_CmdName.size() - 1 && i < reqSize; ++i) {
495 char c = req[i];
496 if (c == ' ' || c == '\t') {
497 ok = true;
498 break;
499 }
500 m_CmdName[i] = c;
501 }
502 m_CmdName[i] = '\0';
503 if (!ok) {
504 LOGE("failed to parse RTSP");
505 return false;
506 }
507 LOGI("RTSP received %s", m_CmdName.data());
508 outIdx = i;
509 return true;
510 }
511
512 void determineCommandType() {
513 if (strstr(m_CmdName.data(), "OPTIONS"))
514 m_RtspCmdType = RTSP_OPTIONS;
515 else if (strstr(m_CmdName.data(), "DESCRIBE"))
516 m_RtspCmdType = RTSP_DESCRIBE;
517 else if (strstr(m_CmdName.data(), "SETUP"))
518 m_RtspCmdType = RTSP_SETUP;
519 else if (strstr(m_CmdName.data(), "PLAY"))
520 m_RtspCmdType = RTSP_PLAY;
521 else if (strstr(m_CmdName.data(), "PAUSE"))
522 m_RtspCmdType = RTSP_PAUSE;
523 else if (strstr(m_CmdName.data(), "TEARDOWN"))
524 m_RtspCmdType = RTSP_TEARDOWN;
525 else
526 LOGE("Error: Unsupported Command received (%s)!", m_CmdName.data());
527 }
528
529 void parseUrlHostPortAndSuffix(char* req, unsigned reqSize, unsigned& i) {
530 unsigned j = i + 1;
531 while (j < reqSize && (req[j] == ' ' || req[j] == '\t')) ++j;
532 for (; (int)j < (int)(reqSize - 8); ++j) {
533 if ((req[j] == 'r' || req[j] == 'R') && (req[j + 1] == 't' || req[j + 1] == 'T') &&
534 (req[j + 2] == 's' || req[j + 2] == 'S') && (req[j + 3] == 'p' || req[j + 3] == 'P') &&
535 req[j + 4] == ':' && req[j + 5] == '/') {
536 j += 6;
537 if (req[j] == '/') {
538 ++j;
539 unsigned uidx = 0;
540 while (j < reqSize && req[j] != '/' && req[j] != ' ' && uidx < m_URLHostPort.size() - 1) {
541 m_URLHostPort[uidx++] = req[j++];
542 }
543 } else {
544 --j;
545 }
546 i = j;
547 break;
548 }
549 }
550 LOGD("m_URLHostPort: %s", m_URLHostPort.data());
551
552 // Extract full RTSP path starting at current index i up to next space
553 if (i < reqSize && req[i] == '/') {
554 unsigned p = 0;
555 unsigned k = i;
556 while (k < reqSize && req[k] != ' ' && p < m_URLPath.size() - 1) {
557 m_URLPath[p++] = req[k++];
558 }
559 m_URLPath[p] = '\0';
560 LOGD("m_URLPath: %s", m_URLPath.data());
561 if (!m_pathNotified && m_onSessionPath) {
562 bool ok = m_onSessionPath(m_URLPath.data(), m_onSessionPathRef);
563 m_pathNotified = true;
564 if (!ok) {
565 LOGW("Session rejected by onSessionPath callback");
566 m_sessionOpen = false;
567 // Early exit: abort further parsing of this request
568 }
569 }
570 }
571
572 bool ok = false;
573 for (unsigned k = i + 1; (int)k < (int)(reqSize - 5); ++k) {
574 if (req[k] == 'R' && req[k + 1] == 'T' && req[k + 2] == 'S' && req[k + 3] == 'P' && req[k + 4] == '/') {
575 while (--k >= i && req[k] == ' ') {}
576 unsigned k1 = k;
577 while (k1 > i && req[k1] != '=') --k1;
578 if (k - k1 + 1 <= m_URLSuffix.size()) {
579 unsigned n = 0, k2 = k1 + 1;
580 while (k2 <= k) m_URLSuffix[n++] = req[k2++];
581 m_URLSuffix[n] = '\0';
582 if (k1 - i <= m_URLPreSuffix.size()) ok = true;
583 n = 0;
584 k2 = i + 1;
585 while (k2 <= k1 - 1) m_URLPreSuffix[n++] = req[k2++];
586 m_URLPreSuffix[n] = '\0';
587 i = k + 7;
588 }
589 break;
590 }
591 }
592 LOGD("m_URLSuffix: %s", m_URLSuffix.data());
593 LOGD("m_URLPreSuffix: %s", m_URLPreSuffix.data());
594 LOGD("URL Suffix parse succeeded: %i", ok);
595 }
596
597 bool parseCSeq(char* req, unsigned reqSize, unsigned startIdx) {
598 bool ok = false;
599 for (unsigned j = startIdx; (int)j < (int)(reqSize - 5); ++j) {
600 if (req[j] == 'C' && req[j + 1] == 'S' && req[j + 2] == 'e' && req[j + 3] == 'q' && req[j + 4] == ':') {
601 j += 5;
602 while (j < reqSize && (req[j] == ' ' || req[j] == '\t')) ++j;
603 unsigned n;
604 for (n = 0; n < m_CSeq.size() - 1 && j < reqSize; ++n, ++j) {
605 char c = req[j];
606 if (c == '\r' || c == '\n') {
607 ok = true;
608 break;
609 }
610 m_CSeq[n] = c;
611 }
612 m_CSeq[n] = '\0';
613 break;
614 }
615 }
616 LOGD("Look for CSeq success: %i", ok);
617 return ok;
618 }
619
620 void parseContentLength(char* req, unsigned reqSize, unsigned startIdx) {
621 for (unsigned j = startIdx; (int)j < (int)(reqSize - 15); ++j) {
622 if (req[j] == 'C' && req[j + 1] == 'o' && req[j + 2] == 'n' && req[j + 3] == 't' &&
623 req[j + 4] == 'e' && req[j + 5] == 'n' && req[j + 6] == 't' && req[j + 7] == '-' &&
624 (req[j + 8] == 'L' || req[j + 8] == 'l') && req[j + 9] == 'e' && req[j + 10] == 'n' &&
625 req[j + 11] == 'g' && req[j + 12] == 't' && req[j + 13] == 'h' && req[j + 14] == ':') {
626 j += 15;
627 while (j < reqSize && (req[j] == ' ' || req[j] == '\t')) ++j;
628 unsigned num;
629 if (sscanf(&req[j], "%u", &num) == 1) m_ContentLength = num;
630 }
631 }
632 }
633
634 void detectClientHeaderPreference(char* req) {
635 char* ua = strstr(req, "User-Agent:");
636 bool want_rfc2250 = false;
637 if (ua) {
638 if (strcasestr(ua, "ffmpeg") || strcasestr(ua, "ffplay") || strcasestr(ua, "libavformat") ||
639 strcasestr(ua, "Lavf")) {
640 want_rfc2250 = true;
641 }
642 if (strcasestr(ua, "vlc")) want_rfc2250 = false;
643 }
644 char* qm = strchr(req, '?');
645 if (qm) {
646 if (strstr(qm, "mpa_hdr=1")) want_rfc2250 = true;
647 if (strstr(qm, "mpa_hdr=0")) want_rfc2250 = false;
648 }
649 if (m_Streamer && m_Streamer->getAudioSource()) {
650 RTSPFormat& fmt = m_Streamer->getAudioSource()->getFormat();
651 fmt.setUseRfc2250Header(want_rfc2250);
652 }
653 }
654
659 snprintf(m_Response.data(), m_Response.size(),
660 "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
661 "Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n\r\n",
662 m_CSeq.data());
663
664 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
665 }
666
671 // check whether we know a stream with the URL which is requested
672 m_StreamID = -1; // invalid URL
673 // find Stream ID
674 if (strcmp(m_URLPreSuffix.data(), STD_URL_PRE_SUFFIX) == 0) {
675 char* end;
676 m_StreamID = strtol(m_URLSuffix.data(), &end, 10);
677 if (*end != '\0') m_StreamID = -1;
678 }
679
680 // simulate DESCRIBE server response
681 char* ColonPtr;
682 strncpy(m_Buf1.data(), m_URLHostPort.data(), 256);
683 ColonPtr = strstr(m_Buf1.data(), ":");
684 if (ColonPtr != nullptr) ColonPtr[0] = 0x00;
685
686 snprintf(
687 m_SDPBuf.data(), m_SDPBuf.size(),
688 "v=0\r\n" // SDP Version
689 "o=- %d 0 IN IP4 %s\r\n"
690 "%s"
691 "a=control:%s=0",
692 rand() & 0xFF, m_Buf1.data(),
693 m_Streamer->getAudioSource()->getFormat().format(m_Buf2.data(), 256),
694 STD_URL_PRE_SUFFIX);
695
696 snprintf(m_URLBuf.data(), m_URLBuf.size(), "rtsp://%s",
697 m_URLHostPort.data());
698
699 snprintf(m_Response.data(), m_Response.size(),
700 "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
701 "%s\r\n"
702 "Content-Base: %s/\r\n"
703 "Content-Type: application/sdp\r\n"
704 "Content-Length: %d\r\n\r\n"
705 "%s",
706 m_CSeq.data(), dateHeader(), m_URLBuf.data(),
707 (int)strlen(m_SDPBuf.data()), m_SDPBuf.data());
708
709 // LOGI("handleRtspDescribe: %s", (const char*)m_Response.data());
710 Serial.println("------------------------------");
711 Serial.println((const char*)m_Response.data());
712 Serial.println("------------------------------");
713 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
714 }
715
720 // Build response depending on requested transport
721 if (m_TransportIsTcp) {
722 // Initialize TCP interleaved transport on same RTSP TCP socket
723 int ch0 = (m_InterleavedRtp >= 0) ? m_InterleavedRtp : 0;
724 int ch1 = (m_InterleavedRtcp >= 0) ? m_InterleavedRtcp : (ch0 + 1);
725 m_Streamer->initTcpInterleavedTransport(m_RtspClient, ch0, ch1);
726
727 // Reply with interleaved channels
728 snprintf(m_Buf1.data(), m_Buf1.size(),
729 "RTP/AVP/TCP;unicast;interleaved=%d-%d", ch0, ch1);
730 } else {
731 // init RTSP Session transport type (UDP) and ports for UDP transport
732 initTransport(m_ClientRTPPort, m_ClientRTCPPort);
733 // UDP Transport response with client/server ports and ssrc
734 snprintf(m_Buf1.data(), m_Buf1.size(),
735 "RTP/AVP;unicast;client_port=%i-%i;server_port=%i-%i;ssrc=%08X",
736 m_ClientRTPPort, m_ClientRTCPPort,
737 m_Streamer->getRtpServerPort(), m_Streamer->getRtcpServerPort(),
738 (unsigned)m_Streamer->currentSsrc());
739 }
740 snprintf(m_Response.data(), m_Response.size(),
741 "RTSP/1.0 200 OK\r\n"
742 "CSeq: %s\r\n"
743 "%s\r\n"
744 "Session: %i\r\n"
745 "Transport: %s\r\n"
746 "\r\n",
747 m_CSeq.data(), dateHeader(), m_RtspSessionID, m_Buf1.data());
748
749 Serial.println("------------------------------");
750 Serial.println((char*)m_Response.data());
751 Serial.println("------------------------------");
752
753 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
754 }
755
760 // Build RTP-Info to help clients (e.g., VLC) synchronize
761 // URL base
762 snprintf(m_URLBuf.data(), m_URLBuf.size(), "rtsp://%s/%s=0",
763 m_URLHostPort.data(), STD_URL_PRE_SUFFIX);
764
765 // Current seq/timestamp from streamer (values used for next packet)
766 uint16_t seq = m_Streamer ? m_Streamer->currentSeq() : 0;
767 uint32_t rtptime = m_Streamer ? m_Streamer->currentRtpTimestamp() : 0;
768
769 // PLAY response with Range, Session and RTP-Info
770 snprintf(m_Response.data(), m_Response.size(),
771 "RTSP/1.0 200 OK\r\n"
772 "CSeq: %s\r\n"
773 "Range: npt=0.000-\r\n"
774 "Session: %i\r\n"
775 "RTP-Info: url=%s;seq=%u;rtptime=%u\r\n\r\n",
776 m_CSeq.data(), m_RtspSessionID, m_URLBuf.data(), (unsigned)seq,
777 (unsigned)rtptime);
778
779 Serial.println("------------------------------");
780 Serial.println((char*)m_Response.data());
781 Serial.println("------------------------------");
782
783 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
784
785 m_Streamer->start();
786 }
787
793 if (m_streaming && m_Streamer) {
794 m_Streamer->stop();
795 }
796 snprintf(m_Response.data(), m_Response.size(),
797 "RTSP/1.0 200 OK\r\n"
798 "CSeq: %s\r\n"
799 "Session: %i\r\n\r\n",
800 m_CSeq.data(), m_RtspSessionID);
801 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
802 }
803
808 m_Streamer->stop();
809
810 // simulate SETUP server response
811 snprintf(m_Response.data(), m_Response.size(),
812 "RTSP/1.0 200 OK\r\n"
813 "CSeq: %s\r\n\r\n",
814 m_CSeq.data());
815
816 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
817
818 m_sessionOpen = false;
819 }
820
825 int getStreamID() { return m_StreamID; }
826
832 void initTransport(u_short aRtpPort, u_short aRtcpPort) {
833 m_RtpClientPort = aRtpPort;
834 m_RtcpClientPort = aRtcpPort;
835
836 IPAddress clientIP;
837 uint16_t clientPort;
838 Platform::getSocketPeerAddr(m_RtspClient, &clientIP, &clientPort);
839
840 LOGI("SETUP peer resolved: %s:%u (RTP client_port=%u)",
841 clientIP.toString().c_str(), (unsigned)clientPort,
842 (unsigned)m_RtpClientPort);
843
844 m_Streamer->initUdpTransport(clientIP, m_RtpClientPort);
845 }
846
847 typename Platform::TcpClientType*& getClient() { return m_RtspClient; }
848
849 uint16_t getRtpClientPort() { return m_RtpClientPort; }
850
859 inline int readSocket(typename Platform::TcpClientType* sock, char* buf,
860 size_t buflen, int timeoutmsec) {
861 return Platform::readSocket(sock, buf, buflen, timeoutmsec);
862 }
863
871 inline ssize_t sendSocket(typename Platform::TcpClientType* sock,
872 const void* buf, size_t len) {
873 return Platform::sendSocket(sock, buf, len);
874 }
875
880 char const* dateHeader() {
881 static char buf[200];
882 time_t tt = time(NULL);
883 strftime(buf, sizeof(buf), "Date: %a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
884 return buf;
885 }
886};
887
888} // namespace audio_tools
virtual RTSPFormat & getFormat()=0
Get the audio format configuration.
RTSPAudioStreamerBase - Core RTP Audio Streaming Engine.
Definition RTSPAudioStreamer.h:50
bool initUdpTransport(IPAddress aClientIP, uint16_t aClientPort)
Initialize UDP transport for RTP streaming.
Definition RTSPAudioStreamer.h:182
IAudioSource * getAudioSource()
Get the configured audio source.
Definition RTSPAudioStreamer.h:401
virtual void start()
Start audio source (base implementation)
Definition RTSPAudioStreamer.h:344
uint32_t currentRtpTimestamp() const
Get current RTP timestamp value that will be used in the next packet.
Definition RTSPAudioStreamer.h:427
uint32_t currentSsrc() const
Get current SSRC used in RTP header.
Definition RTSPAudioStreamer.h:432
uint16_t currentSeq() const
Get current RTP sequence number that will be used in the next packet.
Definition RTSPAudioStreamer.h:421
u_short getRtcpServerPort()
Get the RTCP server port number.
Definition RTSPAudioStreamer.h:394
u_short getRtpServerPort()
Get the RTP server port number.
Definition RTSPAudioStreamer.h:387
void initTcpInterleavedTransport(typename Platform::TcpClientType *tcpSock, int rtpChannel, int rtcpChannel)
Initialize TCP interleaved transport for RTP over RTSP.
Definition RTSPAudioStreamer.h:230
virtual void stop()
Stop audio source (base implementation)
Definition RTSPAudioStreamer.h:372
virtual void setUseRfc2250Header(bool)
Optional: Configure RFC2250 header usage (default: no-op)
Definition RTSPFormat.h:95
RTSP Session Handler - Individual Client Protocol Management.
Definition RTSPSession.h:77
void init()
Definition RTSPSession.h:291
int readSocket(typename Platform::TcpClientType *sock, char *buf, size_t buflen, int timeoutmsec)
Inline helper to read from socket.
Definition RTSPSession.h:859
void closeSession()
Mark session as closed (for server timeout/teardown)
Definition RTSPSession.h:233
void initTransport(u_short aRtpPort, u_short aRtcpPort)
Definition RTSPSession.h:832
ssize_t sendSocket(typename Platform::TcpClientType *sock, const void *buf, size_t len)
Inline helper to send data over socket.
Definition RTSPSession.h:871
void handleRtspSetup()
Definition RTSPSession.h:719
void handleRtspDescribe()
Definition RTSPSession.h:670
bool parseRtspRequest(char const *aRequest, unsigned aRequestSize)
Definition RTSPSession.h:403
RtspSession(typename Platform::TcpClientType &aClient, RTSPAudioStreamerBase< Platform > &aStreamer)
Construct RTSP session for a connected client.
Definition RTSPSession.h:97
int getStreamID()
Definition RTSPSession.h:825
void handleRtspTeardown()
Definition RTSPSession.h:807
RTSP_CMD_TYPES handleRtspRequest(char const *aRequest, unsigned aRequestSize)
Definition RTSPSession.h:361
~RtspSession()
Destructor - cleanup session resources.
Definition RTSPSession.h:113
void handleRtspPlay()
Definition RTSPSession.h:759
void handleRtspOption()
Definition RTSPSession.h:658
bool handleRequests(uint32_t readTimeoutMs)
Process incoming RTSP requests from the client.
Definition RTSPSession.h:148
char const * dateHeader()
Definition RTSPSession.h:880
void setOnSessionPath(bool(*cb)(const char *path, void *ref), void *ref=nullptr)
Set a callback to receive the RTSP URL path that opened the session. The callback is invoked once,...
Definition RTSPSession.h:225
void handleRtspPause()
Definition RTSPSession.h:792
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
RTSP_CMD_TYPES
Supported RTSP command types.
Definition RTSPSession.h:38