Add protocol extension for multi-client-compatible ping support

This commit is contained in:
Cameron Gutman 2023-02-12 01:23:25 -06:00
commit dc186082a7
6 changed files with 54 additions and 19 deletions

View file

@ -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);

View file

@ -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] = {

View file

@ -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

View file

@ -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)) {

View file

@ -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)

View file

@ -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);
}