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
230 protected:
231 const char* STD_URL_PRE_SUFFIX = "trackID";
232
233 // global session state parameters
234 int m_RtspSessionID;
235 typename Platform::TcpClientType m_Client;
236 typename Platform::TcpClientType* m_RtspClient =
237 nullptr; // RTSP socket of session
238 int m_StreamID = -1; // number of simulated stream of that session
239 uint16_t m_ClientRTPPort; // client port for UDP based RTP transport
240 uint16_t m_ClientRTCPPort; // client port for UDP based RTCP transport
242 nullptr; // the UDP streamer of that session
243
244 // parameters of the last received RTSP request
245 RTSP_CMD_TYPES m_RtspCmdType; // command type (if any) of the current request
246 audio_tools::Vector<char> m_URLPreSuffix; // stream name pre suffix
247 audio_tools::Vector<char> m_URLSuffix; // stream name suffix
248 audio_tools::Vector<char> m_CSeq; // RTSP command sequence number
249 audio_tools::Vector<char> m_URLHostPort; // host:port part of the URL
250 audio_tools::Vector<char> m_URLPath; // full RTSP path (starting with '/')
251 unsigned m_ContentLength; // SDP string size
252 uint16_t m_RtpClientPort =
253 0; // RTP receiver port on client (in host byte order!)
254 uint16_t m_RtcpClientPort =
255 0; // RTCP receiver port on client (in host byte order!)
256 // Transport parsing (TCP interleaved)
257 bool m_TransportIsTcp = false;
258 int m_InterleavedRtp = -1;
259 int m_InterleavedRtcp = -1;
261 m_Response; // Note: we assume single threaded, this large buf we
262 // keep off of the tiny stack
268 audio_tools::Vector<char> mCurRequest;
270 bool m_is_init = false;
271 bool m_streaming = false;
272 volatile bool m_sessionOpen = true;
273 bool m_pathNotified = false;
274 bool (*m_onSessionPath)(const char* path, void* ref) = nullptr;
275 void* m_onSessionPathRef = nullptr;
276
284 void init() {
285 if (m_is_init) return;
286 LOGD("init");
287
288 // Reset session state for clean initialization
289 m_streaming = false;
290 m_sessionOpen = true;
291
292 // initialize buffers if not already done
293 if (mRecvBuf.size() == 0) {
294 mRecvBuf.resize(RTSP_BUFFER_SIZE);
295 }
296 if (mCurRequest.size() == 0) {
297 mCurRequest.resize(RTSP_BUFFER_SIZE);
298 }
299 if (m_URLPreSuffix.size() == 0) {
300 m_URLPreSuffix.resize(RTSP_PARAM_STRING_MAX);
301 }
302 if (m_URLSuffix.size() == 0) {
303 m_URLSuffix.resize(RTSP_PARAM_STRING_MAX);
304 }
305 if (m_CSeq.size() == 0) {
306 m_CSeq.resize(RTSP_PARAM_STRING_MAX);
307 }
308 if (m_URLHostPort.size() == 0) {
309 m_URLHostPort.resize(MAX_HOSTNAME_LEN);
310 }
311 if (m_URLPath.size() == 0) {
312 m_URLPath.resize(RTSP_URL_BUFFER_SIZE);
313 }
314 if (m_Response.size() == 0) {
315 m_Response.resize(RTSP_RESPONSE_BUFFER_SIZE);
316 }
317 if (m_SDPBuf.size() == 0) {
318 m_SDPBuf.resize(RTSP_SDP_BUFFER_SIZE);
319 }
320 if (m_URLBuf.size() == 0) {
321 m_URLBuf.resize(RTSP_URL_BUFFER_SIZE);
322 }
323 if (m_Buf1.size() == 0) {
324 m_Buf1.resize(RTSP_SMALL_BUFFER_SIZE);
325 }
326 if (m_Buf2.size() == 0) {
327 m_Buf2.resize(RTSP_SMALL_BUFFER_SIZE);
328 }
329 if (m_CmdName.size() == 0) {
330 m_CmdName.resize(RTSP_PARAM_STRING_MAX);
331 }
332
333 m_RtspCmdType = RTSP_UNKNOWN;
334 memset(m_URLPreSuffix.data(), 0x00, m_URLPreSuffix.size());
335 memset(m_URLSuffix.data(), 0x00, m_URLSuffix.size());
336 memset(m_CSeq.data(), 0x00, m_CSeq.size());
337 memset(m_URLHostPort.data(), 0x00, m_URLHostPort.size());
338 if (m_URLPath.size() > 0) memset(m_URLPath.data(), 0x00, m_URLPath.size());
339 m_ContentLength = 0;
340 m_TransportIsTcp = false;
341 m_InterleavedRtp = -1;
342 m_InterleavedRtcp = -1;
343 m_is_init = true;
344 m_pathNotified = false;
345 }
346
354 RTSP_CMD_TYPES handleRtspRequest(char const* aRequest,
355 unsigned aRequestSize) {
356 if (parseRtspRequest(aRequest, aRequestSize)) {
357 switch (m_RtspCmdType) {
358 case RTSP_OPTIONS: {
360 break;
361 }
362 case RTSP_DESCRIBE: {
364 break;
365 }
366 case RTSP_SETUP: {
368 break;
369 }
370 case RTSP_PLAY: {
372 break;
373 }
374 case RTSP_PAUSE: {
376 break;
377 }
378 case RTSP_TEARDOWN: {
380 break;
381 }
382 default: {
383 }
384 }
385 }
386 return m_RtspCmdType;
387 }
388
396 bool parseRtspRequest(char const* aRequest, unsigned aRequestSize) {
397 LOGI("aRequest: ------------------------\n%s\n-------------------------",
398 aRequest);
399
400 const unsigned CurRequestSize = aRequestSize;
401 memcpy(mCurRequest.data(), aRequest, aRequestSize);
402
403 // 1) Ports and transport
404 parseClientPorts(mCurRequest.data());
405 parseTransportHeader(mCurRequest.data());
406
407 // 2) Command + URL host/parts
408 unsigned idxAfterCmd = 0;
409 if (!parseCommandName(mCurRequest.data(), CurRequestSize, idxAfterCmd))
410 return false;
411 determineCommandType();
412 parseUrlHostPortAndSuffix(mCurRequest.data(), CurRequestSize, idxAfterCmd);
413 if (!m_sessionOpen) {
414 // Aborted by callback during URL parse; don't proceed further
415 return false;
416 }
417
418 // 3) CSeq and Content-Length
419 if (!parseCSeq(mCurRequest.data(), CurRequestSize, idxAfterCmd))
420 return false;
421 parseContentLength(mCurRequest.data(), CurRequestSize, idxAfterCmd);
422
423 // 4) Client preference toggle (User-Agent / URL)
424 detectClientHeaderPreference(mCurRequest.data());
425
426 return true;
427 }
428
429 // ---- Parsing helpers ----
430 void parseClientPorts(char* req) {
431 char* ClientPortPtr = strstr(req, "client_port");
432 if (!ClientPortPtr) return;
433 char* lineEnd = strstr(ClientPortPtr, "\r\n");
434 if (!lineEnd) return;
435 char* CP = m_Response.data();
436 memset(CP, 0, m_Response.size());
437 char saved = lineEnd[0];
438 lineEnd[0] = '\0';
439 strcpy(CP, ClientPortPtr);
440 char* eq = strstr(CP, "=");
441 if (eq) {
442 ++eq;
443 strcpy(CP, eq);
444 char* dash = strstr(CP, "-");
445 if (dash) {
446 dash[0] = '\0';
447 m_ClientRTPPort = atoi(CP);
448 m_ClientRTCPPort = m_ClientRTPPort + 1;
449 }
450 }
451 lineEnd[0] = saved;
452 }
453
454 void parseTransportHeader(char* req) {
455 char* TransportPtr = strstr(req, "Transport:");
456 if (!TransportPtr) return;
457 char* lineEnd = strstr(TransportPtr, "\r\n");
458 if (!lineEnd) return;
459 char* CP = m_Response.data();
460 memset(CP, 0, m_Response.size());
461 char saved = lineEnd[0];
462 lineEnd[0] = '\0';
463 strncpy(CP, TransportPtr, m_Response.size() - 1);
464 CP[m_Response.size() - 1] = '\0';
465 if (strstr(CP, "RTP/AVP/TCP") || strstr(CP, "/TCP")) m_TransportIsTcp = true;
466 char* inter = strstr(CP, "interleaved=");
467 if (inter) {
468 inter += strlen("interleaved=");
469 int a = -1, b = -1;
470 if (sscanf(inter, "%d-%d", &a, &b) == 2) {
471 m_InterleavedRtp = a;
472 m_InterleavedRtcp = b;
473 } else if (sscanf(inter, "%d,%d", &a, &b) == 2) {
474 m_InterleavedRtp = a;
475 m_InterleavedRtcp = b;
476 } else if (sscanf(inter, "%d", &a) == 1) {
477 m_InterleavedRtp = a;
478 m_InterleavedRtcp = a + 1;
479 }
480 }
481 lineEnd[0] = saved;
482 }
483
484 bool parseCommandName(char* req, unsigned reqSize, unsigned& outIdx) {
485 bool ok = false;
486 unsigned i;
487 for (i = 0; i < m_CmdName.size() - 1 && i < reqSize; ++i) {
488 char c = req[i];
489 if (c == ' ' || c == '\t') {
490 ok = true;
491 break;
492 }
493 m_CmdName[i] = c;
494 }
495 m_CmdName[i] = '\0';
496 if (!ok) {
497 LOGE("failed to parse RTSP");
498 return false;
499 }
500 LOGI("RTSP received %s", m_CmdName.data());
501 outIdx = i;
502 return true;
503 }
504
505 void determineCommandType() {
506 if (strstr(m_CmdName.data(), "OPTIONS"))
507 m_RtspCmdType = RTSP_OPTIONS;
508 else if (strstr(m_CmdName.data(), "DESCRIBE"))
509 m_RtspCmdType = RTSP_DESCRIBE;
510 else if (strstr(m_CmdName.data(), "SETUP"))
511 m_RtspCmdType = RTSP_SETUP;
512 else if (strstr(m_CmdName.data(), "PLAY"))
513 m_RtspCmdType = RTSP_PLAY;
514 else if (strstr(m_CmdName.data(), "PAUSE"))
515 m_RtspCmdType = RTSP_PAUSE;
516 else if (strstr(m_CmdName.data(), "TEARDOWN"))
517 m_RtspCmdType = RTSP_TEARDOWN;
518 else
519 LOGE("Error: Unsupported Command received (%s)!", m_CmdName.data());
520 }
521
522 void parseUrlHostPortAndSuffix(char* req, unsigned reqSize, unsigned& i) {
523 unsigned j = i + 1;
524 while (j < reqSize && (req[j] == ' ' || req[j] == '\t')) ++j;
525 for (; (int)j < (int)(reqSize - 8); ++j) {
526 if ((req[j] == 'r' || req[j] == 'R') && (req[j + 1] == 't' || req[j + 1] == 'T') &&
527 (req[j + 2] == 's' || req[j + 2] == 'S') && (req[j + 3] == 'p' || req[j + 3] == 'P') &&
528 req[j + 4] == ':' && req[j + 5] == '/') {
529 j += 6;
530 if (req[j] == '/') {
531 ++j;
532 unsigned uidx = 0;
533 while (j < reqSize && req[j] != '/' && req[j] != ' ' && uidx < m_URLHostPort.size() - 1) {
534 m_URLHostPort[uidx++] = req[j++];
535 }
536 } else {
537 --j;
538 }
539 i = j;
540 break;
541 }
542 }
543 LOGD("m_URLHostPort: %s", m_URLHostPort.data());
544
545 // Extract full RTSP path starting at current index i up to next space
546 if (i < reqSize && req[i] == '/') {
547 unsigned p = 0;
548 unsigned k = i;
549 while (k < reqSize && req[k] != ' ' && p < m_URLPath.size() - 1) {
550 m_URLPath[p++] = req[k++];
551 }
552 m_URLPath[p] = '\0';
553 LOGD("m_URLPath: %s", m_URLPath.data());
554 if (!m_pathNotified && m_onSessionPath) {
555 bool ok = m_onSessionPath(m_URLPath.data(), m_onSessionPathRef);
556 m_pathNotified = true;
557 if (!ok) {
558 LOGW("Session rejected by onSessionPath callback");
559 m_sessionOpen = false;
560 // Early exit: abort further parsing of this request
561 }
562 }
563 }
564
565 bool ok = false;
566 for (unsigned k = i + 1; (int)k < (int)(reqSize - 5); ++k) {
567 if (req[k] == 'R' && req[k + 1] == 'T' && req[k + 2] == 'S' && req[k + 3] == 'P' && req[k + 4] == '/') {
568 while (--k >= i && req[k] == ' ') {}
569 unsigned k1 = k;
570 while (k1 > i && req[k1] != '=') --k1;
571 if (k - k1 + 1 <= m_URLSuffix.size()) {
572 unsigned n = 0, k2 = k1 + 1;
573 while (k2 <= k) m_URLSuffix[n++] = req[k2++];
574 m_URLSuffix[n] = '\0';
575 if (k1 - i <= m_URLPreSuffix.size()) ok = true;
576 n = 0;
577 k2 = i + 1;
578 while (k2 <= k1 - 1) m_URLPreSuffix[n++] = req[k2++];
579 m_URLPreSuffix[n] = '\0';
580 i = k + 7;
581 }
582 break;
583 }
584 }
585 LOGD("m_URLSuffix: %s", m_URLSuffix.data());
586 LOGD("m_URLPreSuffix: %s", m_URLPreSuffix.data());
587 LOGD("URL Suffix parse succeeded: %i", ok);
588 }
589
590 bool parseCSeq(char* req, unsigned reqSize, unsigned startIdx) {
591 bool ok = false;
592 for (unsigned j = startIdx; (int)j < (int)(reqSize - 5); ++j) {
593 if (req[j] == 'C' && req[j + 1] == 'S' && req[j + 2] == 'e' && req[j + 3] == 'q' && req[j + 4] == ':') {
594 j += 5;
595 while (j < reqSize && (req[j] == ' ' || req[j] == '\t')) ++j;
596 unsigned n;
597 for (n = 0; n < m_CSeq.size() - 1 && j < reqSize; ++n, ++j) {
598 char c = req[j];
599 if (c == '\r' || c == '\n') {
600 ok = true;
601 break;
602 }
603 m_CSeq[n] = c;
604 }
605 m_CSeq[n] = '\0';
606 break;
607 }
608 }
609 LOGD("Look for CSeq success: %i", ok);
610 return ok;
611 }
612
613 void parseContentLength(char* req, unsigned reqSize, unsigned startIdx) {
614 for (unsigned j = startIdx; (int)j < (int)(reqSize - 15); ++j) {
615 if (req[j] == 'C' && req[j + 1] == 'o' && req[j + 2] == 'n' && req[j + 3] == 't' &&
616 req[j + 4] == 'e' && req[j + 5] == 'n' && req[j + 6] == 't' && req[j + 7] == '-' &&
617 (req[j + 8] == 'L' || req[j + 8] == 'l') && req[j + 9] == 'e' && req[j + 10] == 'n' &&
618 req[j + 11] == 'g' && req[j + 12] == 't' && req[j + 13] == 'h' && req[j + 14] == ':') {
619 j += 15;
620 while (j < reqSize && (req[j] == ' ' || req[j] == '\t')) ++j;
621 unsigned num;
622 if (sscanf(&req[j], "%u", &num) == 1) m_ContentLength = num;
623 }
624 }
625 }
626
627 void detectClientHeaderPreference(char* req) {
628 char* ua = strstr(req, "User-Agent:");
629 bool want_rfc2250 = false;
630 if (ua) {
631 if (strcasestr(ua, "ffmpeg") || strcasestr(ua, "ffplay") || strcasestr(ua, "libavformat") ||
632 strcasestr(ua, "Lavf")) {
633 want_rfc2250 = true;
634 }
635 if (strcasestr(ua, "vlc")) want_rfc2250 = false;
636 }
637 char* qm = strchr(req, '?');
638 if (qm) {
639 if (strstr(qm, "mpa_hdr=1")) want_rfc2250 = true;
640 if (strstr(qm, "mpa_hdr=0")) want_rfc2250 = false;
641 }
642 if (m_Streamer && m_Streamer->getAudioSource()) {
643 RTSPFormat& fmt = m_Streamer->getAudioSource()->getFormat();
644 fmt.setUseRfc2250Header(want_rfc2250);
645 }
646 }
647
652 snprintf(m_Response.data(), m_Response.size(),
653 "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
654 "Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n\r\n",
655 m_CSeq.data());
656
657 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
658 }
659
664 // check whether we know a stream with the URL which is requested
665 m_StreamID = -1; // invalid URL
666 // find Stream ID
667 if (strcmp(m_URLPreSuffix.data(), STD_URL_PRE_SUFFIX) == 0) {
668 char* end;
669 m_StreamID = strtol(m_URLSuffix.data(), &end, 10);
670 if (*end != '\0') m_StreamID = -1;
671 }
672
673 // simulate DESCRIBE server response
674 char* ColonPtr;
675 strncpy(m_Buf1.data(), m_URLHostPort.data(), 256);
676 ColonPtr = strstr(m_Buf1.data(), ":");
677 if (ColonPtr != nullptr) ColonPtr[0] = 0x00;
678
679 snprintf(
680 m_SDPBuf.data(), m_SDPBuf.size(),
681 "v=0\r\n" // SDP Version
682 "o=- %d 0 IN IP4 %s\r\n"
683 "%s"
684 "a=control:%s=0",
685 rand() & 0xFF, m_Buf1.data(),
686 m_Streamer->getAudioSource()->getFormat().format(m_Buf2.data(), 256),
687 STD_URL_PRE_SUFFIX);
688
689 snprintf(m_URLBuf.data(), m_URLBuf.size(), "rtsp://%s",
690 m_URLHostPort.data());
691
692 snprintf(m_Response.data(), m_Response.size(),
693 "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
694 "%s\r\n"
695 "Content-Base: %s/\r\n"
696 "Content-Type: application/sdp\r\n"
697 "Content-Length: %d\r\n\r\n"
698 "%s",
699 m_CSeq.data(), dateHeader(), m_URLBuf.data(),
700 (int)strlen(m_SDPBuf.data()), m_SDPBuf.data());
701
702 // LOGI("handleRtspDescribe: %s", (const char*)m_Response.data());
703 Serial.println("------------------------------");
704 Serial.println((const char*)m_Response.data());
705 Serial.println("------------------------------");
706 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
707 }
708
713 // Build response depending on requested transport
714 if (m_TransportIsTcp) {
715 // Initialize TCP interleaved transport on same RTSP TCP socket
716 int ch0 = (m_InterleavedRtp >= 0) ? m_InterleavedRtp : 0;
717 int ch1 = (m_InterleavedRtcp >= 0) ? m_InterleavedRtcp : (ch0 + 1);
718 m_Streamer->initTcpInterleavedTransport(m_RtspClient, ch0, ch1);
719
720 // Reply with interleaved channels
721 snprintf(m_Buf1.data(), m_Buf1.size(),
722 "RTP/AVP/TCP;unicast;interleaved=%d-%d", ch0, ch1);
723 } else {
724 // init RTSP Session transport type (UDP) and ports for UDP transport
725 initTransport(m_ClientRTPPort, m_ClientRTCPPort);
726 // UDP Transport response with client/server ports and ssrc
727 snprintf(m_Buf1.data(), m_Buf1.size(),
728 "RTP/AVP;unicast;client_port=%i-%i;server_port=%i-%i;ssrc=%08X",
729 m_ClientRTPPort, m_ClientRTCPPort,
730 m_Streamer->getRtpServerPort(), m_Streamer->getRtcpServerPort(),
731 (unsigned)m_Streamer->currentSsrc());
732 }
733 snprintf(m_Response.data(), m_Response.size(),
734 "RTSP/1.0 200 OK\r\n"
735 "CSeq: %s\r\n"
736 "%s\r\n"
737 "Session: %i\r\n"
738 "Transport: %s\r\n"
739 "\r\n",
740 m_CSeq.data(), dateHeader(), m_RtspSessionID, m_Buf1.data());
741
742 Serial.println("------------------------------");
743 Serial.println((char*)m_Response.data());
744 Serial.println("------------------------------");
745
746 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
747 }
748
753 // Build RTP-Info to help clients (e.g., VLC) synchronize
754 // URL base
755 snprintf(m_URLBuf.data(), m_URLBuf.size(), "rtsp://%s/%s=0",
756 m_URLHostPort.data(), STD_URL_PRE_SUFFIX);
757
758 // Current seq/timestamp from streamer (values used for next packet)
759 uint16_t seq = m_Streamer ? m_Streamer->currentSeq() : 0;
760 uint32_t rtptime = m_Streamer ? m_Streamer->currentRtpTimestamp() : 0;
761
762 // PLAY response with Range, Session and RTP-Info
763 snprintf(m_Response.data(), m_Response.size(),
764 "RTSP/1.0 200 OK\r\n"
765 "CSeq: %s\r\n"
766 "Range: npt=0.000-\r\n"
767 "Session: %i\r\n"
768 "RTP-Info: url=%s;seq=%u;rtptime=%u\r\n\r\n",
769 m_CSeq.data(), m_RtspSessionID, m_URLBuf.data(), (unsigned)seq,
770 (unsigned)rtptime);
771
772 Serial.println("------------------------------");
773 Serial.println((char*)m_Response.data());
774 Serial.println("------------------------------");
775
776 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
777
778 m_Streamer->start();
779 }
780
786 if (m_streaming && m_Streamer) {
787 m_Streamer->stop();
788 }
789 snprintf(m_Response.data(), m_Response.size(),
790 "RTSP/1.0 200 OK\r\n"
791 "CSeq: %s\r\n"
792 "Session: %i\r\n\r\n",
793 m_CSeq.data(), m_RtspSessionID);
794 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
795 }
796
801 m_Streamer->stop();
802
803 // simulate SETUP server response
804 snprintf(m_Response.data(), m_Response.size(),
805 "RTSP/1.0 200 OK\r\n"
806 "CSeq: %s\r\n\r\n",
807 m_CSeq.data());
808
809 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
810
811 m_sessionOpen = false;
812 }
813
818 int getStreamID() { return m_StreamID; }
819
825 void initTransport(u_short aRtpPort, u_short aRtcpPort) {
826 m_RtpClientPort = aRtpPort;
827 m_RtcpClientPort = aRtcpPort;
828
829 IPAddress clientIP;
830 uint16_t clientPort;
831 Platform::getSocketPeerAddr(m_RtspClient, &clientIP, &clientPort);
832
833 LOGI("SETUP peer resolved: %s:%u (RTP client_port=%u)",
834 clientIP.toString().c_str(), (unsigned)clientPort,
835 (unsigned)m_RtpClientPort);
836
837 m_Streamer->initUdpTransport(clientIP, m_RtpClientPort);
838 }
839
840 typename Platform::TcpClientType*& getClient() { return m_RtspClient; }
841
842 uint16_t getRtpClientPort() { return m_RtpClientPort; }
843
852 inline int readSocket(typename Platform::TcpClientType* sock, char* buf,
853 size_t buflen, int timeoutmsec) {
854 return Platform::readSocket(sock, buf, buflen, timeoutmsec);
855 }
856
864 inline ssize_t sendSocket(typename Platform::TcpClientType* sock,
865 const void* buf, size_t len) {
866 return Platform::sendSocket(sock, buf, len);
867 }
868
873 char const* dateHeader() {
874 static char buf[200];
875 time_t tt = time(NULL);
876 strftime(buf, sizeof(buf), "Date: %a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
877 return buf;
878 }
879};
880
881} // 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:87
RTSP Session Handler - Individual Client Protocol Management.
Definition RTSPSession.h:77
void init()
Definition RTSPSession.h:284
int readSocket(typename Platform::TcpClientType *sock, char *buf, size_t buflen, int timeoutmsec)
Inline helper to read from socket.
Definition RTSPSession.h:852
void initTransport(u_short aRtpPort, u_short aRtcpPort)
Definition RTSPSession.h:825
ssize_t sendSocket(typename Platform::TcpClientType *sock, const void *buf, size_t len)
Inline helper to send data over socket.
Definition RTSPSession.h:864
void handleRtspSetup()
Definition RTSPSession.h:712
void handleRtspDescribe()
Definition RTSPSession.h:663
bool parseRtspRequest(char const *aRequest, unsigned aRequestSize)
Definition RTSPSession.h:396
RtspSession(typename Platform::TcpClientType &aClient, RTSPAudioStreamerBase< Platform > &aStreamer)
Construct RTSP session for a connected client.
Definition RTSPSession.h:97
int getStreamID()
Definition RTSPSession.h:818
void handleRtspTeardown()
Definition RTSPSession.h:800
RTSP_CMD_TYPES handleRtspRequest(char const *aRequest, unsigned aRequestSize)
Definition RTSPSession.h:354
~RtspSession()
Destructor - cleanup session resources.
Definition RTSPSession.h:113
void handleRtspPlay()
Definition RTSPSession.h:752
void handleRtspOption()
Definition RTSPSession.h:651
bool handleRequests(uint32_t readTimeoutMs)
Process incoming RTSP requests from the client.
Definition RTSPSession.h:148
char const * dateHeader()
Definition RTSPSession.h:873
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:785
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