Dynamically determine audio, video, and control ports from RTSP DESCRIBE response

This commit is contained in:
Cameron Gutman 2021-07-01 23:45:33 -05:00
parent 2660a05084
commit 56ccd99cc7
6 changed files with 101 additions and 16 deletions

View File

@ -14,6 +14,7 @@ static uint32_t avRiKeyId;
static unsigned short lastSeq; static unsigned short lastSeq;
static bool pingThreadStarted;
static bool receivedDataFromPeer; static bool receivedDataFromPeer;
static uint64_t firstReceiveTime; static uint64_t firstReceiveTime;
@ -22,8 +23,6 @@ static uint64_t firstReceiveTime;
static uint8_t opusHeaderByte; static uint8_t opusHeaderByte;
#endif #endif
#define RTP_PORT 48000
#define MAX_PACKET_SIZE 1400 #define MAX_PACKET_SIZE 1400
// This is much larger than we should typically have buffered, but // This is much larger than we should typically have buffered, but
@ -47,7 +46,7 @@ static void AudioPingThreadProc(void* context) {
LC_SOCKADDR saddr; LC_SOCKADDR saddr;
memcpy(&saddr, &RemoteAddr, sizeof(saddr)); memcpy(&saddr, &RemoteAddr, sizeof(saddr));
SET_PORT(&saddr, RTP_PORT); SET_PORT(&saddr, AudioPortNumber);
// Send PING every 500 milliseconds // Send PING every 500 milliseconds
while (!PltIsThreadInterrupted(&udpPingThread)) { while (!PltIsThreadInterrupted(&udpPingThread)) {
@ -74,6 +73,7 @@ int initializeAudioStream(void) {
RtpaInitializeQueue(&rtpAudioQueue); RtpaInitializeQueue(&rtpAudioQueue);
lastSeq = 0; lastSeq = 0;
receivedDataFromPeer = false; receivedDataFromPeer = false;
pingThreadStarted = false;
firstReceiveTime = 0; firstReceiveTime = 0;
audioDecryptionCtx = PltCreateCryptoContext(); audioDecryptionCtx = PltCreateCryptoContext();
#ifdef LC_DEBUG #ifdef LC_DEBUG
@ -91,15 +91,23 @@ int initializeAudioStream(void) {
return LastSocketFail(); return LastSocketFail();
} }
return 0;
}
// This is called when the RTSP DESCRIBE message is parsed and the audio port
// number is parsed out of it. Alternatively, it's also called if parsing fails
// and will use the well known audio port instead.
int notifyAudioPortNegotiationComplete(void) {
LC_ASSERT(!pingThreadStarted);
// We may receive audio before our threads are started, but that's okay. We'll // We may receive audio before our threads are started, but that's okay. We'll
// drop the first 1 second of audio packets to catch up with the backlog. // drop the first 1 second of audio packets to catch up with the backlog.
int err = PltCreateThread("AudioPing", AudioPingThreadProc, NULL, &udpPingThread); int err = PltCreateThread("AudioPing", AudioPingThreadProc, NULL, &udpPingThread);
if (err != 0) { if (err != 0) {
closeSocket(rtpSocket);
rtpSocket = INVALID_SOCKET;
return err; return err;
} }
pingThreadStarted = true;
return 0; return 0;
} }
@ -119,9 +127,11 @@ static void freePacketList(PLINKED_BLOCKING_QUEUE_ENTRY entry) {
// Tear down the audio stream once we're done with it // Tear down the audio stream once we're done with it
void destroyAudioStream(void) { void destroyAudioStream(void) {
if (rtpSocket != INVALID_SOCKET) { if (rtpSocket != INVALID_SOCKET) {
PltInterruptThread(&udpPingThread); if (pingThreadStarted) {
PltJoinThread(&udpPingThread); PltInterruptThread(&udpPingThread);
PltCloseThread(&udpPingThread); PltJoinThread(&udpPingThread);
PltCloseThread(&udpPingThread);
}
closeSocket(rtpSocket); closeSocket(rtpSocket);
rtpSocket = INVALID_SOCKET; rtpSocket = INVALID_SOCKET;

View File

@ -24,6 +24,10 @@ OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig;
int OriginalVideoBitrate; int OriginalVideoBitrate;
int AudioPacketDuration; int AudioPacketDuration;
bool AudioEncryptionEnabled; bool AudioEncryptionEnabled;
uint16_t RtspPortNumber;
uint16_t ControlPortNumber;
uint16_t AudioPortNumber;
uint16_t VideoPortNumber;
// Connection stages // Connection stages
static const char* stageNames[STAGE_MAX] = { static const char* stageNames[STAGE_MAX] = {
@ -208,6 +212,12 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
OriginalVideoBitrate = streamConfig->bitrate; OriginalVideoBitrate = streamConfig->bitrate;
RemoteAddrString = strdup(serverInfo->address); RemoteAddrString = strdup(serverInfo->address);
// Configure default ports
VideoPortNumber = 47998;
ControlPortNumber = 47999;
AudioPortNumber = 48000;
RtspPortNumber = 48010; // TODO: Parse this out of RTSP session URL
alreadyTerminated = false; alreadyTerminated = false;
ConnectionInterrupted = false; ConnectionInterrupted = false;

View File

@ -1143,7 +1143,7 @@ int startControlStream(void) {
ENetEvent event; ENetEvent event;
enet_address_set_address(&address, (struct sockaddr *)&RemoteAddr, RemoteAddrLen); enet_address_set_address(&address, (struct sockaddr *)&RemoteAddr, RemoteAddrLen);
enet_address_set_port(&address, 47999); enet_address_set_port(&address, ControlPortNumber);
// Create a client that can use 1 outgoing connection and 1 channel // Create a client that can use 1 outgoing connection and 1 channel
client = enet_host_create(address.address.ss_family, NULL, 1, 1, 0, 0); client = enet_host_create(address.address.ss_family, NULL, 1, 1, 0, 0);
@ -1166,7 +1166,7 @@ int startControlStream(void) {
// Wait for the connect to complete // Wait for the connect to complete
if (serviceEnetHost(client, &event, CONTROL_STREAM_TIMEOUT_SEC * 1000) <= 0 || if (serviceEnetHost(client, &event, CONTROL_STREAM_TIMEOUT_SEC * 1000) <= 0 ||
event.type != ENET_EVENT_TYPE_CONNECT) { event.type != ENET_EVENT_TYPE_CONNECT) {
Limelog("Failed to connect to UDP port 47999\n"); Limelog("Failed to connect to UDP port %u\n", ControlPortNumber);
stopping = true; stopping = true;
enet_peer_reset(peer); enet_peer_reset(peer);
peer = NULL; peer = NULL;
@ -1182,6 +1182,7 @@ int startControlStream(void) {
enet_peer_timeout(peer, 2, 10000, 10000); enet_peer_timeout(peer, 2, 10000, 10000);
} }
else { else {
// NB: Do NOT use ControlPortNumber here. 47995 is correct for these old versions.
ctlSock = connectTcpSocket(&RemoteAddr, RemoteAddrLen, ctlSock = connectTcpSocket(&RemoteAddr, RemoteAddrLen,
47995, CONTROL_STREAM_TIMEOUT_SEC); 47995, CONTROL_STREAM_TIMEOUT_SEC);
if (ctlSock == INVALID_SOCKET) { if (ctlSock == INVALID_SOCKET) {

View File

@ -32,6 +32,11 @@ extern int OriginalVideoBitrate;
extern int AudioPacketDuration; extern int AudioPacketDuration;
extern bool AudioEncryptionEnabled; extern bool AudioEncryptionEnabled;
extern uint16_t RtspPortNumber;
extern uint16_t ControlPortNumber;
extern uint16_t AudioPortNumber;
extern uint16_t VideoPortNumber;
#ifndef UINT24_MAX #ifndef UINT24_MAX
#define UINT24_MAX 0xFFFFFF #define UINT24_MAX 0xFFFFFF
#endif #endif
@ -100,6 +105,7 @@ int startVideoStream(void* rendererContext, int drFlags);
void stopVideoStream(void); void stopVideoStream(void);
int initializeAudioStream(void); int initializeAudioStream(void);
int notifyAudioPortNegotiationComplete(void);
void destroyAudioStream(void); void destroyAudioStream(void);
int startAudioStream(void* audioContext, int arFlags); int startAudioStream(void* audioContext, int arFlags);
void stopAudioStream(void); void stopAudioStream(void);

View File

@ -228,7 +228,7 @@ static bool transactRtspMessageTcp(PRTSP_MESSAGE request, PRTSP_MESSAGE response
// returns HTTP 200 OK for the /launch request before the RTSP handshake port // returns HTTP 200 OK for the /launch request before the RTSP handshake port
// is listening. // is listening.
do { do {
sock = connectTcpSocket(&RemoteAddr, RemoteAddrLen, 48010, RTSP_TIMEOUT_SEC); sock = connectTcpSocket(&RemoteAddr, RemoteAddrLen, RtspPortNumber, RTSP_TIMEOUT_SEC);
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
*error = LastSocketError(); *error = LastSocketError();
if (*error == ECONNREFUSED) { if (*error == ECONNREFUSED) {
@ -525,6 +525,57 @@ static int parseOpusConfigFromParamString(char* paramStr, int channelCount, POPU
return 0; return 0;
} }
static bool parseMediaEntry(PRTSP_MESSAGE response, const char* mediaType, const char* transport, uint16_t* port) {
char paramsPrefix[128];
char paramsSuffix[128];
char* paramStart;
sprintf(paramsPrefix, "m=%s ", mediaType);
sprintf(paramsSuffix, " %s", transport);
// Look for the next match
paramStart = response->payload;
while ((paramStart = strstr(paramStart, paramsPrefix)) != NULL) {
// Skip the prefix
paramStart += strlen(paramsPrefix);
// The first part should be the port number
char* nextToken;
long int rawPort = strtol(paramStart, &nextToken, 10);
if (rawPort <= 0 || rawPort >= 65535) {
continue;
}
// Skip this entry if the transport isn't what we expect
if (strncmp(nextToken, paramsSuffix, strlen(paramsSuffix)) != 0) {
continue;
}
// This entry is a match
*port = (uint16_t)rawPort;
return true;
}
// No match for this media type and transport
return false;
}
static void parsePortConfigurations(PRTSP_MESSAGE response) {
// Don't parse ports on very old GFE versions
if (!APP_VERSION_AT_LEAST(7, 0, 0)) {
return;
}
parseMediaEntry(response, "video", "RTP/AVP", &VideoPortNumber);
parseMediaEntry(response, "audio", "RTP/AVP", &AudioPortNumber);
if (!parseMediaEntry(response, "application", "udp", &ControlPortNumber)) {
if (!parseMediaEntry(response, "application", "udp_enc", &ControlPortNumber)) {
parseMediaEntry(response, "application", "udp_ag", &ControlPortNumber);
}
}
}
// Parses the Opus configuration from an RTSP DESCRIBE response // Parses the Opus configuration from an RTSP DESCRIBE response
static int parseOpusConfigurations(PRTSP_MESSAGE response) { static int parseOpusConfigurations(PRTSP_MESSAGE response) {
HighQualitySurroundSupported = false; HighQualitySurroundSupported = false;
@ -644,7 +695,7 @@ int performRtspHandshake(void) {
// Initialize global state // Initialize global state
useEnet = (AppVersionQuad[0] >= 5) && (AppVersionQuad[0] <= 7) && (AppVersionQuad[2] < 404); useEnet = (AppVersionQuad[0] >= 5) && (AppVersionQuad[0] <= 7) && (AppVersionQuad[2] < 404);
sprintf(rtspTargetUrl, "rtsp%s://%s:48010", useEnet ? "ru" : "", urlAddr); sprintf(rtspTargetUrl, "rtsp%s://%s:%u", useEnet ? "ru" : "", urlAddr, RtspPortNumber);
currentSeqNumber = 1; currentSeqNumber = 1;
hasSessionId = false; hasSessionId = false;
controlStreamId = APP_VERSION_AT_LEAST(7, 1, 431) ? "streamid=control/13/0" : "streamid=control/1/0"; controlStreamId = APP_VERSION_AT_LEAST(7, 1, 431) ? "streamid=control/13/0" : "streamid=control/1/0";
@ -676,7 +727,7 @@ int performRtspHandshake(void) {
ENetEvent event; ENetEvent event;
enet_address_set_address(&address, (struct sockaddr *)&RemoteAddr, RemoteAddrLen); enet_address_set_address(&address, (struct sockaddr *)&RemoteAddr, RemoteAddrLen);
enet_address_set_port(&address, 48010); enet_address_set_port(&address, RtspPortNumber);
// Create a client that can use 1 outgoing connection and 1 channel // Create a client that can use 1 outgoing connection and 1 channel
client = enet_host_create(RemoteAddr.ss_family, NULL, 1, 1, 0, 0); client = enet_host_create(RemoteAddr.ss_family, NULL, 1, 1, 0, 0);
@ -695,7 +746,7 @@ int performRtspHandshake(void) {
// Wait for the connect to complete // Wait for the connect to complete
if (serviceEnetHost(client, &event, RTSP_TIMEOUT_SEC * 1000) <= 0 || if (serviceEnetHost(client, &event, RTSP_TIMEOUT_SEC * 1000) <= 0 ||
event.type != ENET_EVENT_TYPE_CONNECT) { event.type != ENET_EVENT_TYPE_CONNECT) {
Limelog("RTSP: Failed to connect to UDP port 48010\n"); Limelog("RTSP: Failed to connect to UDP port %u\n", RtspPortNumber);
enet_peer_reset(peer); enet_peer_reset(peer);
peer = NULL; peer = NULL;
enet_host_destroy(client); enet_host_destroy(client);
@ -773,6 +824,14 @@ int performRtspHandshake(void) {
} }
} }
// Parse audio, video, and control ports out of the RTSP DESCRIBE response.
parsePortConfigurations(&response);
// Let the audio stream know the port number is now finalized.
// NB: This is needed because audio stream init happens before RTSP,
// which is not the case for the video stream.
notifyAudioPortNegotiationComplete();
// Parse the Opus surround parameters out of the RTSP DESCRIBE response. // Parse the Opus surround parameters out of the RTSP DESCRIBE response.
ret = parseOpusConfigurations(&response); ret = parseOpusConfigurations(&response);
if (ret != 0) { if (ret != 0) {

View File

@ -3,7 +3,6 @@
#define FIRST_FRAME_MAX 1500 #define FIRST_FRAME_MAX 1500
#define FIRST_FRAME_TIMEOUT_SEC 10 #define FIRST_FRAME_TIMEOUT_SEC 10
#define RTP_PORT 47998
#define FIRST_FRAME_PORT 47996 #define FIRST_FRAME_PORT 47996
#define RTP_RECV_BUFFER (512 * 1024) #define RTP_RECV_BUFFER (512 * 1024)
@ -48,7 +47,7 @@ static void VideoPingThreadProc(void* context) {
LC_SOCKADDR saddr; LC_SOCKADDR saddr;
memcpy(&saddr, &RemoteAddr, sizeof(saddr)); memcpy(&saddr, &RemoteAddr, sizeof(saddr));
SET_PORT(&saddr, RTP_PORT); SET_PORT(&saddr, VideoPortNumber);
while (!PltIsThreadInterrupted(&udpPingThread)) { while (!PltIsThreadInterrupted(&udpPingThread)) {
// We do not check for errors here. Socket errors will be handled // We do not check for errors here. Socket errors will be handled