diff --git a/src/AudioStream.c b/src/AudioStream.c index 5fdc707..3447d61 100644 --- a/src/AudioStream.c +++ b/src/AudioStream.c @@ -41,8 +41,7 @@ typedef struct _QUEUED_AUDIO_PACKET { } QUEUED_AUDIO_PACKET, *PQUEUED_AUDIO_PACKET; static void AudioPingThreadProc(void* context) { - // Ping in ASCII - char pingData[] = { 0x50, 0x49, 0x4E, 0x47 }; + char legacyPingData[] = { 0x50, 0x49, 0x4E, 0x47 }; LC_SOCKADDR saddr; LC_ASSERT(AudioPortNumber != 0); @@ -50,19 +49,20 @@ static void AudioPingThreadProc(void* context) { memcpy(&saddr, &RemoteAddr, sizeof(saddr)); SET_PORT(&saddr, AudioPortNumber); - // Send PING every 500 milliseconds + // We do not check for errors here. Socket errors will be handled + // on the read-side in ReceiveThreadProc(). This avoids potential + // issues related to receiving ICMP port unreachable messages due + // to sending a packet prior to the host PC binding to that port. + int pingCount = 0; while (!PltIsThreadInterrupted(&udpPingThread)) { - // We do not check for errors here. Socket errors will be handled - // on the read-side in ReceiveThreadProc(). This avoids potential - // issues related to receiving ICMP port unreachable messages due - // to sending a packet prior to the host PC binding to that port. - sendto(rtpSocket, pingData, sizeof(pingData), 0, (struct sockaddr*)&saddr, RemoteAddrLen); + if (AudioPingPayload.payload[0] != 0) { + pingCount++; + AudioPingPayload.sequenceNumber = BE32(pingCount); - if (firstReceiveTime == 0 && isSocketReadable(rtpSocket)) { - // Remember the time when we got our first incoming audio packet. - // We will need to adjust for the delay between this event and - // when the real receive thread is ready to avoid falling behind. - firstReceiveTime = PltGetMillis(); + sendto(rtpSocket, (char*)&AudioPingPayload, sizeof(AudioPingPayload), 0, (struct sockaddr*)&saddr, RemoteAddrLen); + } + else { + sendto(rtpSocket, legacyPingData, sizeof(legacyPingData), 0, (struct sockaddr*)&saddr, RemoteAddrLen); } PltSleepMsInterruptible(&udpPingThread, 500); diff --git a/src/Connection.c b/src/Connection.c index b37201d..3034185 100644 --- a/src/Connection.c +++ b/src/Connection.c @@ -29,6 +29,8 @@ uint16_t RtspPortNumber; uint16_t ControlPortNumber; uint16_t AudioPortNumber; uint16_t VideoPortNumber; +SS_PING AudioPingPayload; +SS_PING VideoPingPayload; // Connection stages static const char* stageNames[STAGE_MAX] = { diff --git a/src/Limelight-internal.h b/src/Limelight-internal.h index cfefe32..d186b46 100644 --- a/src/Limelight-internal.h +++ b/src/Limelight-internal.h @@ -38,6 +38,9 @@ extern uint16_t ControlPortNumber; extern uint16_t AudioPortNumber; extern uint16_t VideoPortNumber; +extern SS_PING AudioPingPayload; +extern SS_PING VideoPingPayload; + #ifndef UINT24_MAX #define UINT24_MAX 0xFFFFFF #endif diff --git a/src/RtspConnection.c b/src/RtspConnection.c index 87f5086..65fca43 100644 --- a/src/RtspConnection.c +++ b/src/RtspConnection.c @@ -893,6 +893,7 @@ int performRtspHandshake(PSERVER_INFORMATION serverInfo) { { RTSP_MESSAGE response; char* sessionId; + char* pingPayload; int error = -1; if (!setupStream(&response, @@ -922,6 +923,13 @@ int performRtspHandshake(PSERVER_INFORMATION serverInfo) { Limelog("Audio port: %u\n", AudioPortNumber); } + // Parse the Sunshine ping payload protocol extension if present + memset(&AudioPingPayload, 0, sizeof(AudioPingPayload)); + pingPayload = getOptionContent(response.options, "X-SS-Ping-Payload"); + if (pingPayload != NULL && strlen(pingPayload) == sizeof(AudioPingPayload.payload)) { + memcpy(AudioPingPayload.payload, pingPayload, sizeof(AudioPingPayload.payload)); + } + // 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. @@ -955,6 +963,7 @@ int performRtspHandshake(PSERVER_INFORMATION serverInfo) { { RTSP_MESSAGE response; int error = -1; + char* pingPayload; if (!setupStream(&response, AppVersionQuad[0] >= 5 ? "streamid=video/0/0" : "streamid=video", @@ -971,6 +980,13 @@ int performRtspHandshake(PSERVER_INFORMATION serverInfo) { goto Exit; } + // Parse the Sunshine ping payload protocol extension if present + memset(&VideoPingPayload, 0, sizeof(VideoPingPayload)); + pingPayload = getOptionContent(response.options, "X-SS-Ping-Payload"); + if (pingPayload != NULL && strlen(pingPayload) == sizeof(VideoPingPayload.payload)) { + memcpy(VideoPingPayload.payload, pingPayload, sizeof(VideoPingPayload.payload)); + } + // Parse the video port out of the RTSP SETUP response LC_ASSERT(VideoPortNumber == 0); if (!parseServerPortFromTransport(&response, &VideoPortNumber)) { diff --git a/src/Video.h b/src/Video.h index 68c5e3c..69ebe5a 100644 --- a/src/Video.h +++ b/src/Video.h @@ -36,4 +36,9 @@ typedef struct _RTP_PACKET { uint32_t ssrc; } RTP_PACKET, *PRTP_PACKET; +typedef struct _SS_PING { + char payload[16]; + uint32_t sequenceNumber; +} SS_PING, *PSS_PING; + #pragma pack(pop) diff --git a/src/VideoStream.c b/src/VideoStream.c index eac29a3..6d75069 100644 --- a/src/VideoStream.c +++ b/src/VideoStream.c @@ -43,7 +43,7 @@ void destroyVideoStream(void) { // UDP Ping proc static void VideoPingThreadProc(void* context) { - char pingData[] = { 0x50, 0x49, 0x4E, 0x47 }; + char legacyPingData[] = { 0x50, 0x49, 0x4E, 0x47 }; LC_SOCKADDR saddr; LC_ASSERT(VideoPortNumber != 0); @@ -51,12 +51,21 @@ static void VideoPingThreadProc(void* context) { memcpy(&saddr, &RemoteAddr, sizeof(saddr)); SET_PORT(&saddr, VideoPortNumber); + // We do not check for errors here. Socket errors will be handled + // on the read-side in ReceiveThreadProc(). This avoids potential + // issues related to receiving ICMP port unreachable messages due + // to sending a packet prior to the host PC binding to that port. + int pingCount = 0; while (!PltIsThreadInterrupted(&udpPingThread)) { - // We do not check for errors here. Socket errors will be handled - // on the read-side in ReceiveThreadProc(). This avoids potential - // issues related to receiving ICMP port unreachable messages due - // to sending a packet prior to the host PC binding to that port. - sendto(rtpSocket, pingData, sizeof(pingData), 0, (struct sockaddr*)&saddr, RemoteAddrLen); + if (VideoPingPayload.payload[0] != 0) { + pingCount++; + VideoPingPayload.sequenceNumber = BE32(pingCount); + + sendto(rtpSocket, (char*)&VideoPingPayload, sizeof(VideoPingPayload), 0, (struct sockaddr*)&saddr, RemoteAddrLen); + } + else { + sendto(rtpSocket, legacyPingData, sizeof(legacyPingData), 0, (struct sockaddr*)&saddr, RemoteAddrLen); + } PltSleepMsInterruptible(&udpPingThread, 500); }