From fd247151ce67ff47114b3e0e6beea61ed65e66ad Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Fri, 19 Feb 2016 17:20:56 +0100 Subject: [PATCH] Add support for Generation 5 servers (GFE 2.10.2+) --- limelight-common/ControlStream.c | 59 ++++++++++++++---- limelight-common/InputStream.c | 19 +++++- limelight-common/Limelight-internal.h | 3 +- limelight-common/RtspConnection.c | 7 ++- limelight-common/SdpGenerator.c | 86 +++++++++++++++++---------- limelight-common/VideoDepacketizer.c | 13 +++- 6 files changed, 138 insertions(+), 49 deletions(-) diff --git a/limelight-common/ControlStream.c b/limelight-common/ControlStream.c index 50da217..7ad6a6a 100644 --- a/limelight-common/ControlStream.c +++ b/limelight-common/ControlStream.c @@ -21,7 +21,8 @@ static PLT_THREAD lossStatsThread; static PLT_THREAD invalidateRefFramesThread; static PLT_EVENT invalidateRefFramesEvent; static int lossCountSinceLastReport; -static long currentFrame; +static long lastGoodFrame; +static long lastSeenFrame; static int stopping; static int idrFrameRequired; @@ -49,6 +50,13 @@ static const short packetTypesGen4[] = { 0x060a, // Loss Stats 0x0611, // Frame Stats (unused) }; +static const short packetTypesGen5[] = { + 0x0305, // Start A + 0x0307, // Start B + 0x0301, // Invalidate reference frames + 0x0201, // Loss Stats + 0x0204, // Frame Stats (unused) +}; static const char startAGen3[] = { 0 }; static const int startBGen3[] = { 0, 0, 0, 0xa }; @@ -56,6 +64,9 @@ static const int startBGen3[] = { 0, 0, 0, 0xa }; static const char requestIdrFrameGen4[] = { 0, 0 }; static const char startBGen4[] = { 0 }; +static const char startAGen5[] = { 0, 0 }; +static const char startBGen5[] = { 0 }; + static const short payloadLengthsGen3[] = { sizeof(startAGen3), // Start A sizeof(startBGen3), // Start B @@ -70,6 +81,13 @@ static const short payloadLengthsGen4[] = { 32, // Loss Stats 64, // Frame Stats }; +static const short payloadLengthsGen5[] = { + sizeof(startAGen5), // Start A + sizeof(startBGen5), // Start B + 24, // Invalidate reference frames + 32, // Loss Stats + 80, // Frame Stats +}; static const char* preconstructedPayloadsGen3[] = { startAGen3, @@ -79,6 +97,10 @@ static const char* preconstructedPayloadsGen4[] = { requestIdrFrameGen4, startBGen4 }; +static const char* preconstructedPayloadsGen5[] = { + startAGen5, + startBGen5 +}; static short* packetTypes; static short* payloadLengths; @@ -97,14 +119,20 @@ int initializeControlStream(void) { payloadLengths = (short*)payloadLengthsGen3; preconstructedPayloads = (char**)preconstructedPayloadsGen3; } - else { + else if (ServerMajorVersion == 4) { packetTypes = (short*)packetTypesGen4; payloadLengths = (short*)payloadLengthsGen4; preconstructedPayloads = (char**)preconstructedPayloadsGen4; } + else { + packetTypes = (short*)packetTypesGen5; + payloadLengths = (short*)payloadLengthsGen5; + preconstructedPayloads = (char**)preconstructedPayloadsGen5; + } idrFrameRequired = 0; - currentFrame = 0; + lastGoodFrame = 0; + lastSeenFrame = 0; lossCountSinceLastReport = 0; return 0; @@ -173,8 +201,12 @@ void connectionDetectedFrameLoss(int startFrame, int endFrame) { } // When we receive a frame, update the number of our current frame -void connectionReceivedFrame(int frameIndex) { - currentFrame = frameIndex; +void connectionReceivedCompleteFrame(int frameIndex) { + lastGoodFrame = frameIndex; +} + +void connectionSawFrame(int frameIndex) { + lastSeenFrame = frameIndex; } // When we lose packets, update our packet loss count @@ -270,7 +302,7 @@ static void lossStatsThreadFunc(void* context) { BbPutInt(&byteBuffer, lossCountSinceLastReport); BbPutInt(&byteBuffer, LOSS_REPORT_INTERVAL_MS); BbPutInt(&byteBuffer, 1000); - BbPutLong(&byteBuffer, currentFrame); + BbPutLong(&byteBuffer, lastGoodFrame); BbPutInt(&byteBuffer, 0); BbPutInt(&byteBuffer, 0); BbPutInt(&byteBuffer, 0x14); @@ -297,10 +329,17 @@ static void lossStatsThreadFunc(void* context) { static void requestIdrFrame(void) { long long payload[3]; - if (ServerMajorVersion == 3) { + if (ServerMajorVersion != 4) { // Form the payload - payload[0] = 0; - payload[1] = 0xFFFFF; + if (lastSeenFrame < 0x20) { + payload[0] = 0; + payload[1] = 0x20; + } + else { + payload[0] = lastSeenFrame - 0x20; + payload[1] = lastSeenFrame; + } + payload[2] = 0; // Send the reference frame invalidation request and read the response @@ -454,4 +493,4 @@ int startControlStream(void) { } return 0; -} \ No newline at end of file +} diff --git a/limelight-common/InputStream.c b/limelight-common/InputStream.c index 3e8422a..ffbd01d 100644 --- a/limelight-common/InputStream.c +++ b/limelight-common/InputStream.c @@ -326,7 +326,11 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) { holder->packetLength = sizeof(NV_MOUSE_MOVE_PACKET); holder->packet.mouseMove.header.packetType = htonl(PACKET_TYPE_MOUSE_MOVE); - holder->packet.mouseMove.magic = htonl(MOUSE_MOVE_MAGIC); + holder->packet.mouseMove.magic = (MOUSE_MOVE_MAGIC); + // On Gen 5 servers, the header code is incremented by one + if (ServerMajorVersion >= 5) { + holder->packet.mouseMove.magic += 0x01000000; + } holder->packet.mouseMove.deltaX = htons(deltaX); holder->packet.mouseMove.deltaY = htons(deltaY); @@ -355,6 +359,9 @@ int LiSendMouseButtonEvent(char action, int button) { holder->packetLength = sizeof(NV_MOUSE_BUTTON_PACKET); holder->packet.mouseButton.header.packetType = htonl(PACKET_TYPE_MOUSE_BUTTON); holder->packet.mouseButton.action = action; + if (ServerMajorVersion >= 5) { + holder->packet.mouseButton.action++; + } holder->packet.mouseButton.button = htonl(button); err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry); @@ -432,6 +439,10 @@ static int sendControllerEventInternal(short controllerNumber, short buttonFlags holder->packetLength = sizeof(NV_MULTI_CONTROLLER_PACKET); holder->packet.multiController.header.packetType = htonl(PACKET_TYPE_MULTI_CONTROLLER); holder->packet.multiController.headerA = MC_HEADER_A; + // On Gen 5 servers, the header code is decremented by one + if (ServerMajorVersion >= 5) { + holder->packet.multiController.headerA--; + } holder->packet.multiController.headerB = MC_HEADER_B; holder->packet.multiController.controllerNumber = controllerNumber; holder->packet.multiController.midA = MC_ACTIVE_CONTROLLER_FLAGS; @@ -488,6 +499,10 @@ int LiSendScrollEvent(signed char scrollClicks) { holder->packetLength = sizeof(NV_SCROLL_PACKET); holder->packet.scroll.header.packetType = htonl(PACKET_TYPE_SCROLL); holder->packet.scroll.magicA = MAGIC_A; + // On Gen 5 servers, the header code is incremented by one + if (ServerMajorVersion >= 5) { + holder->packet.scroll.magicA++; + } holder->packet.scroll.zero1 = 0; holder->packet.scroll.zero2 = 0; holder->packet.scroll.scrollAmt1 = htons(scrollClicks * 120); @@ -500,4 +515,4 @@ int LiSendScrollEvent(signed char scrollClicks) { } return err; -} \ No newline at end of file +} diff --git a/limelight-common/Limelight-internal.h b/limelight-common/Limelight-internal.h index 0dc15b5..ddb7cce 100644 --- a/limelight-common/Limelight-internal.h +++ b/limelight-common/Limelight-internal.h @@ -29,7 +29,8 @@ void destroyControlStream(void); void requestIdrOnDemand(void); void connectionSinkTooSlow(int startFrame, int endFrame); void connectionDetectedFrameLoss(int startFrame, int endFrame); -void connectionReceivedFrame(int frameIndex); +void connectionReceivedCompleteFrame(int frameIndex); +void connectionSawFrame(int frameIndex); void connectionLostPackets(int lastReceivedPacket, int nextReceivedPacket); int performRtspHandshake(void); diff --git a/limelight-common/RtspConnection.c b/limelight-common/RtspConnection.c index fdf5728..a1fc670 100644 --- a/limelight-common/RtspConnection.c +++ b/limelight-common/RtspConnection.c @@ -299,9 +299,12 @@ int performRtspHandshake(void) { if (ServerMajorVersion == 3) { rtspClientVersion = 10; } - else { + else if (ServerMajorVersion == 4) { rtspClientVersion = 11; } + else { + rtspClientVersion = 12; + } { RTSP_MESSAGE response; @@ -440,4 +443,4 @@ int performRtspHandshake(void) { } return 0; -} \ No newline at end of file +} diff --git a/limelight-common/SdpGenerator.c b/limelight-common/SdpGenerator.c index 336f573..da4787a 100644 --- a/limelight-common/SdpGenerator.c +++ b/limelight-common/SdpGenerator.c @@ -139,8 +139,6 @@ static int addGen4Options(PSDP_OPTION* head, char* addrStr) { char payloadStr[92]; int err = 0; unsigned char slicesPerFrame; - int audioChannelCount; - int audioChannelMask; sprintf(payloadStr, "rtsp://%s:48010", addrStr); err |= addAttributeString(head, "x-nv-general.serverAddress", payloadStr); @@ -156,32 +154,25 @@ static int addGen4Options(PSDP_OPTION* head, char* addrStr) { sprintf(payloadStr, "%d", slicesPerFrame); err |= addAttributeString(head, "x-nv-video[0].videoEncoderSlicesPerFrame", payloadStr); - if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_51_SURROUND) { - audioChannelCount = CHANNEL_COUNT_51_SURROUND; - audioChannelMask = CHANNEL_MASK_51_SURROUND; - } - else { - audioChannelCount = CHANNEL_COUNT_STEREO; - audioChannelMask = CHANNEL_MASK_STEREO; - } + return err; +} - sprintf(payloadStr, "%d", audioChannelCount); - err |= addAttributeString(head, "x-nv-audio.surround.numChannels", payloadStr); - sprintf(payloadStr, "%d", audioChannelMask); - err |= addAttributeString(head, "x-nv-audio.surround.channelMask", payloadStr); - if (audioChannelCount > 2) { - err |= addAttributeString(head, "x-nv-audio.surround.enable", "1"); - } - else { - err |= addAttributeString(head, "x-nv-audio.surround.enable", "0"); - } +static int addGen5Options(PSDP_OPTION* head) { + int err = 0; + // We want to use the legacy TCP connections for control and input rather than the new UDP stuff + err |= addAttributeString(head, "x-nv-general.useReliableUdp", "0"); + err |= addAttributeString(head, "x-nv-ri.useControlChannel", "0"); + err |= addAttributeString(head, "x-nv-vqos[0].enableQec", "0"); + return err; } static PSDP_OPTION getAttributesList(char*urlSafeAddr) { PSDP_OPTION optionHead; char payloadStr[92]; + int audioChannelCount; + int audioChannelMask; int err; optionHead = NULL; @@ -200,20 +191,26 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) { err |= addAttributeString(&optionHead, "x-nv-video[0].rateControlMode", "4"); - if (StreamConfig.streamingRemotely) { - err |= addAttributeString(&optionHead, "x-nv-video[0].averageBitrate", "4"); - err |= addAttributeString(&optionHead, "x-nv-video[0].peakBitrate", "4"); - } - err |= addAttributeString(&optionHead, "x-nv-video[0].timeoutLengthMs", "7000"); err |= addAttributeString(&optionHead, "x-nv-video[0].framesWithInvalidRefThreshold", "0"); - // We don't support dynamic bitrate scaling properly (it tends to bounce between min and max and never - // settle on the optimal bitrate if it's somewhere in the middle), so we'll just latch the bitrate - // to the requested value. sprintf(payloadStr, "%d", StreamConfig.bitrate); - err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.minimumBitrate", payloadStr); - err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.maximumBitrate", payloadStr); + if (ServerMajorVersion >= 5) { + err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.minimumBitrateKbps", payloadStr); + err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.maximumBitrateKbps", payloadStr); + } + else { + if (StreamConfig.streamingRemotely) { + err |= addAttributeString(&optionHead, "x-nv-video[0].averageBitrate", "4"); + err |= addAttributeString(&optionHead, "x-nv-video[0].peakBitrate", "4"); + } + // We don't support dynamic bitrate scaling properly (it tends to bounce between min and max and never + // settle on the optimal bitrate if it's somewhere in the middle), so we'll just latch the bitrate + // to the requested value. + + err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.minimumBitrate", payloadStr); + err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.maximumBitrate", payloadStr); + } // Using FEC turns padding on which makes us have to take the slow path // in the depacketizer, not to mention exposing some ambiguous cases with @@ -235,9 +232,34 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) { if (ServerMajorVersion == 3) { err |= addGen3Options(&optionHead, urlSafeAddr); } - else { + else if (ServerMajorVersion == 4) { err |= addGen4Options(&optionHead, urlSafeAddr); } + else { + err |= addGen5Options(&optionHead); + } + + if (ServerMajorVersion >= 4) { + if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_51_SURROUND) { + audioChannelCount = CHANNEL_COUNT_51_SURROUND; + audioChannelMask = CHANNEL_MASK_51_SURROUND; + } + else { + audioChannelCount = CHANNEL_COUNT_STEREO; + audioChannelMask = CHANNEL_MASK_STEREO; + } + + sprintf(payloadStr, "%d", audioChannelCount); + err |= addAttributeString(&optionHead, "x-nv-audio.surround.numChannels", payloadStr); + sprintf(payloadStr, "%d", audioChannelMask); + err |= addAttributeString(&optionHead, "x-nv-audio.surround.channelMask", payloadStr); + if (audioChannelCount > 2) { + err |= addAttributeString(&optionHead, "x-nv-audio.surround.enable", "1"); + } + else { + err |= addAttributeString(&optionHead, "x-nv-audio.surround.enable", "0"); + } + } if (err == 0) { return optionHead; @@ -294,4 +316,4 @@ char* getSdpPayloadForStreamConfig(int rtspClientVersion, int* length) { freeAttributeList(attributeList); *length = offset; return payload; -} \ No newline at end of file +} diff --git a/limelight-common/VideoDepacketizer.c b/limelight-common/VideoDepacketizer.c index 0a383b0..5e95a91 100644 --- a/limelight-common/VideoDepacketizer.c +++ b/limelight-common/VideoDepacketizer.c @@ -231,7 +231,7 @@ static void reassembleAvcFrame(int frameNumber) { } // Notify the control connection - connectionReceivedFrame(frameNumber); + connectionReceivedCompleteFrame(frameNumber); // Clear frame drops consecutiveFrameDrops = 0; @@ -392,6 +392,9 @@ void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length) { return; } + // Notify the listener of the latest frame we've seen from the PC + connectionSawFrame(frameIndex); + // Look for a frame start before receiving a frame end if (firstPacket && decodingFrame) { @@ -469,6 +472,12 @@ void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length) { } lastPacketInStream = streamPacketIndex; + // If this is the first packet, skip the frame header (if one exists) + if (firstPacket && ServerMajorVersion >= 5) { + currentPos.offset += 8; + currentPos.length -= 8; + } + if (firstPacket && getSpecialSeq(¤tPos, &specialSeq) && isSeqFrameStart(&specialSeq) && @@ -519,4 +528,4 @@ void queueRtpPacket(PRTP_PACKET rtpPacket, int length) { } processRtpPayload((PNV_VIDEO_PACKET)(((char*)rtpPacket) + dataOffset), length - dataOffset); -} \ No newline at end of file +}