From 0fa1a02e0a7ac560eff6411b238394459549fd6c Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 1 Feb 2015 20:16:08 -0500 Subject: [PATCH] Add backwards compatibility for GFE 2.1.x --- limelight-common/Connection.c | 6 +- limelight-common/ControlStream.c | 94 ++++++++++++++++++++------- limelight-common/Limelight-internal.h | 7 +- limelight-common/Limelight.h | 2 +- limelight-common/RtspConnection.c | 15 ++++- limelight-common/SdpGenerator.c | 83 +++++++++++++++++++---- limelight-common/VideoStream.c | 33 ++++++++++ 7 files changed, 197 insertions(+), 43 deletions(-) diff --git a/limelight-common/Connection.c b/limelight-common/Connection.c index 6ce783f..f6e94c8 100644 --- a/limelight-common/Connection.c +++ b/limelight-common/Connection.c @@ -8,6 +8,8 @@ static CONNECTION_LISTENER_CALLBACKS originalCallbacks; // This is used for debug prints so it's not declared static PLATFORM_CALLBACKS platformCallbacks; +int serverMajorVersion; + static int alreadyTerminated; /* Connection stages */ @@ -148,8 +150,10 @@ void LiCompleteThreadStart(void) /* Starts the connection to the streaming machine */ int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks, PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, PPLATFORM_CALLBACKS plCallbacks, - void* renderContext, int drFlags) { + void* renderContext, int drFlags, int _serverMajorVersion) { int err; + + serverMajorVersion = _serverMajorVersion; memcpy(&originalCallbacks, clCallbacks, sizeof(originalCallbacks)); memcpy(&platformCallbacks, plCallbacks, sizeof(platformCallbacks)); diff --git a/limelight-common/ControlStream.c b/limelight-common/ControlStream.c index 2a1bc91..a115101 100644 --- a/limelight-common/ControlStream.c +++ b/limelight-common/ControlStream.c @@ -20,22 +20,59 @@ static PCONNECTION_LISTENER_CALLBACKS listenerCallbacks; static int lossCountSinceLastReport = 0; static long currentFrame = 0; -#define PTYPE_START_STREAM_A 0x0606 -#define PPAYLEN_START_STREAM_A 2 -static const char PPAYLOAD_START_STREAM_A[PPAYLEN_START_STREAM_A] = { 0, 0 }; +#define IDX_START_A 0 +#define IDX_START_B 1 +#define IDX_RESYNC 2 +#define IDX_LOSS_STATS 3 -#define PTYPE_START_STREAM_B 0x0609 -#define PPAYLEN_START_STREAM_B 1 -static const char PPAYLOAD_START_STREAM_B[PPAYLEN_START_STREAM_B] = { 0 }; +static const short packetTypesGen3[] = { + 0x140b, // Start A + 0x1410, // Start B + 0x1404, // Resync + 0x140c, // Loss Stats + 0x1417, // Frame Stats (unused) +}; +static const short packetTypesGen4[] = { + 0x0606, // Start A + 0x0609, // Start B + 0x0604, // Resync + 0x060a, // Loss Stats + 0x0611, // Frame Stats (unused) +}; -#define PTYPE_RESYNC 0x0604 -#define PPAYLEN_RESYNC 24 +static const char startAGen3[] = {0}; +static const int startBGen3[] = {0, 0, 0, 0xa}; -#define PTYPE_LOSS_STATS 0x060a -#define PPAYLEN_LOSS_STATS 32 +static const char startAGen4[] = {0, 0}; +static const char startBGen4[] = {0}; -#define PTYPE_FRAME_STATS 0x0611 -#define PPAYLEN_FRAME_STATS 64 +static const short payloadLengthsGen3[] = { + sizeof(startAGen3), // Start A + sizeof(startBGen3), // Start B + 24, // Resync + 32, // Loss Stats + 64, // Frame Stats +}; +static const short payloadLengthsGen4[] = { + sizeof(startAGen4), // Start A + sizeof(startBGen4), // Start B + 24, // Resync + 32, // Loss Stats + 64, // Frame Stats +}; + +static const char* preconstructedPayloadsGen3[] = { + startAGen3, + (char*)startBGen3 +}; +static const char* preconstructedPayloadsGen4[] = { + startAGen4, + startBGen4 +}; + +static short *packetTypes; +static short *payloadLengths; +static char **preconstructedPayloads; #define LOSS_REPORT_INTERVAL_MS 50 @@ -47,6 +84,17 @@ int initializeControlStream(IP_ADDRESS addr, PSTREAM_CONFIGURATION streamConfigP host = addr; listenerCallbacks = clCallbacks; + + if (serverMajorVersion == 3) { + packetTypes = (short*)packetTypesGen3; + payloadLengths = (short*)payloadLengthsGen3; + preconstructedPayloads = (char**)preconstructedPayloadsGen3; + } + else { + packetTypes = (short*)packetTypesGen4; + payloadLengths = (short*)payloadLengthsGen4; + preconstructedPayloads = (char**)preconstructedPayloadsGen4; + } return 0; } @@ -156,12 +204,12 @@ static int sendMessageAndDiscardReply(short ptype, short paylen, const void* pay } static void lossStatsThreadFunc(void* context) { - char lossStatsPayload[PPAYLEN_LOSS_STATS]; + char lossStatsPayload[payloadLengths[IDX_LOSS_STATS]]; BYTE_BUFFER byteBuffer; while (!PltIsThreadInterrupted(&lossStatsThread)) { // Construct the payload - BbInitializeWrappedBuffer(&byteBuffer, lossStatsPayload, 0, PPAYLEN_LOSS_STATS, BYTE_ORDER_LITTLE); + BbInitializeWrappedBuffer(&byteBuffer, lossStatsPayload, 0, payloadLengths[IDX_LOSS_STATS], BYTE_ORDER_LITTLE); BbPutInt(&byteBuffer, lossCountSinceLastReport); BbPutInt(&byteBuffer, LOSS_REPORT_INTERVAL_MS); BbPutInt(&byteBuffer, 1000); @@ -171,8 +219,8 @@ static void lossStatsThreadFunc(void* context) { BbPutInt(&byteBuffer, 0x14); // Send the message (and don't expect a response) - if (!sendMessageAndForget(PTYPE_LOSS_STATS, - PPAYLEN_LOSS_STATS, lossStatsPayload)) { + if (!sendMessageAndForget(packetTypes[IDX_LOSS_STATS], + payloadLengths[IDX_LOSS_STATS], lossStatsPayload)) { Limelog("Loss stats thread terminating #1\n"); listenerCallbacks->connectionTerminated(LastSocketError()); return; @@ -202,7 +250,7 @@ static void resyncThreadFunc(void* context) { PltClearEvent(&resyncEvent); // Send the resync request and read the response - if (!sendMessageAndDiscardReply(PTYPE_RESYNC, PPAYLEN_RESYNC, payload)) { + if (!sendMessageAndDiscardReply(packetTypes[IDX_RESYNC], payloadLengths[IDX_RESYNC], payload)) { Limelog("Resync thread terminating #1\n"); listenerCallbacks->connectionTerminated(LastSocketError()); return; @@ -242,16 +290,16 @@ int startControlStream(void) { enableNoDelay(ctlSock); // Send START A - if (!sendMessageAndDiscardReply(PTYPE_START_STREAM_A, - PPAYLEN_START_STREAM_A, - PPAYLOAD_START_STREAM_A)) { + if (!sendMessageAndDiscardReply(packetTypes[IDX_START_A], + payloadLengths[IDX_START_A], + preconstructedPayloads[IDX_START_A])) { return LastSocketError(); } // Send START B - if (!sendMessageAndDiscardReply(PTYPE_START_STREAM_B, - PPAYLEN_START_STREAM_B, - PPAYLOAD_START_STREAM_B)) { + if (!sendMessageAndDiscardReply(packetTypes[IDX_START_B], + payloadLengths[IDX_START_B], + preconstructedPayloads[IDX_START_B])) { return LastSocketError(); } diff --git a/limelight-common/Limelight-internal.h b/limelight-common/Limelight-internal.h index 3490ae1..dfa8466 100644 --- a/limelight-common/Limelight-internal.h +++ b/limelight-common/Limelight-internal.h @@ -6,11 +6,10 @@ #include "PlatformThreads.h" #include "Video.h" -/* GFE 2.2.2+ RTSP/SDP version code */ -#define RTSP_CLIENT_VERSION 11 -#define RTSP_CLIENT_VERSION_S "11" +extern int serverMajorVersion; -char* getSdpPayloadForStreamConfig(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress, int *length); +char* getSdpPayloadForStreamConfig(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress, + int rtspClientVersion, int *length); int initializeControlStream(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks); int startControlStream(void); diff --git a/limelight-common/Limelight.h b/limelight-common/Limelight.h index 9d499b5..e4f5aa3 100644 --- a/limelight-common/Limelight.h +++ b/limelight-common/Limelight.h @@ -103,7 +103,7 @@ typedef struct _PLATFORM_CALLBACKS { int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks, PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, PPLATFORM_CALLBACKS plCallbacks, - void* renderContext, int drFlags); + void* renderContext, int drFlags, int _serverMajorVersion); void LiStopConnection(void); const char* LiGetStageName(int stage); diff --git a/limelight-common/RtspConnection.c b/limelight-common/RtspConnection.c index e651e4e..5e6f720 100644 --- a/limelight-common/RtspConnection.c +++ b/limelight-common/RtspConnection.c @@ -10,6 +10,7 @@ static char rtspTargetUrl[256]; static char sessionIdString[16]; static int hasSessionId; static char responseBuffer[RTSP_MAX_RESP_SIZE]; +static int rtspClientVersion; /* Create RTSP Option */ static POPTION_ITEM createOptionItem(char* option, char* content) @@ -60,14 +61,16 @@ static int addOption(PRTSP_MESSAGE msg, char* option, char* content) static int initializeRtspRequest(PRTSP_MESSAGE msg, char* command, char* target) { char sequenceNumberStr[16]; + char clientVersionStr[16]; // FIXME: Hacked CSeq attribute due to RTSP parser bug createRtspRequest(msg, NULL, 0, command, target, "RTSP/1.0", 0, NULL, NULL, 0); sprintf(sequenceNumberStr, "%d", currentSeqNumber++); + sprintf(clientVersionStr, "%d", rtspClientVersion); if (!addOption(msg, "CSeq", sequenceNumberStr) || - !addOption(msg, "X-GS-ClientVersion", RTSP_CLIENT_VERSION_S)) { + !addOption(msg, "X-GS-ClientVersion", clientVersionStr)) { freeMessage(msg); return 0; } @@ -247,7 +250,8 @@ static int sendVideoAnnounce(PRTSP_MESSAGE response, PSTREAM_CONFIGURATION strea } memcpy(&sdpAddr, &remoteAddr, sizeof(remoteAddr)); - request.payload = getSdpPayloadForStreamConfig(streamConfig, sdpAddr, &payloadLength); + request.payload = getSdpPayloadForStreamConfig(streamConfig, sdpAddr, + rtspClientVersion, &payloadLength); if (request.payload == NULL) { goto FreeMessage; } @@ -278,6 +282,13 @@ int performRtspHandshake(IP_ADDRESS addr, PSTREAM_CONFIGURATION streamConfigPtr) sprintf(rtspTargetUrl, "rtsp://%s", inet_ntoa(inaddr)); currentSeqNumber = 1; hasSessionId = 0; + + if (serverMajorVersion == 3) { + rtspClientVersion = 10; + } + else { + rtspClientVersion = 11; + } { RTSP_MESSAGE response; diff --git a/limelight-common/SdpGenerator.c b/limelight-common/SdpGenerator.c index 04edb23..3bbcacd 100644 --- a/limelight-common/SdpGenerator.c +++ b/limelight-common/SdpGenerator.c @@ -88,6 +88,62 @@ static int addAttributeString(PSDP_OPTION *head, char* name, const char* payload return addAttributeBinary(head, name, payload, (int)strlen(payload)); } +static int addGen3Options(PSDP_OPTION *head, char* addrStr) { + int payloadInt; + int err = 0; + + err |= addAttributeString(head, "x-nv-general.serverAddress", addrStr); + + payloadInt = htonl(0x42774141); + err |= addAttributeBinary(head, + "x-nv-general.featureFlags", &payloadInt, sizeof(payloadInt)); + + + payloadInt = htonl(0x41514141); + err |= addAttributeBinary(head, + "x-nv-video[0].transferProtocol", &payloadInt, sizeof(payloadInt)); + err |= addAttributeBinary(head, + "x-nv-video[1].transferProtocol", &payloadInt, sizeof(payloadInt)); + err |= addAttributeBinary(head, + "x-nv-video[2].transferProtocol", &payloadInt, sizeof(payloadInt)); + err |= addAttributeBinary(head, + "x-nv-video[3].transferProtocol", &payloadInt, sizeof(payloadInt)); + + payloadInt = htonl(0x42414141); + err |= addAttributeBinary(head, + "x-nv-video[0].rateControlMode", &payloadInt, sizeof(payloadInt)); + payloadInt = htonl(0x42514141); + err |= addAttributeBinary(head, + "x-nv-video[1].rateControlMode", &payloadInt, sizeof(payloadInt)); + err |= addAttributeBinary(head, + "x-nv-video[2].rateControlMode", &payloadInt, sizeof(payloadInt)); + err |= addAttributeBinary(head, + "x-nv-video[3].rateControlMode", &payloadInt, sizeof(payloadInt)); + + err |= addAttributeString(head, "x-nv-vqos[0].bw.flags", "14083"); + + err |= addAttributeString(head, "x-nv-vqos[0].videoQosMaxConsecutiveDrops", "0"); + err |= addAttributeString(head, "x-nv-vqos[1].videoQosMaxConsecutiveDrops", "0"); + err |= addAttributeString(head, "x-nv-vqos[2].videoQosMaxConsecutiveDrops", "0"); + err |= addAttributeString(head, "x-nv-vqos[3].videoQosMaxConsecutiveDrops", "0"); + + return err; +} + +static int addGen4Options(PSDP_OPTION *head, char* addrStr) { + char payloadStr[92]; + int err = 0; + + sprintf(payloadStr, "rtsp://%s:48010", addrStr); + err |= addAttributeString(head, "x-nv-general.serverAddress", payloadStr); + + err |= addAttributeString(head, "x-nv-video[0].rateControlMode", "4"); + + err |= addAttributeString(head, "x-nv-vqos[0].bw.flags", "51"); + + return err; +} + static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress) { PSDP_OPTION optionHead; char payloadStr[92]; @@ -95,9 +151,6 @@ static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct optionHead = NULL; err = 0; - - sprintf(payloadStr, "rtsp://%s:48010", inet_ntoa(targetAddress)); - err |= addAttributeString(&optionHead, "x-nv-general.serverAddress", payloadStr); sprintf(payloadStr, "%d", streamConfig->width); err |= addAttributeString(&optionHead, "x-nv-video[0].clientViewportWd", payloadStr); @@ -121,9 +174,6 @@ static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct err |= addAttributeString(&optionHead, "x-nv-video[0].timeoutLengthMs", "7000"); err |= addAttributeString(&optionHead, "x-nv-video[0].framesWithInvalidRefThreshold", "0"); - // This flags value will mean that resolution won't change as bitrate falls - err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.flags", "51"); - // Lock the bitrate since we're not scaling resolution so the picture doesn't get too bad if (streamConfig->height >= 1080 && streamConfig->fps >= 60) { if (streamConfig->bitrate < 10000) { @@ -167,6 +217,13 @@ static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct // FIXME: Remote optimizations err |= addAttributeString(&optionHead, "x-nv-vqos[0].qosTrafficType", "5"); err |= addAttributeString(&optionHead, "x-nv-aqos.qosTrafficType", "4"); + + if (serverMajorVersion == 3) { + err |= addGen3Options(&optionHead, inet_ntoa(targetAddress)); + } + else { + err |= addGen4Options(&optionHead, inet_ntoa(targetAddress)); + } if (err == 0) { return optionHead; @@ -177,22 +234,24 @@ static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct } /* Populate the SDP header with required information */ -static int fillSdpHeader(char* buffer, struct in_addr targetAddress) { +static int fillSdpHeader(char* buffer, struct in_addr targetAddress, int rtspClientVersion) { return sprintf(buffer, "v=0\r\n" - "o=android 0 "RTSP_CLIENT_VERSION_S" IN IPv4 %s\r\n" - "s=NVIDIA Streaming Client\r\n", inet_ntoa(targetAddress)); + "o=android 0 %d IN IPv4 %s\r\n" + "s=NVIDIA Streaming Client\r\n", rtspClientVersion, inet_ntoa(targetAddress)); } /* Populate the SDP tail with required information */ static int fillSdpTail(char* buffer) { return sprintf(buffer, "t=0 0\r\n" - "m=video 47998 \r\n"); + "m=video %d \r\n", + serverMajorVersion < 4 ? 47996 : 47998); } /* Get the SDP attributes for the stream config */ -char* getSdpPayloadForStreamConfig(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress, int *length) { +char* getSdpPayloadForStreamConfig(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress, + int rtspClientVersion, int *length) { PSDP_OPTION attributeList; int offset; char* payload; @@ -209,7 +268,7 @@ char* getSdpPayloadForStreamConfig(PSTREAM_CONFIGURATION streamConfig, struct in return NULL; } - offset = fillSdpHeader(payload, targetAddress); + offset = fillSdpHeader(payload, targetAddress, rtspClientVersion); offset += fillSerializedAttributeList(&payload[offset], attributeList); offset += fillSdpTail(&payload[offset]); diff --git a/limelight-common/VideoStream.c b/limelight-common/VideoStream.c index 18f70fe..a3f0d4b 100644 --- a/limelight-common/VideoStream.c +++ b/limelight-common/VideoStream.c @@ -6,6 +6,7 @@ #define FIRST_FRAME_MAX 1500 #define RTP_PORT 47998 +#define FIRST_FRAME_PORT 47996 static DECODER_RENDERER_CALLBACKS callbacks; static STREAM_CONFIGURATION configuration; @@ -13,6 +14,7 @@ static IP_ADDRESS remoteHost; static PCONNECTION_LISTENER_CALLBACKS listenerCallbacks; static SOCKET rtpSocket = INVALID_SOCKET; +static SOCKET firstFrameSocket = INVALID_SOCKET; static PLT_THREAD udpPingThread; static PLT_THREAD receiveThread; @@ -108,6 +110,17 @@ static void DecoderThreadProc(void* context) { } } +/* Read the first frame of the video stream */ +int readFirstFrame(void) { + // All that matters is that we close this socket. + // This starts the flow of video on Gen 3 servers. + + closesocket(firstFrameSocket); + firstFrameSocket = INVALID_SOCKET; + + return 0; +} + /* Terminate the video stream */ void stopVideoStream(void) { callbacks.stop(); @@ -116,6 +129,10 @@ void stopVideoStream(void) { PltInterruptThread(&receiveThread); PltInterruptThread(&decoderThread); + if (firstFrameSocket != INVALID_SOCKET) { + closesocket(firstFrameSocket); + firstFrameSocket = INVALID_SOCKET; + } if (rtpSocket != INVALID_SOCKET) { closesocket(rtpSocket); rtpSocket = INVALID_SOCKET; @@ -156,12 +173,28 @@ int startVideoStream(void* rendererContext, int drFlags) { return err; } + if (serverMajorVersion == 3) { + // Connect this socket to open port 47998 for our ping thread + firstFrameSocket = connectTcpSocket(remoteHost, FIRST_FRAME_PORT); + if (firstFrameSocket == INVALID_SOCKET) { + return LastSocketError(); + } + } + // Start pinging before reading the first frame so GFE knows where // to send UDP data err = PltCreateThread(UdpPingThreadProc, NULL, &udpPingThread); if (err != 0) { return err; } + + if (serverMajorVersion == 3) { + // Read the first frame to start the flow of video + err = readFirstFrame(); + if (err != 0) { + return err; + } + } return 0; }