diff --git a/limelight-common/ByteBuffer.c b/limelight-common/ByteBuffer.c index cf0e6f7..edaf325 100644 --- a/limelight-common/ByteBuffer.c +++ b/limelight-common/ByteBuffer.c @@ -8,6 +8,15 @@ void BbInitializeWrappedBuffer(PBYTE_BUFFER buff, char* data, int offset, int le buff->byteOrder = byteOrder; } +static long long byteSwapLongLong(PBYTE_BUFFER buff, long long l) { + if (buff->byteOrder == BYTE_ORDER_BIG) { + return HTONLL(l); + } + else { + return l; + } +} + static int byteSwapInt(PBYTE_BUFFER buff, int i) { if (buff->byteOrder == BYTE_ORDER_BIG) { return htonl(i); @@ -63,6 +72,19 @@ int BbGetInt(PBYTE_BUFFER buff, int *i) { return 1; } +int BbGetLong(PBYTE_BUFFER buff, long long *l) { + if (buff->position + sizeof(*l) > buff->length) { + return 0; + } + + memcpy(l, &buff->buffer[buff->position], sizeof(*l)); + buff->position += sizeof(*l); + + *l = byteSwapInt(buff, *l); + + return 1; +} + int BbPutInt(PBYTE_BUFFER buff, int i) { if (buff->position + sizeof(i) > buff->length) { return 0; @@ -76,6 +98,19 @@ int BbPutInt(PBYTE_BUFFER buff, int i) { return 1; } +int BbPutLong(PBYTE_BUFFER buff, long long l) { + if (buff->position + sizeof(l) > buff->length) { + return 0; + } + + l = byteSwapInt(buff, l); + + memcpy(&buff->buffer[buff->position], &l, sizeof(l)); + buff->position += sizeof(l); + + return 1; +} + int BbPutShort(PBYTE_BUFFER buff, short s) { if (buff->position + sizeof(s) > buff->length) { return 0; diff --git a/limelight-common/ByteBuffer.h b/limelight-common/ByteBuffer.h index 522ed4a..8f13150 100644 --- a/limelight-common/ByteBuffer.h +++ b/limelight-common/ByteBuffer.h @@ -5,6 +5,16 @@ #define BYTE_ORDER_LITTLE 1 #define BYTE_ORDER_BIG 2 +#define HTONLL(x) \ + ((((x) & 0xff00000000000000ull) >> 56) \ + | (((x) & 0x00ff000000000000ull) >> 40) \ + | (((x) & 0x0000ff0000000000ull) >> 24) \ + | (((x) & 0x000000ff00000000ull) >> 8) \ + | (((x) & 0x00000000ff000000ull) << 8) \ + | (((x) & 0x0000000000ff0000ull) << 24) \ + | (((x) & 0x000000000000ff00ull) << 40) \ + | (((x) & 0x00000000000000ffull) << 56)) + typedef struct _BYTE_BUFFER { char* buffer; unsigned int offset; @@ -18,7 +28,9 @@ void BbInitializeWrappedBuffer(PBYTE_BUFFER buff, char* data, int offset, int le int BbGet(PBYTE_BUFFER buff, char *c); int BbGetShort(PBYTE_BUFFER buff, short *s); int BbGetInt(PBYTE_BUFFER buff, int *i); +int BbGetLong(PBYTE_BUFFER buff, long long *l); int BbPutInt(PBYTE_BUFFER buff, int i); int BbPutShort(PBYTE_BUFFER buff, short s); -int BbPut(PBYTE_BUFFER buff, char c); \ No newline at end of file +int BbPut(PBYTE_BUFFER buff, char c); +int BbPutLong(PBYTE_BUFFER buff, long long l); diff --git a/limelight-common/Config.c b/limelight-common/Config.c deleted file mode 100644 index 9220bc5..0000000 --- a/limelight-common/Config.c +++ /dev/null @@ -1,135 +0,0 @@ -#include "Limelight-internal.h" - -#include "ByteBuffer.h" - -static const int UNKNOWN_CONFIG[] = { - 70151, - 68291329, - 1280, - 68291584, - 1280, - 68291840, - 15360, - 68292096, - 25600, - 68292352, - 2048, - 68292608, - 1024, - 68289024, - 262144, - 17957632, - 302055424, - 134217729, - 16777490, - 70153, - 68293120, - 768000, - 17961216, - 303235072, - 335609857, - 838861842, - 352321536, - 1006634002, - 369098752, - 335545362, - 385875968, - 1042, - 402653184, - 134218770, - 419430400, - 167773202, - 436207616, - 855638290, - 266779, - 10000, - 266780, - 2000, - 266781, - 50, - 266782, - 3000, - 266783, - 2, - 266794, - 5000, - 266795, - 500, - 266784, - 75, - 266785, - 25, - 266786, - 10, - 266787, - 60, - 266788, - 30, - 266789, - 3, - 266790, - 1000, - 266791, - 5000, - 266792, - 5000, - 266793, - 5000, - 70190, - 68301063, - 10240, - 68301312, - 6400, - 68301568, - 768000, - 68299776, - 768, - 68300032, - 2560, - 68300544, - 0, - 34746368, - (int) 0xFE000000 -}; - -static const int CONFIG_SIZE = sizeof(UNKNOWN_CONFIG) +(8 * 4) + 3; - -int getConfigDataSize(PSTREAM_CONFIGURATION streamConfig) { - return CONFIG_SIZE; -} - -char* allocateConfigDataForStreamConfig(PSTREAM_CONFIGURATION streamConfig) { - BYTE_BUFFER bb; - int i; - char* config = (char *)malloc(CONFIG_SIZE); - if (config == NULL) { - return NULL; - } - - BbInitializeWrappedBuffer(&bb, config, 0, CONFIG_SIZE, BYTE_ORDER_LITTLE); - - BbPutShort(&bb, 0x1204); - BbPutShort(&bb, 0x0004); - BbPutInt(&bb, streamConfig->width); - - BbPutShort(&bb, 0x1205); - BbPutShort(&bb, 0x0004); - BbPutInt(&bb, streamConfig->height); - - BbPutShort(&bb, 0x1206); - BbPutShort(&bb, 0x0004); - BbPutInt(&bb, 1); - - BbPutShort(&bb, 0x120A); - BbPutShort(&bb, 0x0004); - BbPutInt(&bb, streamConfig->fps); - - for (i = 0; i < sizeof(UNKNOWN_CONFIG) / sizeof(int); i++) { - BbPutInt(&bb, UNKNOWN_CONFIG[i]); - } - - BbPutShort(&bb, 0x0013); - BbPut(&bb, 0x00); - - return config; -} diff --git a/limelight-common/ControlStream.c b/limelight-common/ControlStream.c index 9167229..1060fdb 100644 --- a/limelight-common/ControlStream.c +++ b/limelight-common/ControlStream.c @@ -2,6 +2,8 @@ #include "PlatformSockets.h" #include "PlatformThreads.h" +#include "ByteBuffer.h" + typedef struct _NVCTL_PACKET_HEADER { unsigned short type; unsigned short payloadLength; @@ -10,26 +12,31 @@ typedef struct _NVCTL_PACKET_HEADER { static IP_ADDRESS host; static SOCKET ctlSock = INVALID_SOCKET; static STREAM_CONFIGURATION streamConfig; -static PLT_THREAD heartbeatThread; -static PLT_THREAD jitterThread; +static PLT_THREAD lossStatsThread; static PLT_THREAD resyncThread; static PLT_EVENT resyncEvent; static PCONNECTION_LISTENER_CALLBACKS listenerCallbacks; +static int lossCountSinceLastReport = 0; +static long currentFrame = 0; -static const short PTYPE_KEEPALIVE = 0x13ff; -static const short PPAYLEN_KEEPALIVE = 0x0000; +#define PTYPE_START_STREAM_A 0x140b +#define PPAYLEN_START_STREAM_A 1 +static const char PPAYLOAD_START_STREAM_A[1] = { 0 }; -static const short PTYPE_HEARTBEAT = 0x1401; -static const short PPAYLEN_HEARTBEAT = 0x0000; +#define PTYPE_START_STREAM_B 0x1410 +#define PPAYLEN_START_STREAM_B 16 +static const int PPAYLOAD_START_STREAM_B[4] = { 0, 0, 0, 0xa }; // FIXME: Little endian -static const short PTYPE_1405 = 0x1405; -static const short PPAYLEN_1405 = 0x0000; +#define PTYPE_RESYNC 0x1404 +#define PPAYLEN_RESYNC 24 -static const short PTYPE_RESYNC = 0x1404; -static const short PPAYLEN_RESYNC = 16; +#define PTYPE_LOSS_STATS 0x140c +#define PPAYLEN_LOSS_STATS 20 -static const short PTYPE_JITTER = 0x140c; -static const short PPAYLEN_JITTER = 0x10; +#define PTYPE_FRAME_STATS 0x1417 +#define PPAYLEN_FRAME_STATS 64 + +#define LOSS_REPORT_INTERVAL_MS 50 int initializeControlStream(IP_ADDRESS addr, PSTREAM_CONFIGURATION streamConfigPtr, PCONNECTION_LISTENER_CALLBACKS clCallbacks) { memcpy(&streamConfig, streamConfigPtr, sizeof(*streamConfigPtr)); @@ -56,6 +63,14 @@ void connectionDetectedFrameLoss(int startFrame, int endFrame) { PltSetEvent(&resyncEvent); } +void connectionReceivedFrame(int frameIndex) { + currentFrame = frameIndex; +} + +void connectionLostPackets(int lastReceivedPacket, int nextReceivedPacket) { + lossCountSinceLastReport += (nextReceivedPacket - lastReceivedPacket) - 1; +} + static PNVCTL_PACKET_HEADER readNvctlPacket(void) { NVCTL_PACKET_HEADER staticHeader; PNVCTL_PACKET_HEADER fullPacket; @@ -83,7 +98,7 @@ static PNVCTL_PACKET_HEADER readNvctlPacket(void) { return fullPacket; } -static PNVCTL_PACKET_HEADER sendNoPayloadAndReceive(short ptype, short paylen) { +static int sendMessageAndForget(short ptype, short paylen, const void* payload) { NVCTL_PACKET_HEADER header; int err; @@ -91,105 +106,86 @@ static PNVCTL_PACKET_HEADER sendNoPayloadAndReceive(short ptype, short paylen) { header.payloadLength = paylen; err = send(ctlSock, (char*) &header, sizeof(header), 0); if (err != sizeof(header)) { + return 0; + } + + if (payload != NULL) { + err = send(ctlSock, payload, paylen, 0); + if (err != paylen) { + return 0; + } + } + + return 1; +} + +static PNVCTL_PACKET_HEADER sendMessage(short ptype, short paylen, const void* payload) { + int success; + + success = sendMessageAndForget(ptype, paylen, payload); + if (!success) { return NULL; } return readNvctlPacket(); } -static void heartbeatThreadFunc(void* context) { - int err; - NVCTL_PACKET_HEADER header; +static void lossStatsThreadFunc(void* context) { + char lossStatsPayload[PPAYLEN_LOSS_STATS]; + BYTE_BUFFER byteBuffer; - header.type = PTYPE_HEARTBEAT; - header.payloadLength = PPAYLEN_HEARTBEAT; - while (!PltIsThreadInterrupted(&heartbeatThread)) { - err = send(ctlSock, (char*) &header, sizeof(header), 0); - if (err != sizeof(header)) { - Limelog("Heartbeat thread terminating #1\n"); - listenerCallbacks->connectionTerminated(err); + while (!PltIsThreadInterrupted(&lossStatsThread)) { + // Construct the payload + BbInitializeWrappedBuffer(&byteBuffer, lossStatsPayload, 0, PPAYLEN_LOSS_STATS, BYTE_ORDER_LITTLE); + BbPutInt(&byteBuffer, lossCountSinceLastReport); + BbPutInt(&byteBuffer, LOSS_REPORT_INTERVAL_MS); + BbPutInt(&byteBuffer, 1000); + BbPutLong(&byteBuffer, currentFrame); + + // Send the message (and don't expect a response) + if (!sendMessageAndForget(PTYPE_LOSS_STATS, + PPAYLEN_LOSS_STATS, lossStatsPayload)) { + Limelog("Loss stats thread terminating #1\n"); return; } - PltSleepMs(3000); - } -} + // Clear the transient state + lossCountSinceLastReport = 0; -static void jitterThreadFunc(void* context) { - int payload[4]; - NVCTL_PACKET_HEADER header; - int err; - - header.type = PTYPE_JITTER; - header.payloadLength = PPAYLEN_JITTER; - while (!PltIsThreadInterrupted(&jitterThread)) { - err = send(ctlSock, (char*) &header, sizeof(header), 0); - if (err != sizeof(header)) { - Limelog("Jitter thread terminating #1\n"); - listenerCallbacks->connectionTerminated(err); - return; - } - - payload[0] = 0; - payload[1] = 77; - payload[2] = 888; - payload[3] = 0; // FIXME: Sequence number? - - err = send(ctlSock, (char*) payload, sizeof(payload), 0); - if (err != sizeof(payload)) { - Limelog("Jitter thread terminating #2\n"); - listenerCallbacks->connectionTerminated(err); - return; - } - - PltSleepMs(100); + // Wait a bit + PltSleepMs(LOSS_REPORT_INTERVAL_MS); } } static void resyncThreadFunc(void* context) { - long long payload[2]; - NVCTL_PACKET_HEADER header; + long long payload[3]; PNVCTL_PACKET_HEADER response; - int err; - header.type = PTYPE_RESYNC; - header.payloadLength = PPAYLEN_RESYNC; while (!PltIsThreadInterrupted(&resyncThread)) { + // Wait for a resync request PltWaitForEvent(&resyncEvent); - err = send(ctlSock, (char*) &header, sizeof(header), 0); - if (err != sizeof(header)) { - Limelog("Resync thread terminating #1\n"); - listenerCallbacks->connectionTerminated(err); - return; - } - + // Form the payload payload[0] = 0; payload[1] = 0xFFFFF; + payload[2] = 0; - Limelog("Sending resync packet\n"); - err = send(ctlSock, (char*) payload, sizeof(payload), 0); - if (err != sizeof(payload)) { - Limelog("Resync thread terminating #2\n"); - listenerCallbacks->connectionTerminated(err); - return; - } + // Done capturing the parameters + PltClearEvent(&resyncEvent); - response = readNvctlPacket(); + // Send the resync request and read the response + response = sendMessage(PTYPE_RESYNC, PPAYLEN_RESYNC, payload); if (response == NULL) { - Limelog("Resync thread terminating #3\n"); + Limelog("Resync thread terminating #1\n"); listenerCallbacks->connectionTerminated(LastSocketError()); return; } Limelog("Resync complete\n"); - - PltClearEvent(&resyncEvent); } } int stopControlStream(void) { - PltInterruptThread(&heartbeatThread); - PltInterruptThread(&jitterThread); + PltInterruptThread(&lossStatsThread); PltInterruptThread(&resyncThread); if (ctlSock != INVALID_SOCKET) { @@ -197,12 +193,10 @@ int stopControlStream(void) { ctlSock = INVALID_SOCKET; } - PltJoinThread(&heartbeatThread); - PltJoinThread(&jitterThread); + PltJoinThread(&lossStatsThread); PltJoinThread(&resyncThread); - PltCloseThread(&heartbeatThread); - PltCloseThread(&jitterThread); + PltCloseThread(&lossStatsThread); PltCloseThread(&resyncThread); return 0; @@ -210,8 +204,6 @@ int stopControlStream(void) { int startControlStream(void) { int err; - char* config; - int configSize; PNVCTL_PACKET_HEADER response; ctlSock = connectTcpSocket(host, 47995); @@ -221,39 +213,21 @@ int startControlStream(void) { enableNoDelay(ctlSock); - configSize = getConfigDataSize(&streamConfig); - config = allocateConfigDataForStreamConfig(&streamConfig); - if (config == NULL) { - return -1; - } - - // Send config - err = send(ctlSock, config, configSize, 0); - free(config); - if (err != configSize) { - return LastSocketError(); - } - - // Ping pong - response = sendNoPayloadAndReceive(PTYPE_KEEPALIVE, PPAYLEN_KEEPALIVE); + // Send START A + response = sendMessage(PTYPE_START_STREAM_A, + PPAYLEN_START_STREAM_A, PPAYLOAD_START_STREAM_A); if (response == NULL) { return LastSocketError(); } - free(response); - // 1405 - response = sendNoPayloadAndReceive(PTYPE_1405, PPAYLEN_1405); + // Send START B + response = sendMessage(PTYPE_START_STREAM_B, + PPAYLEN_START_STREAM_B, PPAYLOAD_START_STREAM_B); if (response == NULL) { return LastSocketError(); } - free(response); - err = PltCreateThread(heartbeatThreadFunc, NULL, &heartbeatThread); - if (err != 0) { - return err; - } - - err = PltCreateThread(jitterThreadFunc, NULL, &jitterThread); + err = PltCreateThread(lossStatsThreadFunc, NULL, &lossStatsThread); if (err != 0) { return err; } diff --git a/limelight-common/Handshake.c b/limelight-common/Handshake.c deleted file mode 100644 index cbddb79..0000000 --- a/limelight-common/Handshake.c +++ /dev/null @@ -1,97 +0,0 @@ -#include "Limelight.h" -#include "PlatformSockets.h" -#include "PlatformThreads.h" - -static const char HELLO [] = { - 0x07, 0x00, 0x00, 0x00, - 0x61, 0x6e, 0x64, 0x72, - 0x6f, 0x69, 0x64, 0x03, - 0x01, 0x00, 0x00 -}; - -static const char PACKET2 [] = { - 0x01, 0x03, 0x02, 0x00, - 0x08, 0x00 -}; - -static const char PACKET3 [] = { - 0x04, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 -}; - -static const char PACKET4 [] = { - 0x01, 0x01, 0x00, 0x0 -}; - -static SOCKET sock = INVALID_SOCKET; - -static int waitAndDiscardResponse(SOCKET sock) { - char temp[256]; - return recv(sock, temp, sizeof(temp), 0); -} - -void terminateHandshake(void) { - if (sock != INVALID_SOCKET) { - closesocket(sock); - sock = INVALID_SOCKET; - } -} - -#include - -int performHandshake(IP_ADDRESS host) { - int err; - - sock = connectTcpSocket(host, 47991); - if (sock == INVALID_SOCKET) { - return LastSocketError(); - } - - enableNoDelay(sock); - - err = send(sock, HELLO, sizeof(HELLO), 0); - if (err == SOCKET_ERROR) { - goto CleanupError; - } - - err = waitAndDiscardResponse(sock); - if (err == SOCKET_ERROR) { - goto CleanupError; - } - - err = send(sock, PACKET2, sizeof(PACKET2), 0); - if (err == SOCKET_ERROR) { - goto CleanupError; - } - - err = waitAndDiscardResponse(sock); - if (err == SOCKET_ERROR) { - goto CleanupError; - } - - err = send(sock, PACKET3, sizeof(PACKET3), 0); - if (err == SOCKET_ERROR) { - goto CleanupError; - } - - err = waitAndDiscardResponse(sock); - if (err == SOCKET_ERROR) { - goto CleanupError; - } - - err = send(sock, PACKET4, sizeof(PACKET4), 0); - if (err == SOCKET_ERROR) { - goto CleanupError; - } - - closesocket(sock); - sock = INVALID_SOCKET; - - return 0; - -CleanupError: - closesocket(sock); - sock = INVALID_SOCKET; - - return LastSocketError(); -} \ No newline at end of file diff --git a/limelight-common/Limelight-internal.h b/limelight-common/Limelight-internal.h index 33a2150..3f9cf81 100644 --- a/limelight-common/Limelight-internal.h +++ b/limelight-common/Limelight-internal.h @@ -6,18 +6,14 @@ #include "PlatformThreads.h" #include "Video.h" -char* allocateConfigDataForStreamConfig(PSTREAM_CONFIGURATION streamConfig); -int getConfigDataSize(PSTREAM_CONFIGURATION streamConfig); - int initializeControlStream(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks); int startControlStream(void); int stopControlStream(void); void destroyControlStream(void); void connectionSinkTooSlow(int startFrame, int endFrame); void connectionDetectedFrameLoss(int startFrame, int endFrame); - -int performHandshake(IP_ADDRESS host); -void terminateHandshake(void); +void connectionReceivedFrame(int frameIndex); +void connectionLostPackets(int lastReceivedPacket, int nextReceivedPacket); void initializeVideoDepacketizer(void); void destroyVideoDepacketizer(void); diff --git a/limelight-common/Video.h b/limelight-common/Video.h index d42a34d..ad9ddc2 100644 --- a/limelight-common/Video.h +++ b/limelight-common/Video.h @@ -9,7 +9,8 @@ typedef struct _NV_VIDEO_PACKET { int totalPackets; int flags; int payloadLength; - char reserved2[36]; + int streamPacketIndex; + char reserved2[32]; } NV_VIDEO_PACKET, *PNV_VIDEO_PACKET; typedef struct _RTP_PACKET { diff --git a/limelight-common/VideoDepacketizer.c b/limelight-common/VideoDepacketizer.c index 9d74a06..ccd16dc 100644 --- a/limelight-common/VideoDepacketizer.c +++ b/limelight-common/VideoDepacketizer.c @@ -12,6 +12,7 @@ static int nextPacketNumber; static int startFrameNumber = 1; static int waitingForNextSuccessfulFrame; static int gotNextFrameStart; +static int lastPacketInStream = 0; static LINKED_BLOCKING_QUEUE decodeUnitQueue; @@ -124,6 +125,9 @@ static void reassembleFrame(int frameNumber) { // FIXME: Get proper lower bound connectionSinkTooSlow(0, frameNumber); } + + // Notify the control connection + connectionReceivedFrame(frameNumber); } } } @@ -332,6 +336,13 @@ void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length) { } } + int streamPacketIndex = videoPacket->streamPacketIndex; + if (streamPacketIndex != (int) (lastPacketInStream + 1)) { + // Packets were lost so report this to the server + connectionLostPackets(lastPacketInStream, streamPacketIndex); + } + lastPacketInStream = streamPacketIndex; + nextPacketNumber++; // Remove extra padding diff --git a/limelight-common/limelight-common.vcxproj b/limelight-common/limelight-common.vcxproj index aff9254..c58bb07 100644 --- a/limelight-common/limelight-common.vcxproj +++ b/limelight-common/limelight-common.vcxproj @@ -129,10 +129,8 @@ - - diff --git a/limelight-common/limelight-common.vcxproj.filters b/limelight-common/limelight-common.vcxproj.filters index dc1a9bc..3620481 100644 --- a/limelight-common/limelight-common.vcxproj.filters +++ b/limelight-common/limelight-common.vcxproj.filters @@ -18,15 +18,9 @@ Source Files - - Source Files - Source Files - - Source Files - Source Files