Tasmota/lib/libesp32/rtsp/CRtspSession.cpp

413 lines
14 KiB
C++
Executable File

#include "CRtspSession.h"
#include <stdio.h>
#include <time.h>
CRtspSession::CRtspSession(SOCKET aRtspClient, CStreamer * aStreamer) : m_RtspClient(aRtspClient),m_Streamer(aStreamer)
{
printf("Creating RTSP session\n");
Init();
m_RtspSessionID = getRandom(); // create a session ID
m_RtspSessionID |= 0x80000000;
m_StreamID = -1;
m_ClientRTPPort = 0;
m_ClientRTCPPort = 0;
m_TcpTransport = false;
m_streaming = false;
m_stopped = false;
};
CRtspSession::~CRtspSession()
{
closesocket(m_RtspClient);
};
void CRtspSession::Init()
{
m_RtspCmdType = RTSP_UNKNOWN;
memset(m_URLPreSuffix, 0x00, sizeof(m_URLPreSuffix));
memset(m_URLSuffix, 0x00, sizeof(m_URLSuffix));
memset(m_CSeq, 0x00, sizeof(m_CSeq));
memset(m_URLHostPort, 0x00, sizeof(m_URLHostPort));
m_ContentLength = 0;
};
bool CRtspSession::ParseRtspRequest(char const * aRequest, unsigned aRequestSize)
{
char CmdName[RTSP_PARAM_STRING_MAX];
static char CurRequest[RTSP_BUFFER_SIZE]; // Note: we assume single threaded, this large buf we keep off of the tiny stack
unsigned CurRequestSize;
Init();
CurRequestSize = aRequestSize;
memcpy(CurRequest,aRequest,aRequestSize);
// check whether the request contains information about the RTP/RTCP UDP client ports (SETUP command)
char * ClientPortPtr;
char * TmpPtr;
static char CP[1024];
char * pCP;
ClientPortPtr = strstr(CurRequest,"client_port");
if (ClientPortPtr != nullptr)
{
TmpPtr = strstr(ClientPortPtr,"\r\n");
if (TmpPtr != nullptr)
{
TmpPtr[0] = 0x00;
strcpy(CP,ClientPortPtr);
pCP = strstr(CP,"=");
if (pCP != nullptr)
{
pCP++;
strcpy(CP,pCP);
pCP = strstr(CP,"-");
if (pCP != nullptr)
{
pCP[0] = 0x00;
m_ClientRTPPort = atoi(CP);
m_ClientRTCPPort = m_ClientRTPPort + 1;
};
};
};
};
// Read everything up to the first space as the command name
bool parseSucceeded = false;
unsigned i;
for (i = 0; i < sizeof(CmdName)-1 && i < CurRequestSize; ++i)
{
char c = CurRequest[i];
if (c == ' ' || c == '\t')
{
parseSucceeded = true;
break;
}
CmdName[i] = c;
}
CmdName[i] = '\0';
if (!parseSucceeded) {
printf("failed to parse RTSP\n");
return false;
}
printf("RTSP received %s\n", CmdName);
// find out the command type
if (strstr(CmdName,"OPTIONS") != nullptr) m_RtspCmdType = RTSP_OPTIONS; else
if (strstr(CmdName,"DESCRIBE") != nullptr) m_RtspCmdType = RTSP_DESCRIBE; else
if (strstr(CmdName,"SETUP") != nullptr) m_RtspCmdType = RTSP_SETUP; else
if (strstr(CmdName,"PLAY") != nullptr) m_RtspCmdType = RTSP_PLAY; else
if (strstr(CmdName,"TEARDOWN") != nullptr) m_RtspCmdType = RTSP_TEARDOWN;
// check whether the request contains transport information (UDP or TCP)
if (m_RtspCmdType == RTSP_SETUP)
{
TmpPtr = strstr(CurRequest,"RTP/AVP/TCP");
if (TmpPtr != nullptr) m_TcpTransport = true; else m_TcpTransport = false;
};
// Skip over the prefix of any "rtsp://" or "rtsp:/" URL that follows:
unsigned j = i+1;
while (j < CurRequestSize && (CurRequest[j] == ' ' || CurRequest[j] == '\t')) ++j; // skip over any additional white space
for (; (int)j < (int)(CurRequestSize-8); ++j)
{
if ((CurRequest[j] == 'r' || CurRequest[j] == 'R') &&
(CurRequest[j+1] == 't' || CurRequest[j+1] == 'T') &&
(CurRequest[j+2] == 's' || CurRequest[j+2] == 'S') &&
(CurRequest[j+3] == 'p' || CurRequest[j+3] == 'P') &&
CurRequest[j+4] == ':' && CurRequest[j+5] == '/')
{
j += 6;
if (CurRequest[j] == '/')
{ // This is a "rtsp://" URL; skip over the host:port part that follows:
++j;
unsigned uidx = 0;
while (j < CurRequestSize && CurRequest[j] != '/' && CurRequest[j] != ' ' && uidx < sizeof(m_URLHostPort) - 1)
{ // extract the host:port part of the URL here
m_URLHostPort[uidx] = CurRequest[j];
uidx++;
++j;
};
}
else --j;
i = j;
break;
}
}
// Look for the URL suffix (before the following "RTSP/"):
parseSucceeded = false;
for (unsigned k = i+1; (int)k < (int)(CurRequestSize-5); ++k)
{
if (CurRequest[k] == 'R' && CurRequest[k+1] == 'T' &&
CurRequest[k+2] == 'S' && CurRequest[k+3] == 'P' &&
CurRequest[k+4] == '/')
{
while (--k >= i && CurRequest[k] == ' ') {}
unsigned k1 = k;
while (k1 > i && CurRequest[k1] != '/') --k1;
if (k - k1 + 1 > sizeof(m_URLSuffix)) return false;
unsigned n = 0, k2 = k1+1;
while (k2 <= k) m_URLSuffix[n++] = CurRequest[k2++];
m_URLSuffix[n] = '\0';
if (k1 - i > sizeof(m_URLPreSuffix)) return false;
n = 0; k2 = i + 1;
while (k2 <= k1 - 1) m_URLPreSuffix[n++] = CurRequest[k2++];
m_URLPreSuffix[n] = '\0';
i = k + 7;
parseSucceeded = true;
break;
}
}
if (!parseSucceeded) return false;
// Look for "CSeq:", skip whitespace, then read everything up to the next \r or \n as 'CSeq':
parseSucceeded = false;
for (j = i; (int)j < (int)(CurRequestSize-5); ++j)
{
if (CurRequest[j] == 'C' && CurRequest[j+1] == 'S' &&
CurRequest[j+2] == 'e' && CurRequest[j+3] == 'q' &&
CurRequest[j+4] == ':')
{
j += 5;
while (j < CurRequestSize && (CurRequest[j] == ' ' || CurRequest[j] == '\t')) ++j;
unsigned n;
for (n = 0; n < sizeof(m_CSeq)-1 && j < CurRequestSize; ++n,++j)
{
char c = CurRequest[j];
if (c == '\r' || c == '\n')
{
parseSucceeded = true;
break;
}
m_CSeq[n] = c;
}
m_CSeq[n] = '\0';
break;
}
}
if (!parseSucceeded) return false;
// Also: Look for "Content-Length:" (optional)
for (j = i; (int)j < (int)(CurRequestSize-15); ++j)
{
if (CurRequest[j] == 'C' && CurRequest[j+1] == 'o' &&
CurRequest[j+2] == 'n' && CurRequest[j+3] == 't' &&
CurRequest[j+4] == 'e' && CurRequest[j+5] == 'n' &&
CurRequest[j+6] == 't' && CurRequest[j+7] == '-' &&
(CurRequest[j+8] == 'L' || CurRequest[j+8] == 'l') &&
CurRequest[j+9] == 'e' && CurRequest[j+10] == 'n' &&
CurRequest[j+11] == 'g' && CurRequest[j+12] == 't' &&
CurRequest[j+13] == 'h' && CurRequest[j+14] == ':')
{
j += 15;
while (j < CurRequestSize && (CurRequest[j] == ' ' || CurRequest[j] == '\t')) ++j;
unsigned num;
if (sscanf(&CurRequest[j], "%u", &num) == 1) m_ContentLength = num;
}
}
return true;
};
RTSP_CMD_TYPES CRtspSession::Handle_RtspRequest(char const * aRequest, unsigned aRequestSize)
{
if (ParseRtspRequest(aRequest,aRequestSize))
{
switch (m_RtspCmdType)
{
case RTSP_OPTIONS: { Handle_RtspOPTION(); break; };
case RTSP_DESCRIBE: { Handle_RtspDESCRIBE(); break; };
case RTSP_SETUP: { Handle_RtspSETUP(); break; };
case RTSP_PLAY: { Handle_RtspPLAY(); break; };
default: {};
};
};
return m_RtspCmdType;
};
void CRtspSession::Handle_RtspOPTION()
{
static char Response[1024]; // Note: we assume single threaded, this large buf we keep off of the tiny stack
snprintf(Response,sizeof(Response),
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
"Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n\r\n",m_CSeq);
socketsend(m_RtspClient,Response,strlen(Response));
}
void CRtspSession::Handle_RtspDESCRIBE()
{
static char Response[1024]; // Note: we assume single threaded, this large buf we keep off of the tiny stack
static char SDPBuf[1024];
static char URLBuf[1024];
// check whether we know a stream with the URL which is requested
m_StreamID = -1; // invalid URL
if ((strcmp(m_URLPreSuffix,"mjpeg") == 0) && (strcmp(m_URLSuffix,"1") == 0)) m_StreamID = 0; else
if ((strcmp(m_URLPreSuffix,"mjpeg") == 0) && (strcmp(m_URLSuffix,"2") == 0)) m_StreamID = 1;
if (m_StreamID == -1)
{ // Stream not available
snprintf(Response,sizeof(Response),
"RTSP/1.0 404 Stream Not Found\r\nCSeq: %s\r\n%s\r\n",
m_CSeq,
DateHeader());
socketsend(m_RtspClient,Response,strlen(Response));
return;
};
// simulate DESCRIBE server response
static char OBuf[256];
char * ColonPtr;
strcpy(OBuf,m_URLHostPort);
ColonPtr = strstr(OBuf,":");
if (ColonPtr != nullptr) ColonPtr[0] = 0x00;
snprintf(SDPBuf,sizeof(SDPBuf),
"v=0\r\n"
"o=- %d 1 IN IP4 %s\r\n"
"s=\r\n"
"t=0 0\r\n" // start / stop - 0 -> unbounded and permanent session
"m=video 0 RTP/AVP 26\r\n" // currently we just handle UDP sessions
// "a=x-dimensions: 640,480\r\n"
"c=IN IP4 0.0.0.0\r\n",
rand(),
OBuf);
char StreamName[64];
switch (m_StreamID)
{
case 0: strcpy(StreamName,"mjpeg/1"); break;
case 1: strcpy(StreamName,"mjpeg/2"); break;
};
snprintf(URLBuf,sizeof(URLBuf),
"rtsp://%s/%s",
m_URLHostPort,
StreamName);
snprintf(Response,sizeof(Response),
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
"%s\r\n"
"Content-Base: %s/\r\n"
"Content-Type: application/sdp\r\n"
"Content-Length: %d\r\n\r\n"
"%s",
m_CSeq,
DateHeader(),
URLBuf,
(int) strlen(SDPBuf),
SDPBuf);
socketsend(m_RtspClient,Response,strlen(Response));
}
void CRtspSession::Handle_RtspSETUP()
{
static char Response[1024];
static char Transport[255];
// init RTP streamer transport type (UDP or TCP) and ports for UDP transport
m_Streamer->InitTransport(m_ClientRTPPort,m_ClientRTCPPort,m_TcpTransport);
// simulate SETUP server response
if (m_TcpTransport)
snprintf(Transport,sizeof(Transport),"RTP/AVP/TCP;unicast;interleaved=0-1");
else
snprintf(Transport,sizeof(Transport),
"RTP/AVP;unicast;destination=127.0.0.1;source=127.0.0.1;client_port=%i-%i;server_port=%i-%i",
m_ClientRTPPort,
m_ClientRTCPPort,
m_Streamer->GetRtpServerPort(),
m_Streamer->GetRtcpServerPort());
snprintf(Response,sizeof(Response),
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
"%s\r\n"
"Transport: %s\r\n"
"Session: %i\r\n\r\n",
m_CSeq,
DateHeader(),
Transport,
m_RtspSessionID);
socketsend(m_RtspClient,Response,strlen(Response));
}
void CRtspSession::Handle_RtspPLAY()
{
static char Response[1024];
// simulate SETUP server response
snprintf(Response,sizeof(Response),
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
"%s\r\n"
"Range: npt=0.000-\r\n"
"Session: %i\r\n"
"RTP-Info: url=rtsp://127.0.0.1:8554/mjpeg/1/track1\r\n\r\n",
m_CSeq,
DateHeader(),
m_RtspSessionID);
socketsend(m_RtspClient,Response,strlen(Response));
}
char const * CRtspSession::DateHeader()
{
static char buf[200];
time_t tt = time(NULL);
strftime(buf, sizeof buf, "Date: %a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
return buf;
}
int CRtspSession::GetStreamID()
{
return m_StreamID;
};
/**
Read from our socket, parsing commands as possible.
*/
bool CRtspSession::handleRequests(uint32_t readTimeoutMs)
{
if(m_stopped)
return false; // Already closed down
static char RecvBuf[RTSP_BUFFER_SIZE]; // Note: we assume single threaded, this large buf we keep off of the tiny stack
memset(RecvBuf,0x00,sizeof(RecvBuf));
int res = socketread(m_RtspClient,RecvBuf,sizeof(RecvBuf), readTimeoutMs);
if(res > 0) {
// we filter away everything which seems not to be an RTSP command: O-ption, D-escribe, S-etup, P-lay, T-eardown
if ((RecvBuf[0] == 'O') || (RecvBuf[0] == 'D') || (RecvBuf[0] == 'S') || (RecvBuf[0] == 'P') || (RecvBuf[0] == 'T'))
{
RTSP_CMD_TYPES C = Handle_RtspRequest(RecvBuf,res);
if (C == RTSP_PLAY)
m_streaming = true;
else if (C == RTSP_TEARDOWN)
m_stopped = true;
}
return true;
}
else if(res == 0) {
printf("client closed socket, exiting\n");
m_stopped = true;
return true;
}
else {
// Timeout on read
return false;
}
}
void CRtspSession::broadcastCurrentFrame(uint32_t curMsec) {
// Send a frame
if (m_streaming && !m_stopped) {
// printf("serving a frame\n");
m_Streamer->streamImage(curMsec);
}
}