16#include "AudioTools/CoreAudio/AudioBasic/Collections/Vector.h"
17#include "RTSPAudioStreamer.h"
18#include "RTSPPlatform.h"
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
76template <
typename Platform>
99 : m_Client(aClient), m_Streamer(&aStreamer) {
100 m_RtspClient = &m_Client;
101 m_RtspSessionID = random(65536);
102 LOGI(
"RTSP session created");
114 LOGI(
"RTSP session destructor");
117 if (m_streaming && m_Streamer) {
118 LOGI(
"Final cleanup: stopping streamer in destructor");
120 m_Streamer->releaseUdpTransport();
125 if (m_RtspClient && m_RtspClient->connected()) {
126 Platform::closeSocket(m_RtspClient);
129 LOGI(
"RTSP session cleanup completed");
149 LOGD(
"handleRequests");
153 if (!m_sessionOpen) {
158 memset(mRecvBuf.data(), 0x00, RTSP_BUFFER_SIZE);
159 int res =
readSocket(m_RtspClient, mRecvBuf.data(), RTSP_BUFFER_SIZE,
164 if ((mRecvBuf[0] ==
'O') || (mRecvBuf[0] ==
'D') ||
165 (mRecvBuf[0] ==
'S') || (mRecvBuf[0] ==
'P') ||
166 (mRecvBuf[0] ==
'T')) {
168 if (!m_sessionOpen) {
173 if (C == RTSP_PLAY) {
175 }
else if (C == RTSP_PAUSE) {
177 }
else if (C == RTSP_TEARDOWN) {
178 m_sessionOpen =
false;
181 if (m_streaming && m_Streamer) {
182 LOGI(
"Stopping streamer due to TEARDOWN");
184 m_Streamer->releaseUdpTransport();
190 }
else if (res == 0) {
191 LOGW(
"client closed socket, exiting");
192 m_sessionOpen =
false;
195 if (m_streaming && m_Streamer) {
196 LOGI(
"Stopping streamer due to client disconnect");
198 m_Streamer->releaseUdpTransport();
210 bool isSessionOpen() {
return m_sessionOpen; }
212 bool isStreaming() {
return m_streaming; }
226 m_onSessionPath = cb;
227 m_onSessionPathRef = ref;
231 const char* STD_URL_PRE_SUFFIX =
"trackID";
235 typename Platform::TcpClientType m_Client;
236 typename Platform::TcpClientType* m_RtspClient =
239 uint16_t m_ClientRTPPort;
240 uint16_t m_ClientRTCPPort;
251 unsigned m_ContentLength;
252 uint16_t m_RtpClientPort =
254 uint16_t m_RtcpClientPort =
257 bool m_TransportIsTcp =
false;
258 int m_InterleavedRtp = -1;
259 int m_InterleavedRtcp = -1;
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;
285 if (m_is_init)
return;
290 m_sessionOpen =
true;
293 if (mRecvBuf.size() == 0) {
294 mRecvBuf.resize(RTSP_BUFFER_SIZE);
296 if (mCurRequest.size() == 0) {
297 mCurRequest.resize(RTSP_BUFFER_SIZE);
299 if (m_URLPreSuffix.size() == 0) {
300 m_URLPreSuffix.resize(RTSP_PARAM_STRING_MAX);
302 if (m_URLSuffix.size() == 0) {
303 m_URLSuffix.resize(RTSP_PARAM_STRING_MAX);
305 if (m_CSeq.size() == 0) {
306 m_CSeq.resize(RTSP_PARAM_STRING_MAX);
308 if (m_URLHostPort.size() == 0) {
309 m_URLHostPort.resize(MAX_HOSTNAME_LEN);
311 if (m_URLPath.size() == 0) {
312 m_URLPath.resize(RTSP_URL_BUFFER_SIZE);
314 if (m_Response.size() == 0) {
315 m_Response.resize(RTSP_RESPONSE_BUFFER_SIZE);
317 if (m_SDPBuf.size() == 0) {
318 m_SDPBuf.resize(RTSP_SDP_BUFFER_SIZE);
320 if (m_URLBuf.size() == 0) {
321 m_URLBuf.resize(RTSP_URL_BUFFER_SIZE);
323 if (m_Buf1.size() == 0) {
324 m_Buf1.resize(RTSP_SMALL_BUFFER_SIZE);
326 if (m_Buf2.size() == 0) {
327 m_Buf2.resize(RTSP_SMALL_BUFFER_SIZE);
329 if (m_CmdName.size() == 0) {
330 m_CmdName.resize(RTSP_PARAM_STRING_MAX);
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());
340 m_TransportIsTcp =
false;
341 m_InterleavedRtp = -1;
342 m_InterleavedRtcp = -1;
344 m_pathNotified =
false;
355 unsigned aRequestSize) {
357 switch (m_RtspCmdType) {
362 case RTSP_DESCRIBE: {
378 case RTSP_TEARDOWN: {
386 return m_RtspCmdType;
397 LOGI(
"aRequest: ------------------------\n%s\n-------------------------",
400 const unsigned CurRequestSize = aRequestSize;
401 memcpy(mCurRequest.data(), aRequest, aRequestSize);
404 parseClientPorts(mCurRequest.data());
405 parseTransportHeader(mCurRequest.data());
408 unsigned idxAfterCmd = 0;
409 if (!parseCommandName(mCurRequest.data(), CurRequestSize, idxAfterCmd))
411 determineCommandType();
412 parseUrlHostPortAndSuffix(mCurRequest.data(), CurRequestSize, idxAfterCmd);
413 if (!m_sessionOpen) {
419 if (!parseCSeq(mCurRequest.data(), CurRequestSize, idxAfterCmd))
421 parseContentLength(mCurRequest.data(), CurRequestSize, idxAfterCmd);
424 detectClientHeaderPreference(mCurRequest.data());
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];
439 strcpy(CP, ClientPortPtr);
440 char* eq = strstr(CP,
"=");
444 char* dash = strstr(CP,
"-");
447 m_ClientRTPPort = atoi(CP);
448 m_ClientRTCPPort = m_ClientRTPPort + 1;
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];
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=");
468 inter += strlen(
"interleaved=");
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;
484 bool parseCommandName(
char* req,
unsigned reqSize,
unsigned& outIdx) {
487 for (i = 0; i < m_CmdName.size() - 1 && i < reqSize; ++i) {
489 if (c ==
' ' || c ==
'\t') {
497 LOGE(
"failed to parse RTSP");
500 LOGI(
"RTSP received %s", m_CmdName.data());
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;
519 LOGE(
"Error: Unsupported Command received (%s)!", m_CmdName.data());
522 void parseUrlHostPortAndSuffix(
char* req,
unsigned reqSize,
unsigned& i) {
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] ==
'/') {
533 while (j < reqSize && req[j] !=
'/' && req[j] !=
' ' && uidx < m_URLHostPort.size() - 1) {
534 m_URLHostPort[uidx++] = req[j++];
543 LOGD(
"m_URLHostPort: %s", m_URLHostPort.data());
546 if (i < reqSize && req[i] ==
'/') {
549 while (k < reqSize && req[k] !=
' ' && p < m_URLPath.size() - 1) {
550 m_URLPath[p++] = req[k++];
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;
558 LOGW(
"Session rejected by onSessionPath callback");
559 m_sessionOpen =
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] ==
' ') {}
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;
578 while (k2 <= k1 - 1) m_URLPreSuffix[n++] = req[k2++];
579 m_URLPreSuffix[n] =
'\0';
585 LOGD(
"m_URLSuffix: %s", m_URLSuffix.data());
586 LOGD(
"m_URLPreSuffix: %s", m_URLPreSuffix.data());
587 LOGD(
"URL Suffix parse succeeded: %i", ok);
590 bool parseCSeq(
char* req,
unsigned reqSize,
unsigned startIdx) {
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] ==
':') {
595 while (j < reqSize && (req[j] ==
' ' || req[j] ==
'\t')) ++j;
597 for (n = 0; n < m_CSeq.size() - 1 && j < reqSize; ++n, ++j) {
599 if (c ==
'\r' || c ==
'\n') {
609 LOGD(
"Look for CSeq success: %i", ok);
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] ==
':') {
620 while (j < reqSize && (req[j] ==
' ' || req[j] ==
'\t')) ++j;
622 if (sscanf(&req[j],
"%u", &num) == 1) m_ContentLength = num;
627 void detectClientHeaderPreference(
char* req) {
628 char* ua = strstr(req,
"User-Agent:");
629 bool want_rfc2250 =
false;
631 if (strcasestr(ua,
"ffmpeg") || strcasestr(ua,
"ffplay") || strcasestr(ua,
"libavformat") ||
632 strcasestr(ua,
"Lavf")) {
635 if (strcasestr(ua,
"vlc")) want_rfc2250 =
false;
637 char* qm = strchr(req,
'?');
639 if (strstr(qm,
"mpa_hdr=1")) want_rfc2250 =
true;
640 if (strstr(qm,
"mpa_hdr=0")) want_rfc2250 =
false;
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",
657 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
667 if (strcmp(m_URLPreSuffix.data(), STD_URL_PRE_SUFFIX) == 0) {
669 m_StreamID = strtol(m_URLSuffix.data(), &end, 10);
670 if (*end !=
'\0') m_StreamID = -1;
675 strncpy(m_Buf1.data(), m_URLHostPort.data(), 256);
676 ColonPtr = strstr(m_Buf1.data(),
":");
677 if (ColonPtr !=
nullptr) ColonPtr[0] = 0x00;
680 m_SDPBuf.data(), m_SDPBuf.size(),
682 "o=- %d 0 IN IP4 %s\r\n"
685 rand() & 0xFF, m_Buf1.data(),
689 snprintf(m_URLBuf.data(), m_URLBuf.size(),
"rtsp://%s",
690 m_URLHostPort.data());
692 snprintf(m_Response.data(), m_Response.size(),
693 "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
695 "Content-Base: %s/\r\n"
696 "Content-Type: application/sdp\r\n"
697 "Content-Length: %d\r\n\r\n"
700 (
int)strlen(m_SDPBuf.data()), m_SDPBuf.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()));
714 if (m_TransportIsTcp) {
716 int ch0 = (m_InterleavedRtp >= 0) ? m_InterleavedRtp : 0;
717 int ch1 = (m_InterleavedRtcp >= 0) ? m_InterleavedRtcp : (ch0 + 1);
721 snprintf(m_Buf1.data(), m_Buf1.size(),
722 "RTP/AVP/TCP;unicast;interleaved=%d-%d", ch0, ch1);
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,
733 snprintf(m_Response.data(), m_Response.size(),
734 "RTSP/1.0 200 OK\r\n"
740 m_CSeq.data(),
dateHeader(), m_RtspSessionID, m_Buf1.data());
742 Serial.println(
"------------------------------");
743 Serial.println((
char*)m_Response.data());
744 Serial.println(
"------------------------------");
746 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
755 snprintf(m_URLBuf.data(), m_URLBuf.size(),
"rtsp://%s/%s=0",
756 m_URLHostPort.data(), STD_URL_PRE_SUFFIX);
759 uint16_t seq = m_Streamer ? m_Streamer->
currentSeq() : 0;
763 snprintf(m_Response.data(), m_Response.size(),
764 "RTSP/1.0 200 OK\r\n"
766 "Range: npt=0.000-\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,
772 Serial.println(
"------------------------------");
773 Serial.println((
char*)m_Response.data());
774 Serial.println(
"------------------------------");
776 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
786 if (m_streaming && m_Streamer) {
789 snprintf(m_Response.data(), m_Response.size(),
790 "RTSP/1.0 200 OK\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()));
804 snprintf(m_Response.data(), m_Response.size(),
805 "RTSP/1.0 200 OK\r\n"
809 sendSocket(m_RtspClient, m_Response.data(), strlen(m_Response.data()));
811 m_sessionOpen =
false;
826 m_RtpClientPort = aRtpPort;
827 m_RtcpClientPort = aRtcpPort;
831 Platform::getSocketPeerAddr(m_RtspClient, &clientIP, &clientPort);
833 LOGI(
"SETUP peer resolved: %s:%u (RTP client_port=%u)",
834 clientIP.toString().c_str(), (
unsigned)clientPort,
835 (
unsigned)m_RtpClientPort);
840 typename Platform::TcpClientType*& getClient() {
return m_RtspClient; }
842 uint16_t getRtpClientPort() {
return m_RtpClientPort; }
852 inline int readSocket(
typename Platform::TcpClientType* sock,
char* buf,
853 size_t buflen,
int timeoutmsec) {
854 return Platform::readSocket(sock, buf, buflen, timeoutmsec);
864 inline ssize_t
sendSocket(
typename Platform::TcpClientType* sock,
865 const void* buf,
size_t len) {
866 return Platform::sendSocket(sock, buf, len);
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));