Reorganize the folder structure and delete the Xcode build files

This commit is contained in:
Cameron Gutman
2016-03-31 07:22:03 -04:00
parent b7c7c98c45
commit 48a5d63045
39 changed files with 0 additions and 484 deletions

310
src/AudioStream.c Normal file
View File

@@ -0,0 +1,310 @@
#include "Limelight-internal.h"
#include "PlatformSockets.h"
#include "PlatformThreads.h"
#include "LinkedBlockingQueue.h"
#include "RtpReorderQueue.h"
static SOCKET rtpSocket = INVALID_SOCKET;
static LINKED_BLOCKING_QUEUE packetQueue;
static RTP_REORDER_QUEUE rtpReorderQueue;
static PLT_THREAD udpPingThread;
static PLT_THREAD receiveThread;
static PLT_THREAD decoderThread;
static unsigned short lastSeq;
#define RTP_PORT 48000
#define MAX_PACKET_SIZE 250
// This is much larger than we should typically have buffered, but
// it needs to be. We need a cushion in case our thread gets blocked
// for longer than normal.
#define RTP_RECV_BUFFER (64 * MAX_PACKET_SIZE)
#define SAMPLE_RATE 48000
static OPUS_MULTISTREAM_CONFIGURATION opusStereoConfig = {
.sampleRate = SAMPLE_RATE,
.channelCount = 2,
.streams = 1,
.coupledStreams = 1,
.mapping = {0, 1}
};
static OPUS_MULTISTREAM_CONFIGURATION opus51SurroundConfig = {
.sampleRate = SAMPLE_RATE,
.channelCount = 6,
.streams = 4,
.coupledStreams = 2,
.mapping = {0, 4, 1, 5, 2, 3}
};
static POPUS_MULTISTREAM_CONFIGURATION opusConfigArray[] = {
&opusStereoConfig,
&opus51SurroundConfig,
};
typedef struct _QUEUED_AUDIO_PACKET {
// data must remain at the front
char data[MAX_PACKET_SIZE];
int size;
union {
RTP_QUEUE_ENTRY rentry;
LINKED_BLOCKING_QUEUE_ENTRY lentry;
} q;
} QUEUED_AUDIO_PACKET, *PQUEUED_AUDIO_PACKET;
// Initialize the audio stream
void initializeAudioStream(void) {
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
LbqInitializeLinkedBlockingQueue(&packetQueue, 30);
}
RtpqInitializeQueue(&rtpReorderQueue, RTPQ_DEFAULT_MAX_SIZE, RTPQ_DEFAULT_QUEUE_TIME);
lastSeq = 0;
}
static void freePacketList(PLINKED_BLOCKING_QUEUE_ENTRY entry) {
PLINKED_BLOCKING_QUEUE_ENTRY nextEntry;
while (entry != NULL) {
nextEntry = entry->flink;
// The entry is stored within the data allocation
free(entry->data);
entry = nextEntry;
}
}
// Tear down the audio stream once we're done with it
void destroyAudioStream(void) {
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
freePacketList(LbqDestroyLinkedBlockingQueue(&packetQueue));
}
RtpqCleanupQueue(&rtpReorderQueue);
}
static void UdpPingThreadProc(void* context) {
// Ping in ASCII
char pingData[] = { 0x50, 0x49, 0x4E, 0x47 };
struct sockaddr_in6 saddr;
SOCK_RET err;
memcpy(&saddr, &RemoteAddr, sizeof(saddr));
saddr.sin6_port = htons(RTP_PORT);
// Send PING every 500 milliseconds
while (!PltIsThreadInterrupted(&udpPingThread)) {
err = sendto(rtpSocket, pingData, sizeof(pingData), 0, (struct sockaddr*)&saddr, RemoteAddrLen);
if (err != sizeof(pingData)) {
Limelog("Audio Ping: sendto() failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketError());
return;
}
PltSleepMs(500);
}
}
static int queuePacketToLbq(PQUEUED_AUDIO_PACKET* packet) {
int err;
err = LbqOfferQueueItem(&packetQueue, *packet, &(*packet)->q.lentry);
if (err == LBQ_SUCCESS) {
// The LBQ owns the buffer now
*packet = NULL;
}
else if (err == LBQ_BOUND_EXCEEDED) {
Limelog("Audio packet queue overflow\n");
freePacketList(LbqFlushQueueItems(&packetQueue));
}
else if (err == LBQ_INTERRUPTED) {
return 0;
}
return 1;
}
static void decodeInputData(PQUEUED_AUDIO_PACKET packet) {
PRTP_PACKET rtp;
rtp = (PRTP_PACKET)&packet->data[0];
if (lastSeq != 0 && (unsigned short)(lastSeq + 1) != rtp->sequenceNumber) {
Limelog("Received OOS audio data (expected %d, but got %d)\n", lastSeq + 1, rtp->sequenceNumber);
AudioCallbacks.decodeAndPlaySample(NULL, 0);
}
lastSeq = rtp->sequenceNumber;
AudioCallbacks.decodeAndPlaySample((char*)(rtp + 1), packet->size - sizeof(*rtp));
}
static void ReceiveThreadProc(void* context) {
PRTP_PACKET rtp;
PQUEUED_AUDIO_PACKET packet;
int queueStatus;
packet = NULL;
while (!PltIsThreadInterrupted(&receiveThread)) {
if (packet == NULL) {
packet = (PQUEUED_AUDIO_PACKET)malloc(sizeof(*packet));
if (packet == NULL) {
Limelog("Audio Receive: malloc() failed\n");
ListenerCallbacks.connectionTerminated(-1);
break;
}
}
packet->size = recvUdpSocket(rtpSocket, &packet->data[0], MAX_PACKET_SIZE);
if (packet->size < 0) {
Limelog("Audio Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketError());
break;
}
else if (packet->size == 0) {
// Receive timed out; try again
continue;
}
if (packet->size < sizeof(RTP_PACKET)) {
// Runt packet
continue;
}
rtp = (PRTP_PACKET)&packet->data[0];
if (rtp->packetType != 97) {
// Not audio
continue;
}
// RTP sequence number must be in host order for the RTP queue
rtp->sequenceNumber = htons(rtp->sequenceNumber);
queueStatus = RtpqAddPacket(&rtpReorderQueue, (PRTP_PACKET)packet, &packet->q.rentry);
if (queueStatus == RTPQ_RET_HANDLE_IMMEDIATELY) {
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
if (!queuePacketToLbq(&packet)) {
// An exit signal was received
break;
}
}
else {
decodeInputData(packet);
}
}
else {
if (queueStatus != RTPQ_RET_REJECTED) {
// The queue consumed our packet, so we must allocate a new one
packet = NULL;
}
if (queueStatus == RTPQ_RET_QUEUED_PACKETS_READY) {
// If packets are ready, pull them and send them to the decoder
while ((packet = (PQUEUED_AUDIO_PACKET)RtpqGetQueuedPacket(&rtpReorderQueue)) != NULL) {
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
if (!queuePacketToLbq(&packet)) {
// An exit signal was received
break;
}
}
else {
decodeInputData(packet);
free(packet);
}
}
// Break on exit
if (packet != NULL) {
break;
}
}
}
}
if (packet != NULL) {
free(packet);
}
}
static void DecoderThreadProc(void* context) {
int err;
PQUEUED_AUDIO_PACKET packet;
while (!PltIsThreadInterrupted(&decoderThread)) {
err = LbqWaitForQueueElement(&packetQueue, (void**)&packet);
if (err != LBQ_SUCCESS) {
// An exit signal was received
return;
}
decodeInputData(packet);
free(packet);
}
}
void stopAudioStream(void) {
PltInterruptThread(&udpPingThread);
PltInterruptThread(&receiveThread);
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
// Signal threads waiting on the LBQ
LbqSignalQueueShutdown(&packetQueue);
PltInterruptThread(&decoderThread);
}
PltJoinThread(&udpPingThread);
PltJoinThread(&receiveThread);
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltJoinThread(&decoderThread);
}
PltCloseThread(&udpPingThread);
PltCloseThread(&receiveThread);
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltCloseThread(&decoderThread);
}
if (rtpSocket != INVALID_SOCKET) {
closeSocket(rtpSocket);
rtpSocket = INVALID_SOCKET;
}
AudioCallbacks.cleanup();
}
int startAudioStream(void) {
int err;
AudioCallbacks.init(StreamConfig.audioConfiguration,
opusConfigArray[StreamConfig.audioConfiguration]);
rtpSocket = bindUdpSocket(RemoteAddr.ss_family, RTP_RECV_BUFFER);
if (rtpSocket == INVALID_SOCKET) {
return LastSocketFail();
}
err = PltCreateThread(UdpPingThreadProc, NULL, &udpPingThread);
if (err != 0) {
return err;
}
err = PltCreateThread(ReceiveThreadProc, NULL, &receiveThread);
if (err != 0) {
return err;
}
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
err = PltCreateThread(DecoderThreadProc, NULL, &decoderThread);
if (err != 0) {
return err;
}
}
return 0;
}

147
src/ByteBuffer.c Normal file
View File

@@ -0,0 +1,147 @@
#include "ByteBuffer.h"
void BbInitializeWrappedBuffer(PBYTE_BUFFER buff, char* data, int offset, int length, int byteOrder) {
buff->buffer = data;
buff->offset = offset;
buff->length = length;
buff->position = 0;
buff->byteOrder = byteOrder;
}
// Get the long long in the correct byte order
static long long byteSwapLongLong(PBYTE_BUFFER buff, long long l) {
if (buff->byteOrder == BYTE_ORDER_BIG) {
return HTONLL(l);
}
else {
return l;
}
}
// Get the int in the correct byte order
static int byteSwapInt(PBYTE_BUFFER buff, int i) {
if (buff->byteOrder == BYTE_ORDER_BIG) {
return htonl(i);
}
else {
return i;
}
}
// Get the short in the correct byte order
static int byteSwapShort(PBYTE_BUFFER buff, short s) {
if (buff->byteOrder == BYTE_ORDER_BIG) {
return htons(s);
}
else {
return s;
}
}
// Get a byte from the byte buffer
int BbGet(PBYTE_BUFFER buff, char* c) {
if (buff->position + sizeof(*c) > buff->length) {
return 0;
}
memcpy(c, &buff->buffer[buff->position], sizeof(*c));
buff->position += sizeof(*c);
return 1;
}
// Get a short from the byte buffer
int BbGetShort(PBYTE_BUFFER buff, short* s) {
if (buff->position + sizeof(*s) >= buff->length) {
return 0;
}
memcpy(s, &buff->buffer[buff->position], sizeof(*s));
buff->position += sizeof(*s);
*s = byteSwapShort(buff, *s);
return 1;
}
// Get an int from the byte buffer
int BbGetInt(PBYTE_BUFFER buff, int* i) {
if (buff->position + sizeof(*i) > buff->length) {
return 0;
}
memcpy(i, &buff->buffer[buff->position], sizeof(*i));
buff->position += sizeof(*i);
*i = byteSwapInt(buff, *i);
return 1;
}
// Get a long from the byte buffer
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 = byteSwapLongLong(buff, *l);
return 1;
}
// Put an int into the byte buffer
int BbPutInt(PBYTE_BUFFER buff, int i) {
if (buff->position + sizeof(i) > buff->length) {
return 0;
}
i = byteSwapInt(buff, i);
memcpy(&buff->buffer[buff->position], &i, sizeof(i));
buff->position += sizeof(i);
return 1;
}
// Put a long into the byte buffer
int BbPutLong(PBYTE_BUFFER buff, long long l) {
if (buff->position + sizeof(l) > buff->length) {
return 0;
}
l = byteSwapLongLong(buff, l);
memcpy(&buff->buffer[buff->position], &l, sizeof(l));
buff->position += sizeof(l);
return 1;
}
// Put a short into the byte buffer
int BbPutShort(PBYTE_BUFFER buff, short s) {
if (buff->position + sizeof(s) > buff->length) {
return 0;
}
s = byteSwapShort(buff, s);
memcpy(&buff->buffer[buff->position], &s, sizeof(s));
buff->position += sizeof(s);
return 1;
}
// Put a byte into the buffer
int BbPut(PBYTE_BUFFER buff, char c) {
if (buff->position + sizeof(c) > buff->length) {
return 0;
}
memcpy(&buff->buffer[buff->position], &c, sizeof(c));
buff->position += sizeof(c);
return 1;
}

38
src/ByteBuffer.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include "Platform.h"
#define BYTE_ORDER_LITTLE 1
#define BYTE_ORDER_BIG 2
#ifndef HTONLL
#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))
#endif
typedef struct _BYTE_BUFFER {
char* buffer;
unsigned int offset;
unsigned int length;
unsigned int position;
unsigned int byteOrder;
} BYTE_BUFFER, *PBYTE_BUFFER;
void BbInitializeWrappedBuffer(PBYTE_BUFFER buff, char* data, int offset, int length, int byteOrder);
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);
int BbPutLong(PBYTE_BUFFER buff, long long l);

338
src/Connection.c Normal file
View File

@@ -0,0 +1,338 @@
#include "Limelight-internal.h"
#include "Platform.h"
static int stage = STAGE_NONE;
static ConnListenerConnectionTerminated originalTerminationCallback;
static int alreadyTerminated;
static PLT_THREAD terminationCallbackThread;
static long terminationCallbackErrorCode;
// Common globals
char* RemoteAddrString;
struct sockaddr_storage RemoteAddr;
SOCKADDR_LEN RemoteAddrLen;
int ServerMajorVersion;
STREAM_CONFIGURATION StreamConfig;
CONNECTION_LISTENER_CALLBACKS ListenerCallbacks;
DECODER_RENDERER_CALLBACKS VideoCallbacks;
AUDIO_RENDERER_CALLBACKS AudioCallbacks;
int NegotiatedVideoFormat;
// Connection stages
static const char* stageNames[STAGE_MAX] = {
"none",
"platform initialization",
"name resolution",
"RTSP handshake",
"control stream initialization",
"video stream initialization",
"audio stream initialization",
"input stream initialization",
"control stream establishment",
"video stream establishment",
"audio stream establishment",
"input stream establishment"
};
// Get the name of the current stage based on its number
const char* LiGetStageName(int stage) {
return stageNames[stage];
}
// Stop the connection by undoing the step at the current stage and those before it
void LiStopConnection(void) {
// Disable termination callbacks now
alreadyTerminated = 1;
if (stage == STAGE_INPUT_STREAM_START) {
Limelog("Stopping input stream...");
stopInputStream();
stage--;
Limelog("done\n");
}
if (stage == STAGE_AUDIO_STREAM_START) {
Limelog("Stopping audio stream...");
stopAudioStream();
stage--;
Limelog("done\n");
}
if (stage == STAGE_VIDEO_STREAM_START) {
Limelog("Stopping video stream...");
stopVideoStream();
stage--;
Limelog("done\n");
}
if (stage == STAGE_CONTROL_STREAM_START) {
Limelog("Stopping control stream...");
stopControlStream();
stage--;
Limelog("done\n");
}
if (stage == STAGE_INPUT_STREAM_INIT) {
Limelog("Cleaning up input stream...");
destroyInputStream();
stage--;
Limelog("done\n");
}
if (stage == STAGE_AUDIO_STREAM_INIT) {
Limelog("Cleaning up audio stream...");
destroyAudioStream();
stage--;
Limelog("done\n");
}
if (stage == STAGE_VIDEO_STREAM_INIT) {
Limelog("Cleaning up video stream...");
destroyVideoStream();
stage--;
Limelog("done\n");
}
if (stage == STAGE_CONTROL_STREAM_INIT) {
Limelog("Cleaning up control stream...");
destroyControlStream();
stage--;
Limelog("done\n");
}
if (stage == STAGE_RTSP_HANDSHAKE) {
Limelog("Terminating RTSP handshake...");
terminateRtspHandshake();
stage--;
Limelog("done\n");
}
if (stage == STAGE_NAME_RESOLUTION) {
// Nothing to do
stage--;
}
if (stage == STAGE_PLATFORM_INIT) {
Limelog("Cleaning up platform...");
cleanupPlatform();
stage--;
Limelog("done\n");
}
LC_ASSERT(stage == STAGE_NONE);
if (RemoteAddrString != NULL) {
free(RemoteAddrString);
RemoteAddrString = NULL;
}
}
static void terminationCallbackThreadFunc(void* context)
{
// Invoke the client's termination callback
originalTerminationCallback(terminationCallbackErrorCode);
}
// This shim callback runs the client's connectionTerminated() callback on a
// separate thread. This is neccessary because other internal threads directly
// invoke this callback. That can result in a deadlock if the client
// calls LiStopConnection() in the callback when the cleanup code
// attempts to join the thread that the termination callback (and LiStopConnection)
// is running on.
static void ClInternalConnectionTerminated(long errorCode)
{
int err;
// Avoid recursion and issuing multiple callbacks
if (alreadyTerminated) {
return;
}
alreadyTerminated = 1;
// Invoke the termination callback on a separate thread
err = PltCreateThread(terminationCallbackThreadFunc, NULL, &terminationCallbackThread);
if (err != 0) {
// Nothing we can safely do here, so we'll just assert on debug builds
Limelog("Failed to create termination thread: %d\n", err);
LC_ASSERT(err == 0);
}
// Close the thread handle since we can never wait on it
PltCloseThread(&terminationCallbackThread);
}
static int resolveHostName(const char*host)
{
struct addrinfo hints, *res;
int err;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_ADDRCONFIG;
err = getaddrinfo(host, NULL, &hints, &res);
if (err != 0) {
Limelog("getaddrinfo() failed: %d\n", err);
return err;
}
if (res == NULL) {
Limelog("getaddrinfo() returned success without addresses\n");
return -1;
}
// Use the first address in the list
memcpy(&RemoteAddr, res->ai_addr, res->ai_addrlen);
RemoteAddrLen = res->ai_addrlen;
freeaddrinfo(res);
return 0;
}
// Starts the connection to the streaming machine
int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks,
void* renderContext, int drFlags, int _serverMajorVersion) {
int err;
NegotiatedVideoFormat = 0;
ServerMajorVersion = _serverMajorVersion;
memcpy(&StreamConfig, streamConfig, sizeof(StreamConfig));
RemoteAddrString = strdup(host);
// Replace missing callbacks with placeholders
fixupMissingCallbacks(&drCallbacks, &arCallbacks, &clCallbacks);
memcpy(&VideoCallbacks, drCallbacks, sizeof(VideoCallbacks));
memcpy(&AudioCallbacks, arCallbacks, sizeof(AudioCallbacks));
// Hook the termination callback so we can avoid issuing a termination callback
// after LiStopConnection() is called
originalTerminationCallback = clCallbacks->connectionTerminated;
memcpy(&ListenerCallbacks, clCallbacks, sizeof(ListenerCallbacks));
ListenerCallbacks.connectionTerminated = ClInternalConnectionTerminated;
alreadyTerminated = 0;
Limelog("Initializing platform...");
ListenerCallbacks.stageStarting(STAGE_PLATFORM_INIT);
err = initializePlatform();
if (err != 0) {
Limelog("failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_PLATFORM_INIT, err);
goto Cleanup;
}
stage++;
LC_ASSERT(stage == STAGE_PLATFORM_INIT);
ListenerCallbacks.stageComplete(STAGE_PLATFORM_INIT);
Limelog("done\n");
Limelog("Resolving host name...");
ListenerCallbacks.stageStarting(STAGE_NAME_RESOLUTION);
err = resolveHostName(host);
if (err != 0) {
Limelog("failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err);
goto Cleanup;
}
stage++;
LC_ASSERT(stage == STAGE_NAME_RESOLUTION);
ListenerCallbacks.stageComplete(STAGE_NAME_RESOLUTION);
Limelog("done\n");
Limelog("Starting RTSP handshake...");
ListenerCallbacks.stageStarting(STAGE_RTSP_HANDSHAKE);
err = performRtspHandshake();
if (err != 0) {
Limelog("failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_RTSP_HANDSHAKE, err);
goto Cleanup;
}
stage++;
LC_ASSERT(stage == STAGE_RTSP_HANDSHAKE);
ListenerCallbacks.stageComplete(STAGE_RTSP_HANDSHAKE);
Limelog("done\n");
Limelog("Initializing control stream...");
ListenerCallbacks.stageStarting(STAGE_CONTROL_STREAM_INIT);
err = initializeControlStream();
if (err != 0) {
Limelog("failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_CONTROL_STREAM_INIT, err);
goto Cleanup;
}
stage++;
LC_ASSERT(stage == STAGE_CONTROL_STREAM_INIT);
ListenerCallbacks.stageComplete(STAGE_CONTROL_STREAM_INIT);
Limelog("done\n");
Limelog("Initializing video stream...");
ListenerCallbacks.stageStarting(STAGE_VIDEO_STREAM_INIT);
initializeVideoStream();
stage++;
LC_ASSERT(stage == STAGE_VIDEO_STREAM_INIT);
ListenerCallbacks.stageComplete(STAGE_VIDEO_STREAM_INIT);
Limelog("done\n");
Limelog("Initializing audio stream...");
ListenerCallbacks.stageStarting(STAGE_AUDIO_STREAM_INIT);
initializeAudioStream();
stage++;
LC_ASSERT(stage == STAGE_AUDIO_STREAM_INIT);
ListenerCallbacks.stageComplete(STAGE_AUDIO_STREAM_INIT);
Limelog("done\n");
Limelog("Initializing input stream...");
ListenerCallbacks.stageStarting(STAGE_INPUT_STREAM_INIT);
initializeInputStream(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey),
streamConfig->remoteInputAesIv, sizeof(streamConfig->remoteInputAesIv));
stage++;
LC_ASSERT(stage == STAGE_INPUT_STREAM_INIT);
ListenerCallbacks.stageComplete(STAGE_INPUT_STREAM_INIT);
Limelog("done\n");
Limelog("Starting control stream...");
ListenerCallbacks.stageStarting(STAGE_CONTROL_STREAM_START);
err = startControlStream();
if (err != 0) {
Limelog("failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_CONTROL_STREAM_START, err);
goto Cleanup;
}
stage++;
LC_ASSERT(stage == STAGE_CONTROL_STREAM_START);
ListenerCallbacks.stageComplete(STAGE_CONTROL_STREAM_START);
Limelog("done\n");
Limelog("Starting video stream...");
ListenerCallbacks.stageStarting(STAGE_VIDEO_STREAM_START);
err = startVideoStream(renderContext, drFlags);
if (err != 0) {
Limelog("Video stream start failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_VIDEO_STREAM_START, err);
goto Cleanup;
}
stage++;
LC_ASSERT(stage == STAGE_VIDEO_STREAM_START);
ListenerCallbacks.stageComplete(STAGE_VIDEO_STREAM_START);
Limelog("done\n");
Limelog("Starting audio stream...");
ListenerCallbacks.stageStarting(STAGE_AUDIO_STREAM_START);
err = startAudioStream();
if (err != 0) {
Limelog("Audio stream start failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_AUDIO_STREAM_START, err);
goto Cleanup;
}
stage++;
LC_ASSERT(stage == STAGE_AUDIO_STREAM_START);
ListenerCallbacks.stageComplete(STAGE_AUDIO_STREAM_START);
Limelog("done\n");
Limelog("Starting input stream...");
ListenerCallbacks.stageStarting(STAGE_INPUT_STREAM_START);
err = startInputStream();
if (err != 0) {
Limelog("Input stream start failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_INPUT_STREAM_START, err);
goto Cleanup;
}
stage++;
LC_ASSERT(stage == STAGE_INPUT_STREAM_START);
ListenerCallbacks.stageComplete(STAGE_INPUT_STREAM_START);
Limelog("done\n");
ListenerCallbacks.connectionStarted();
Cleanup:
return err;
}

701
src/ControlStream.c Normal file
View File

@@ -0,0 +1,701 @@
#include "Limelight-internal.h"
#include "PlatformSockets.h"
#include "PlatformThreads.h"
#include "ByteBuffer.h"
#include <enet/enet.h>
// NV control stream packet header for TCP
typedef struct _NVCTL_TCP_PACKET_HEADER {
unsigned short type;
unsigned short payloadLength;
} NVCTL_TCP_PACKET_HEADER, *PNVCTL_TCP_PACKET_HEADER;
typedef struct _NVCTL_ENET_PACKET_HEADER {
unsigned short type;
} NVCTL_ENET_PACKET_HEADER, *PNVCTL_ENET_PACKET_HEADER;
typedef struct _QUEUED_FRAME_INVALIDATION_TUPLE {
int startFrame;
int endFrame;
LINKED_BLOCKING_QUEUE_ENTRY entry;
} QUEUED_FRAME_INVALIDATION_TUPLE, *PQUEUED_FRAME_INVALIDATION_TUPLE;
static SOCKET ctlSock = INVALID_SOCKET;
static ENetHost* client;
static ENetPeer* peer;
static PLT_MUTEX enetMutex;
static PLT_THREAD lossStatsThread;
static PLT_THREAD invalidateRefFramesThread;
static PLT_EVENT invalidateRefFramesEvent;
static int lossCountSinceLastReport;
static long lastGoodFrame;
static long lastSeenFrame;
static int stopping;
static int idrFrameRequired;
static LINKED_BLOCKING_QUEUE invalidReferenceFrameTuples;
#define IDX_START_A 0
#define IDX_REQUEST_IDR_FRAME 0
#define IDX_START_B 1
#define IDX_INVALIDATE_REF_FRAMES 2
#define IDX_LOSS_STATS 3
#define IDX_INPUT_DATA 5
#define CONTROL_STREAM_TIMEOUT_SEC 10
static const short packetTypesGen3[] = {
0x140b, // Start A
0x1410, // Start B
0x1404, // Invalidate reference frames
0x140c, // Loss Stats
0x1417, // Frame Stats (unused)
-1, // Input data (unused)
};
static const short packetTypesGen4[] = {
0x0606, // Request IDR frame
0x0609, // Start B
0x0604, // Invalidate reference frames
0x060a, // Loss Stats
0x0611, // Frame Stats (unused)
-1, // Input data (unused)
};
static const short packetTypesGen5[] = {
0x0305, // Start A
0x0307, // Start B
0x0301, // Invalidate reference frames
0x0201, // Loss Stats
0x0204, // Frame Stats (unused)
0x0207, // Input data
};
static const short packetTypesGen7[] = {
0x0305, // Start A
0x0307, // Start B
0x0301, // Invalidate reference frames
0x0201, // Loss Stats
0x0204, // Frame Stats (unused)
0x0206, // Input data
};
static const char startAGen3[] = { 0 };
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
24, // Invalidate reference frames
32, // Loss Stats
64, // Frame Stats
-1, // Input data
};
static const short payloadLengthsGen4[] = {
sizeof(requestIdrFrameGen4), // Request IDR frame
sizeof(startBGen4), // Start B
24, // Invalidate reference frames
32, // Loss Stats
64, // Frame Stats
-1, // Input data
};
static const short payloadLengthsGen5[] = {
sizeof(startAGen5), // Start A
sizeof(startBGen5), // Start B
24, // Invalidate reference frames
32, // Loss Stats
80, // Frame Stats
-1, // Input data
};
static const short payloadLengthsGen7[] = {
sizeof(startAGen5), // Start A
sizeof(startBGen5), // Start B
24, // Invalidate reference frames
32, // Loss Stats
80, // Frame Stats
-1, // Input data
};
static const char* preconstructedPayloadsGen3[] = {
startAGen3,
(char*)startBGen3
};
static const char* preconstructedPayloadsGen4[] = {
requestIdrFrameGen4,
startBGen4
};
static const char* preconstructedPayloadsGen5[] = {
startAGen5,
startBGen5
};
static const char* preconstructedPayloadsGen7[] = {
startAGen5,
startBGen5
};
static short* packetTypes;
static short* payloadLengths;
static char**preconstructedPayloads;
#define LOSS_REPORT_INTERVAL_MS 50
// Initializes the control stream
int initializeControlStream(void) {
stopping = 0;
PltCreateEvent(&invalidateRefFramesEvent);
LbqInitializeLinkedBlockingQueue(&invalidReferenceFrameTuples, 20);
if (ServerMajorVersion == 3) {
packetTypes = (short*)packetTypesGen3;
payloadLengths = (short*)payloadLengthsGen3;
preconstructedPayloads = (char**)preconstructedPayloadsGen3;
}
else if (ServerMajorVersion == 4) {
packetTypes = (short*)packetTypesGen4;
payloadLengths = (short*)payloadLengthsGen4;
preconstructedPayloads = (char**)preconstructedPayloadsGen4;
}
else if (ServerMajorVersion == 5) {
packetTypes = (short*)packetTypesGen5;
payloadLengths = (short*)payloadLengthsGen5;
preconstructedPayloads = (char**)preconstructedPayloadsGen5;
}
else {
packetTypes = (short*)packetTypesGen7;
payloadLengths = (short*)payloadLengthsGen7;
preconstructedPayloads = (char**)preconstructedPayloadsGen7;
}
idrFrameRequired = 0;
lastGoodFrame = 0;
lastSeenFrame = 0;
lossCountSinceLastReport = 0;
return 0;
}
void freeFrameInvalidationList(PLINKED_BLOCKING_QUEUE_ENTRY entry) {
PLINKED_BLOCKING_QUEUE_ENTRY nextEntry;
while (entry != NULL) {
nextEntry = entry->flink;
free(entry->data);
entry = nextEntry;
}
}
// Cleans up control stream
void destroyControlStream(void) {
LC_ASSERT(stopping);
PltCloseEvent(&invalidateRefFramesEvent);
freeFrameInvalidationList(LbqDestroyLinkedBlockingQueue(&invalidReferenceFrameTuples));
}
int getNextFrameInvalidationTuple(PQUEUED_FRAME_INVALIDATION_TUPLE* qfit) {
int err = LbqPollQueueElement(&invalidReferenceFrameTuples, (void**)qfit);
return (err == LBQ_SUCCESS);
}
void queueFrameInvalidationTuple(int startFrame, int endFrame) {
if (VideoCallbacks.capabilities & CAPABILITY_REFERENCE_FRAME_INVALIDATION) {
PQUEUED_FRAME_INVALIDATION_TUPLE qfit;
qfit = malloc(sizeof(*qfit));
if (qfit != NULL) {
qfit->startFrame = startFrame;
qfit->endFrame = endFrame;
if (LbqOfferQueueItem(&invalidReferenceFrameTuples, qfit, &qfit->entry) == LBQ_BOUND_EXCEEDED) {
// Too many invalidation tuples, so we need an IDR frame now
free(qfit);
idrFrameRequired = 1;
}
}
else {
idrFrameRequired = 1;
}
}
else {
idrFrameRequired = 1;
}
PltSetEvent(&invalidateRefFramesEvent);
}
// Request an IDR frame on demand by the decoder
void requestIdrOnDemand(void) {
idrFrameRequired = 1;
PltSetEvent(&invalidateRefFramesEvent);
}
// Invalidate reference frames if the decoder is too slow
void connectionSinkTooSlow(int startFrame, int endFrame) {
queueFrameInvalidationTuple(startFrame, endFrame);
}
// Invalidate reference frames lost by the network
void connectionDetectedFrameLoss(int startFrame, int endFrame) {
queueFrameInvalidationTuple(startFrame, endFrame);
}
// When we receive a frame, update the number of our current frame
void connectionReceivedCompleteFrame(int frameIndex) {
lastGoodFrame = frameIndex;
}
void connectionSawFrame(int frameIndex) {
lastSeenFrame = frameIndex;
}
// When we lose packets, update our packet loss count
void connectionLostPackets(int lastReceivedPacket, int nextReceivedPacket) {
lossCountSinceLastReport += (nextReceivedPacket - lastReceivedPacket) - 1;
}
// Reads an NV control stream packet from the TCP connection
static PNVCTL_TCP_PACKET_HEADER readNvctlPacketTcp(void) {
NVCTL_TCP_PACKET_HEADER staticHeader;
PNVCTL_TCP_PACKET_HEADER fullPacket;
SOCK_RET err;
err = recv(ctlSock, (char*)&staticHeader, sizeof(staticHeader), 0);
if (err != sizeof(staticHeader)) {
return NULL;
}
fullPacket = (PNVCTL_TCP_PACKET_HEADER)malloc(staticHeader.payloadLength + sizeof(staticHeader));
if (fullPacket == NULL) {
return NULL;
}
memcpy(fullPacket, &staticHeader, sizeof(staticHeader));
if (staticHeader.payloadLength != 0) {
err = recv(ctlSock, (char*)(fullPacket + 1), staticHeader.payloadLength, 0);
if (err != staticHeader.payloadLength) {
free(fullPacket);
return NULL;
}
}
return fullPacket;
}
static int sendMessageEnet(short ptype, short paylen, const void* payload) {
PNVCTL_ENET_PACKET_HEADER packet;
ENetPacket* enetPacket;
ENetEvent event;
int err;
LC_ASSERT(ServerMajorVersion >= 5);
packet = malloc(sizeof(*packet) + paylen);
if (packet == NULL) {
return 0;
}
packet->type = ptype;
memcpy(&packet[1], payload, paylen);
// Gen 5+ servers do control protocol over ENet instead of TCP
while ((err = enet_host_service(client, &event, 0)) > 0) {
if (event.type == ENET_EVENT_TYPE_RECEIVE) {
enet_packet_destroy(event.packet);
}
else if (event.type == ENET_EVENT_TYPE_DISCONNECT) {
Limelog("Control stream received disconnect event\n");
free(packet);
return 0;
}
}
if (err < 0) {
Limelog("Control stream connection failed\n");
return 0;
}
enetPacket = enet_packet_create(packet, sizeof(*packet) + paylen, ENET_PACKET_FLAG_RELIABLE);
if (packet == NULL) {
free(packet);
return 0;
}
if (enet_peer_send(peer, 0, enetPacket) < 0) {
Limelog("Failed to send ENet control packet\n");
enet_packet_destroy(enetPacket);
free(packet);
return 0;
}
enet_host_flush(client);
free(packet);
return 1;
}
static int sendMessageTcp(short ptype, short paylen, const void* payload) {
PNVCTL_TCP_PACKET_HEADER packet;
SOCK_RET err;
LC_ASSERT(ServerMajorVersion < 5);
packet = malloc(sizeof(*packet) + paylen);
if (packet == NULL) {
return 0;
}
packet->type = ptype;
packet->payloadLength = paylen;
memcpy(&packet[1], payload, paylen);
err = send(ctlSock, (char*) packet, sizeof(*packet) + paylen, 0);
free(packet);
if (err != sizeof(*packet) + paylen) {
return 0;
}
return 1;
}
static int sendMessageAndForget(short ptype, short paylen, const void* payload) {
int ret;
// Unlike regular sockets, ENet sockets aren't safe to invoke from multiple
// threads at once. We have to synchronize them with a lock.
if (ServerMajorVersion >= 5) {
PltLockMutex(&enetMutex);
ret = sendMessageEnet(ptype, paylen, payload);
PltUnlockMutex(&enetMutex);
}
else {
ret = sendMessageTcp(ptype, paylen, payload);
}
return ret;
}
static int sendMessageAndDiscardReply(short ptype, short paylen, const void* payload) {
// Discard the response
if (ServerMajorVersion >= 5) {
ENetEvent event;
PltLockMutex(&enetMutex);
if (!sendMessageEnet(ptype, paylen, payload)) {
PltUnlockMutex(&enetMutex);
return 0;
}
if (enet_host_service(client, &event, CONTROL_STREAM_TIMEOUT_SEC * 1000) <= 0 ||
event.type != ENET_EVENT_TYPE_RECEIVE) {
PltUnlockMutex(&enetMutex);
return 0;
}
enet_packet_destroy(event.packet);
PltUnlockMutex(&enetMutex);
}
else {
PNVCTL_TCP_PACKET_HEADER reply;
if (!sendMessageTcp(ptype, paylen, payload)) {
return 0;
}
reply = readNvctlPacketTcp();
if (reply == NULL) {
return 0;
}
free(reply);
}
return 1;
}
static void lossStatsThreadFunc(void* context) {
char*lossStatsPayload;
BYTE_BUFFER byteBuffer;
lossStatsPayload = malloc(payloadLengths[IDX_LOSS_STATS]);
if (lossStatsPayload == NULL) {
Limelog("Loss Stats: malloc() failed\n");
ListenerCallbacks.connectionTerminated(-1);
return;
}
while (!PltIsThreadInterrupted(&lossStatsThread)) {
// Construct the payload
BbInitializeWrappedBuffer(&byteBuffer, lossStatsPayload, 0, payloadLengths[IDX_LOSS_STATS], BYTE_ORDER_LITTLE);
BbPutInt(&byteBuffer, lossCountSinceLastReport);
BbPutInt(&byteBuffer, LOSS_REPORT_INTERVAL_MS);
BbPutInt(&byteBuffer, 1000);
BbPutLong(&byteBuffer, lastGoodFrame);
BbPutInt(&byteBuffer, 0);
BbPutInt(&byteBuffer, 0);
BbPutInt(&byteBuffer, 0x14);
// Send the message (and don't expect a response)
if (!sendMessageAndForget(packetTypes[IDX_LOSS_STATS],
payloadLengths[IDX_LOSS_STATS], lossStatsPayload)) {
free(lossStatsPayload);
Limelog("Loss Stats: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketError());
return;
}
// Clear the transient state
lossCountSinceLastReport = 0;
// Wait a bit
PltSleepMs(LOSS_REPORT_INTERVAL_MS);
}
free(lossStatsPayload);
}
static void requestIdrFrame(void) {
long long payload[3];
if (ServerMajorVersion != 4) {
// Form the payload
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
if (!sendMessageAndDiscardReply(packetTypes[IDX_INVALIDATE_REF_FRAMES],
payloadLengths[IDX_INVALIDATE_REF_FRAMES], payload)) {
Limelog("Request IDR Frame: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketError());
return;
}
}
else {
// Send IDR frame request and read the response
if (!sendMessageAndDiscardReply(packetTypes[IDX_REQUEST_IDR_FRAME],
payloadLengths[IDX_REQUEST_IDR_FRAME], preconstructedPayloads[IDX_REQUEST_IDR_FRAME])) {
Limelog("Request IDR Frame: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketError());
return;
}
}
Limelog("IDR frame request sent\n");
}
static void requestInvalidateReferenceFrames(void) {
long long payload[3];
PQUEUED_FRAME_INVALIDATION_TUPLE qfit;
LC_ASSERT(VideoCallbacks.capabilities & CAPABILITY_REFERENCE_FRAME_INVALIDATION);
if (!getNextFrameInvalidationTuple(&qfit)) {
return;
}
LC_ASSERT(qfit->startFrame <= qfit->endFrame);
payload[0] = qfit->startFrame;
payload[1] = qfit->endFrame;
payload[2] = 0;
// Aggregate all lost frames into one range
do {
LC_ASSERT(qfit->endFrame >= payload[1]);
payload[1] = qfit->endFrame;
free(qfit);
} while (getNextFrameInvalidationTuple(&qfit));
// Send the reference frame invalidation request and read the response
if (!sendMessageAndDiscardReply(packetTypes[IDX_INVALIDATE_REF_FRAMES],
payloadLengths[IDX_INVALIDATE_REF_FRAMES], payload)) {
Limelog("Request Invaldiate Reference Frames: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketError());
return;
}
Limelog("Invalidate reference frame request sent\n");
}
static void invalidateRefFramesFunc(void* context) {
while (!PltIsThreadInterrupted(&invalidateRefFramesThread)) {
// Wait for a request to invalidate reference frames
PltWaitForEvent(&invalidateRefFramesEvent);
PltClearEvent(&invalidateRefFramesEvent);
// Bail if we've been shutdown
if (stopping) {
break;
}
// Sometimes we absolutely need an IDR frame
if (idrFrameRequired) {
// Empty invalidate reference frames tuples
PQUEUED_FRAME_INVALIDATION_TUPLE qfit;
while (getNextFrameInvalidationTuple(&qfit)) {
free(qfit);
}
// Send an IDR frame request
idrFrameRequired = 0;
requestIdrFrame();
}
else {
// Otherwise invalidate reference frames
requestInvalidateReferenceFrames();
}
}
}
// Stops the control stream
int stopControlStream(void) {
stopping = 1;
LbqSignalQueueShutdown(&invalidReferenceFrameTuples);
PltSetEvent(&invalidateRefFramesEvent);
if (peer != NULL) {
PltLockMutex(&enetMutex);
enet_peer_disconnect_now(peer, 0);
PltUnlockMutex(&enetMutex);
}
if (ctlSock != INVALID_SOCKET) {
shutdownTcpSocket(ctlSock);
}
PltInterruptThread(&lossStatsThread);
PltInterruptThread(&invalidateRefFramesThread);
PltJoinThread(&lossStatsThread);
PltJoinThread(&invalidateRefFramesThread);
PltCloseThread(&lossStatsThread);
PltCloseThread(&invalidateRefFramesThread);
peer = NULL;
if (client != NULL) {
enet_host_destroy(client);
client = NULL;
}
if (ctlSock != INVALID_SOCKET) {
closeSocket(ctlSock);
ctlSock = INVALID_SOCKET;
}
PltDeleteMutex(&enetMutex);
return 0;
}
// Called by the input stream to send a packet for Gen 5+ servers
int sendInputPacketOnControlStream(unsigned char* data, int length) {
LC_ASSERT(ServerMajorVersion >= 5);
// Send the input data (no reply expected)
if (sendMessageAndForget(packetTypes[IDX_INPUT_DATA], length, data) == 0) {
return -1;
}
return 0;
}
// Starts the control stream
int startControlStream(void) {
int err;
PltCreateMutex(&enetMutex);
if (ServerMajorVersion >= 5) {
ENetAddress address;
ENetEvent event;
// This will do DNS resolution if required
if (enet_address_set_host(&address, RemoteAddrString) < 0) {
return -1;
}
enet_address_set_port(&address, 47999);
// Create a client that can use 1 outgoing connection and 1 channel
client = enet_host_create(address.address.ss_family, NULL, 1, 1, 0, 0);
if (client == NULL) {
return -1;
}
// Connect to the host
peer = enet_host_connect(client, &address, 1, 0);
if (peer == NULL) {
enet_host_destroy(client);
client = NULL;
return -1;
}
// Wait for the connect to complete
if (enet_host_service(client, &event, CONTROL_STREAM_TIMEOUT_SEC * 1000) <= 0 ||
event.type != ENET_EVENT_TYPE_CONNECT) {
Limelog("RTSP: Failed to connect to UDP port 47999\n");
enet_peer_reset(peer);
peer = NULL;
enet_host_destroy(client);
client = NULL;
return -1;
}
// Ensure the connect verify ACK is sent immediately
enet_host_flush(client);
// Set the max peer timeout to 10 seconds
enet_peer_timeout(peer, ENET_PEER_TIMEOUT_LIMIT, ENET_PEER_TIMEOUT_MINIMUM, 10000);
}
else {
ctlSock = connectTcpSocket(&RemoteAddr, RemoteAddrLen,
47995, CONTROL_STREAM_TIMEOUT_SEC);
if (ctlSock == INVALID_SOCKET) {
return LastSocketFail();
}
enableNoDelay(ctlSock);
}
// Send START A
if (!sendMessageAndDiscardReply(packetTypes[IDX_START_A],
payloadLengths[IDX_START_A],
preconstructedPayloads[IDX_START_A])) {
Limelog("Start A failed: %d\n", (int)LastSocketError());
return LastSocketFail();
}
// Send START B
if (!sendMessageAndDiscardReply(packetTypes[IDX_START_B],
payloadLengths[IDX_START_B],
preconstructedPayloads[IDX_START_B])) {
Limelog("Start B failed: %d\n", (int)LastSocketError());
return LastSocketFail();
}
err = PltCreateThread(lossStatsThreadFunc, NULL, &lossStatsThread);
if (err != 0) {
return err;
}
err = PltCreateThread(invalidateRefFramesFunc, NULL, &invalidateRefFramesThread);
if (err != 0) {
return err;
}
return 0;
}

100
src/FakeCallbacks.c Normal file
View File

@@ -0,0 +1,100 @@
#include "Limelight-internal.h"
static void fakeDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {}
static void fakeDrCleanup(void) {}
static int fakeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) { return DR_OK; }
static DECODER_RENDERER_CALLBACKS fakeDrCallbacks = {
.setup = fakeDrSetup,
.cleanup = fakeDrCleanup,
.submitDecodeUnit = fakeDrSubmitDecodeUnit,
};
static void fakeArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig) {}
static void fakeArCleanup(void) {}
static void fakeArDecodeAndPlaySample(char* sampleData, int sampleLength) {}
AUDIO_RENDERER_CALLBACKS fakeArCallbacks = {
.init = fakeArInit,
.cleanup = fakeArCleanup,
.decodeAndPlaySample = fakeArDecodeAndPlaySample,
};
static void fakeClStageStarting(int stage) {}
static void fakeClStageComplete(int stage) {}
static void fakeClStageFailed(int stage, long errorCode) {}
static void fakeClConnectionStarted(void) {}
static void fakeClConnectionTerminated(long errorCode) {}
static void fakeClDisplayMessage(char* message) {}
static void fakeClDisplayTransientMessage(char* message) {}
static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = {
.stageStarting = fakeClStageStarting,
.stageComplete = fakeClStageComplete,
.stageFailed = fakeClStageFailed,
.connectionStarted = fakeClConnectionStarted,
.connectionTerminated = fakeClConnectionTerminated,
.displayMessage = fakeClDisplayMessage,
.displayTransientMessage = fakeClDisplayTransientMessage,
};
void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks,
PCONNECTION_LISTENER_CALLBACKS* clCallbacks)
{
if (*drCallbacks == NULL) {
*drCallbacks = &fakeDrCallbacks;
}
else {
if ((*drCallbacks)->setup == NULL) {
(*drCallbacks)->setup = fakeDrSetup;
}
if ((*drCallbacks)->cleanup == NULL) {
(*drCallbacks)->cleanup = fakeDrCleanup;
}
if ((*drCallbacks)->submitDecodeUnit == NULL) {
(*drCallbacks)->submitDecodeUnit = fakeDrSubmitDecodeUnit;
}
}
if (*arCallbacks == NULL) {
*arCallbacks = &fakeArCallbacks;
}
else {
if ((*arCallbacks)->init == NULL) {
(*arCallbacks)->init = fakeArInit;
}
if ((*arCallbacks)->cleanup == NULL) {
(*arCallbacks)->cleanup = fakeArCleanup;
}
if ((*arCallbacks)->decodeAndPlaySample == NULL) {
(*arCallbacks)->decodeAndPlaySample = fakeArDecodeAndPlaySample;
}
}
if (*clCallbacks == NULL) {
*clCallbacks = &fakeClCallbacks;
}
else {
if ((*clCallbacks)->stageStarting == NULL) {
(*clCallbacks)->stageStarting = fakeClStageStarting;
}
if ((*clCallbacks)->stageComplete == NULL) {
(*clCallbacks)->stageComplete = fakeClStageComplete;
}
if ((*clCallbacks)->stageFailed == NULL) {
(*clCallbacks)->stageFailed = fakeClStageFailed;
}
if ((*clCallbacks)->connectionStarted == NULL) {
(*clCallbacks)->connectionStarted = fakeClConnectionStarted;
}
if ((*clCallbacks)->connectionTerminated == NULL) {
(*clCallbacks)->connectionTerminated = fakeClConnectionTerminated;
}
if ((*clCallbacks)->displayMessage == NULL) {
(*clCallbacks)->displayMessage = fakeClDisplayMessage;
}
if ((*clCallbacks)->displayTransientMessage == NULL) {
(*clCallbacks)->displayTransientMessage = fakeClDisplayTransientMessage;
}
}
}

92
src/Input.h Normal file
View File

@@ -0,0 +1,92 @@
#pragma once
#pragma pack(push, 1)
typedef struct _NV_INPUT_HEADER {
int packetType;
} NV_INPUT_HEADER, PNV_INPUT_HEADER;
#define PACKET_TYPE_KEYBOARD 0x0A
typedef struct _NV_KEYBOARD_PACKET {
NV_INPUT_HEADER header;
char keyAction;
int zero1;
short keyCode;
char modifiers;
short zero2;
} NV_KEYBOARD_PACKET, *PNV_KEYBOARD_PACKET;
#define PACKET_TYPE_MOUSE_MOVE 0x08
#define MOUSE_MOVE_MAGIC 0x06
typedef struct _NV_MOUSE_MOVE_PACKET {
NV_INPUT_HEADER header;
int magic;
short deltaX;
short deltaY;
} NV_MOUSE_MOVE_PACKET, *PNV_MOUSE_MOVE_PACKET;
#define PACKET_TYPE_MOUSE_BUTTON 0x05
typedef struct _NV_MOUSE_BUTTON_PACKET {
NV_INPUT_HEADER header;
char action;
int button;
} NV_MOUSE_BUTTON_PACKET, *PNV_MOUSE_BUTTON_PACKET;
#define PACKET_TYPE_CONTROLLER 0x18
#define C_HEADER_A 0x0000000A
#define C_HEADER_B 0x1400
#define C_TAIL_A 0x0000009C
#define C_TAIL_B 0x0055
typedef struct _NV_CONTROLLER_PACKET {
NV_INPUT_HEADER header;
int headerA;
short headerB;
short buttonFlags;
unsigned char leftTrigger;
unsigned char rightTrigger;
short leftStickX;
short leftStickY;
short rightStickX;
short rightStickY;
int tailA;
short tailB;
} NV_CONTROLLER_PACKET, *PNV_CONTROLLER_PACKET;
#define PACKET_TYPE_MULTI_CONTROLLER 0x1E
#define MC_HEADER_A 0x0000000D
#define MC_HEADER_B 0x001A
#define MC_ACTIVE_CONTROLLER_FLAGS 0x000F
#define MC_MID_B 0x0014
#define MC_TAIL_A 0x0000009C
#define MC_TAIL_B 0x0055
typedef struct _NV_MULTI_CONTROLLER_PACKET {
NV_INPUT_HEADER header;
int headerA;
short headerB;
short controllerNumber;
short midA;
short midB;
short buttonFlags;
unsigned char leftTrigger;
unsigned char rightTrigger;
short leftStickX;
short leftStickY;
short rightStickX;
short rightStickY;
int tailA;
short tailB;
} NV_MULTI_CONTROLLER_PACKET, *PNV_MULTI_CONTROLLER_PACKET;
#define PACKET_TYPE_SCROLL 0xA
#define MAGIC_A 0x09
typedef struct _NV_SCROLL_PACKET {
NV_INPUT_HEADER header;
char magicA;
char zero1;
short zero2;
short scrollAmt1;
short scrollAmt2;
short zero3;
} NV_SCROLL_PACKET, *PNV_SCROLL_PACKET;
#pragma pack(pop)

532
src/InputStream.c Normal file
View File

@@ -0,0 +1,532 @@
#include "Limelight-internal.h"
#include "PlatformSockets.h"
#include "PlatformThreads.h"
#include "LinkedBlockingQueue.h"
#include "Input.h"
#include "OpenAES/oaes_lib.h"
#include "OpenAES/oaes_common.h"
static SOCKET inputSock = INVALID_SOCKET;
static int initialized;
static LINKED_BLOCKING_QUEUE packetQueue;
static PLT_THREAD inputSendThread;
static OAES_CTX* oaesContext;
#define MAX_INPUT_PACKET_SIZE 128
#define INPUT_STREAM_TIMEOUT_SEC 10
// Contains input stream packets
typedef struct _PACKET_HOLDER {
int packetLength;
union {
NV_KEYBOARD_PACKET keyboard;
NV_MOUSE_MOVE_PACKET mouseMove;
NV_MOUSE_BUTTON_PACKET mouseButton;
NV_CONTROLLER_PACKET controller;
NV_MULTI_CONTROLLER_PACKET multiController;
NV_SCROLL_PACKET scroll;
} packet;
LINKED_BLOCKING_QUEUE_ENTRY entry;
} PACKET_HOLDER, *PPACKET_HOLDER;
// Initializes the input stream
int initializeInputStream(char* aesKeyData, int aesKeyDataLength,
char* aesIv, int aesIvLength) {
if (aesIvLength != OAES_BLOCK_SIZE)
{
Limelog("AES IV is incorrect length. Should be %d\n", aesIvLength);
return -1;
}
oaesContext = oaes_alloc();
if (oaesContext == NULL)
{
Limelog("Failed to allocate OpenAES context\n");
return -1;
}
if (oaes_set_option(oaesContext, OAES_OPTION_CBC, aesIv) != OAES_RET_SUCCESS)
{
Limelog("Failed to set CBC and IV on OAES context\n");
return -1;
}
if (oaes_key_import_data(oaesContext, (const unsigned char*)aesKeyData, aesKeyDataLength) != OAES_RET_SUCCESS)
{
Limelog("Failed to import AES key data\n");
return -1;
}
LbqInitializeLinkedBlockingQueue(&packetQueue, 30);
initialized = 1;
return 0;
}
// Destroys and cleans up the input stream
void destroyInputStream(void) {
PLINKED_BLOCKING_QUEUE_ENTRY entry, nextEntry;
if (oaesContext != NULL)
{
// FIXME: This crashes trying to free ctx->key
// oaes_free(oaesContext);
oaesContext = NULL;
}
entry = LbqDestroyLinkedBlockingQueue(&packetQueue);
while (entry != NULL) {
nextEntry = entry->flink;
// The entry is stored in the data buffer
free(entry->data);
entry = nextEntry;
}
initialized = 0;
}
// Checks if values are compatible with controller batching
static int checkDirs(short currentVal, short newVal, int* dir) {
if (currentVal == newVal) {
return 1;
}
// We want to send a new packet if we've now zeroed an axis
if (newVal == 0) {
return 0;
}
if (*dir == 0) {
if (newVal < currentVal) {
*dir = -1;
}
else {
*dir = 1;
}
}
else if (*dir == -1) {
return newVal < currentVal;
}
else if (newVal < currentVal) {
return 0;
}
return 1;
}
#define OAES_DATA_OFFSET 32
// Input thread proc
static void inputSendThreadProc(void* context) {
SOCK_RET err;
PPACKET_HOLDER holder;
char encryptedBuffer[MAX_INPUT_PACKET_SIZE];
size_t encryptedSize;
while (!PltIsThreadInterrupted(&inputSendThread)) {
int encryptedLengthPrefix;
err = LbqWaitForQueueElement(&packetQueue, (void**)&holder);
if (err != LBQ_SUCCESS) {
return;
}
// If it's a multi-controller packet we can do batching
if (holder->packet.multiController.header.packetType == htonl(PACKET_TYPE_MULTI_CONTROLLER)) {
PPACKET_HOLDER controllerBatchHolder;
PNV_MULTI_CONTROLLER_PACKET origPkt;
int dirs[6];
memset(dirs, 0, sizeof(dirs));
origPkt = &holder->packet.multiController;
for (;;) {
PNV_MULTI_CONTROLLER_PACKET newPkt;
// Peek at the next packet
if (LbqPeekQueueElement(&packetQueue, (void**)&controllerBatchHolder) != LBQ_SUCCESS) {
break;
}
// If it's not a controller packet, we're done
if (controllerBatchHolder->packet.multiController.header.packetType != htonl(PACKET_TYPE_MULTI_CONTROLLER)) {
break;
}
// Check if it's able to be batched
newPkt = &controllerBatchHolder->packet.multiController;
if (newPkt->buttonFlags != origPkt->buttonFlags ||
newPkt->controllerNumber != origPkt->controllerNumber ||
!checkDirs(origPkt->leftTrigger, newPkt->leftTrigger, &dirs[0]) ||
!checkDirs(origPkt->rightTrigger, newPkt->rightTrigger, &dirs[1]) ||
!checkDirs(origPkt->leftStickX, newPkt->leftStickX, &dirs[2]) ||
!checkDirs(origPkt->leftStickY, newPkt->leftStickY, &dirs[3]) ||
!checkDirs(origPkt->rightStickX, newPkt->rightStickX, &dirs[4]) ||
!checkDirs(origPkt->rightStickY, newPkt->rightStickY, &dirs[5])) {
// Batching not allowed
break;
}
// Remove the batchable controller packet
if (LbqPollQueueElement(&packetQueue, (void**)&controllerBatchHolder) != LBQ_SUCCESS) {
break;
}
// Update the original packet
origPkt->leftTrigger = newPkt->leftTrigger;
origPkt->rightTrigger = newPkt->rightTrigger;
origPkt->leftStickX = newPkt->leftStickX;
origPkt->leftStickY = newPkt->leftStickY;
origPkt->rightStickX = newPkt->rightStickX;
origPkt->rightStickY = newPkt->rightStickY;
// Free the batched packet holder
free(controllerBatchHolder);
}
}
// If it's a mouse move packet, we can also do batching
else if (holder->packet.mouseMove.header.packetType == htonl(PACKET_TYPE_MOUSE_MOVE)) {
PPACKET_HOLDER mouseBatchHolder;
int totalDeltaX = (short)htons(holder->packet.mouseMove.deltaX);
int totalDeltaY = (short)htons(holder->packet.mouseMove.deltaY);
for (;;) {
int partialDeltaX;
int partialDeltaY;
// Peek at the next packet
if (LbqPeekQueueElement(&packetQueue, (void**)&mouseBatchHolder) != LBQ_SUCCESS) {
break;
}
// If it's not a mouse move packet, we're done
if (mouseBatchHolder->packet.mouseMove.header.packetType != htonl(PACKET_TYPE_MOUSE_MOVE)) {
break;
}
partialDeltaX = (short)htons(mouseBatchHolder->packet.mouseMove.deltaX);
partialDeltaY = (short)htons(mouseBatchHolder->packet.mouseMove.deltaY);
// Check for overflow
if (partialDeltaX + totalDeltaX > INT16_MAX ||
partialDeltaX + totalDeltaX < INT16_MIN ||
partialDeltaY + totalDeltaY > INT16_MAX ||
partialDeltaY + totalDeltaY < INT16_MIN) {
// Total delta would overflow our 16-bit short
break;
}
// Remove the batchable mouse move packet
if (LbqPollQueueElement(&packetQueue, (void**)&mouseBatchHolder) != LBQ_SUCCESS) {
break;
}
totalDeltaX += partialDeltaX;
totalDeltaY += partialDeltaY;
// Free the batched packet holder
free(mouseBatchHolder);
}
// Update the original packet
holder->packet.mouseMove.deltaX = htons((short)totalDeltaX);
holder->packet.mouseMove.deltaY = htons((short)totalDeltaY);
}
encryptedSize = sizeof(encryptedBuffer);
err = oaes_encrypt(oaesContext, (const unsigned char*)&holder->packet, holder->packetLength,
(unsigned char*)encryptedBuffer, &encryptedSize);
free(holder);
if (err != OAES_RET_SUCCESS) {
Limelog("Input: Encryption failed: %d\n", (int)err);
ListenerCallbacks.connectionTerminated(err);
return;
}
// The first 32-bytes of the output are internal OAES stuff that we want to ignore
encryptedSize -= OAES_DATA_OFFSET;
// Overwrite the last 4 bytes before the encrypted data with the length so
// we can send the message all at once. GFE can choke if it gets the header
// before the rest of the message.
encryptedLengthPrefix = htonl((unsigned long)encryptedSize);
memcpy(&encryptedBuffer[OAES_DATA_OFFSET - sizeof(encryptedLengthPrefix)],
&encryptedLengthPrefix, sizeof(encryptedLengthPrefix));
if (ServerMajorVersion < 5) {
// Send the encrypted payload
err = send(inputSock, (const char*) &encryptedBuffer[OAES_DATA_OFFSET - sizeof(encryptedLengthPrefix)],
(int) (encryptedSize + sizeof(encryptedLengthPrefix)), 0);
if (err <= 0) {
Limelog("Input: send() failed: %d\n", (int) LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketError());
return;
}
}
else {
err = (SOCK_RET)sendInputPacketOnControlStream((unsigned char*) &encryptedBuffer[OAES_DATA_OFFSET - sizeof(encryptedLengthPrefix)],
(int) (encryptedSize + sizeof(encryptedLengthPrefix)));
if (err < 0) {
Limelog("Input: sendInputPacketOnControlStream() failed: %d\n", (int) err);
ListenerCallbacks.connectionTerminated(LastSocketError());
return;
}
}
}
}
// Begin the input stream
int startInputStream(void) {
int err;
// After Gen 5, we send input on the control stream
if (ServerMajorVersion < 5) {
inputSock = connectTcpSocket(&RemoteAddr, RemoteAddrLen,
35043, INPUT_STREAM_TIMEOUT_SEC);
if (inputSock == INVALID_SOCKET) {
return LastSocketFail();
}
enableNoDelay(inputSock);
}
err = PltCreateThread(inputSendThreadProc, NULL, &inputSendThread);
if (err != 0) {
return err;
}
return err;
}
// Stops the input stream
int stopInputStream(void) {
// Signal the input send thread
LbqSignalQueueShutdown(&packetQueue);
PltInterruptThread(&inputSendThread);
if (inputSock != INVALID_SOCKET) {
shutdownTcpSocket(inputSock);
}
PltJoinThread(&inputSendThread);
PltCloseThread(&inputSendThread);
if (inputSock != INVALID_SOCKET) {
closeSocket(inputSock);
inputSock = INVALID_SOCKET;
}
return 0;
}
// Send a mouse move event to the streaming machine
int LiSendMouseMoveEvent(short deltaX, short deltaY) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = malloc(sizeof(*holder));
if (holder == NULL) {
return -1;
}
holder->packetLength = sizeof(NV_MOUSE_MOVE_PACKET);
holder->packet.mouseMove.header.packetType = htonl(PACKET_TYPE_MOUSE_MOVE);
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++;
}
holder->packet.mouseMove.deltaX = htons(deltaX);
holder->packet.mouseMove.deltaY = htons(deltaY);
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) {
free(holder);
}
return err;
}
// Send a mouse button event to the streaming machine
int LiSendMouseButtonEvent(char action, int button) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = malloc(sizeof(*holder));
if (holder == NULL) {
return -1;
}
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);
if (err != LBQ_SUCCESS) {
free(holder);
}
return err;
}
// Send a key press event to the streaming machine
int LiSendKeyboardEvent(short keyCode, char keyAction, char modifiers) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = malloc(sizeof(*holder));
if (holder == NULL) {
return -1;
}
holder->packetLength = sizeof(NV_KEYBOARD_PACKET);
holder->packet.keyboard.header.packetType = htonl(PACKET_TYPE_KEYBOARD);
holder->packet.keyboard.keyAction = keyAction;
holder->packet.keyboard.zero1 = 0;
holder->packet.keyboard.keyCode = keyCode;
holder->packet.keyboard.modifiers = modifiers;
holder->packet.keyboard.zero2 = 0;
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) {
free(holder);
}
return err;
}
static int sendControllerEventInternal(short controllerNumber, short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY)
{
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = malloc(sizeof(*holder));
if (holder == NULL) {
return -1;
}
if (ServerMajorVersion == 3) {
// Generation 3 servers don't support multiple controllers so we send
// the legacy packet
holder->packetLength = sizeof(NV_CONTROLLER_PACKET);
holder->packet.controller.header.packetType = htonl(PACKET_TYPE_CONTROLLER);
holder->packet.controller.headerA = C_HEADER_A;
holder->packet.controller.headerB = C_HEADER_B;
holder->packet.controller.buttonFlags = buttonFlags;
holder->packet.controller.leftTrigger = leftTrigger;
holder->packet.controller.rightTrigger = rightTrigger;
holder->packet.controller.leftStickX = leftStickX;
holder->packet.controller.leftStickY = leftStickY;
holder->packet.controller.rightStickX = rightStickX;
holder->packet.controller.rightStickY = rightStickY;
holder->packet.controller.tailA = C_TAIL_A;
holder->packet.controller.tailB = C_TAIL_B;
}
else {
// Generation 4+ servers support passing the controller number
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;
holder->packet.multiController.midB = MC_MID_B;
holder->packet.multiController.buttonFlags = buttonFlags;
holder->packet.multiController.leftTrigger = leftTrigger;
holder->packet.multiController.rightTrigger = rightTrigger;
holder->packet.multiController.leftStickX = leftStickX;
holder->packet.multiController.leftStickY = leftStickY;
holder->packet.multiController.rightStickX = rightStickX;
holder->packet.multiController.rightStickY = rightStickY;
holder->packet.multiController.tailA = MC_TAIL_A;
holder->packet.multiController.tailB = MC_TAIL_B;
}
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) {
free(holder);
}
return err;
}
// Send a controller event to the streaming machine
int LiSendControllerEvent(short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY)
{
return sendControllerEventInternal(0, buttonFlags, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
// Send a controller event to the streaming machine
int LiSendMultiControllerEvent(short controllerNumber, short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY)
{
return sendControllerEventInternal(controllerNumber, buttonFlags, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
// Send a scroll event to the streaming machine
int LiSendScrollEvent(signed char scrollClicks) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = malloc(sizeof(*holder));
if (holder == NULL) {
return -1;
}
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);
holder->packet.scroll.scrollAmt2 = holder->packet.scroll.scrollAmt1;
holder->packet.scroll.zero3 = 0;
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) {
free(holder);
}
return err;
}

62
src/Limelight-internal.h Normal file
View File

@@ -0,0 +1,62 @@
#pragma once
#include "Limelight.h"
#include "Platform.h"
#include "PlatformSockets.h"
#include "PlatformThreads.h"
#include "Video.h"
// Common globals
extern char* RemoteAddrString;
extern struct sockaddr_storage RemoteAddr;
extern SOCKADDR_LEN RemoteAddrLen;
extern int ServerMajorVersion;
extern STREAM_CONFIGURATION StreamConfig;
extern CONNECTION_LISTENER_CALLBACKS ListenerCallbacks;
extern DECODER_RENDERER_CALLBACKS VideoCallbacks;
extern AUDIO_RENDERER_CALLBACKS AudioCallbacks;
extern int NegotiatedVideoFormat;
int isBeforeSignedInt(int numA, int numB, int ambiguousCase);
void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks,
PCONNECTION_LISTENER_CALLBACKS* clCallbacks);
char* getSdpPayloadForStreamConfig(int rtspClientVersion, int* length);
int initializeControlStream(void);
int startControlStream(void);
int stopControlStream(void);
void destroyControlStream(void);
void requestIdrOnDemand(void);
void connectionSinkTooSlow(int startFrame, int endFrame);
void connectionDetectedFrameLoss(int startFrame, int endFrame);
void connectionReceivedCompleteFrame(int frameIndex);
void connectionSawFrame(int frameIndex);
void connectionLostPackets(int lastReceivedPacket, int nextReceivedPacket);
int sendInputPacketOnControlStream(unsigned char* data, int length);
int performRtspHandshake(void);
void terminateRtspHandshake(void);
void initializeVideoDepacketizer(int pktSize);
void destroyVideoDepacketizer(void);
void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length);
void queueRtpPacket(PRTP_PACKET rtpPacket, int length);
void stopVideoDepacketizer(void);
void requestDecoderRefresh(void);
void initializeVideoStream(void);
void destroyVideoStream(void);
int startVideoStream(void* rendererContext, int drFlags);
void stopVideoStream(void);
void initializeAudioStream(void);
void destroyAudioStream(void);
int startAudioStream(void);
void stopAudioStream(void);
int initializeInputStream(char* aesKeyData, int aesKeyDataLength, char* aesIv, int aesIvLength);
void destroyInputStream(void);
int startInputStream(void);
int stopInputStream(void);

287
src/Limelight.h Normal file
View File

@@ -0,0 +1,287 @@
//
// This header exposes the public streaming API for client usage
//
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// Enable this definition during debugging to enable assertions
//#define LC_DEBUG
typedef struct _STREAM_CONFIGURATION {
// Dimensions in pixels of the desired video stream
int width;
int height;
// FPS of the desired video stream
int fps;
// Bitrate of the desired video stream (audio adds another ~1 Mbps)
int bitrate;
// Max video packet size in bytes (use 1024 if unsure)
int packetSize;
// Set to non-zero value to enable remote (over the Internet)
// streaming optimizations. If unsure, set to 0.
int streamingRemotely;
// Specifies the channel configuration of the audio stream.
// See AUDIO_CONFIGURATION_XXX constants below.
int audioConfiguration;
// Specifies that the client can accept an H.265 video stream
// if the server is able to provide one.
int supportsHevc;
// AES encryption data for the remote input stream. This must be
// the same as what was passed as rikey and rikeyid
// in /launch and /resume requests.
char remoteInputAesKey[16];
char remoteInputAesIv[16];
} STREAM_CONFIGURATION, *PSTREAM_CONFIGURATION;
// Use this function to zero the stream configuration when allocated on the stack or heap
void LiInitializeStreamConfiguration(PSTREAM_CONFIGURATION streamConfig);
typedef struct _LENTRY {
// Pointer to the next entry or NULL if this is the last entry
struct _LENTRY* next;
// Pointer to data (never NULL)
char* data;
// Size of data in bytes (never <= 0)
int length;
} LENTRY, *PLENTRY;
// A decode unit describes a buffer chain of video data from multiple packets
typedef struct _DECODE_UNIT {
// Length of the entire buffer chain in bytes
int fullLength;
// Head of the buffer chain (never NULL)
PLENTRY bufferList;
} DECODE_UNIT, *PDECODE_UNIT;
// Specifies that the audio stream should be encoded in stereo (default)
#define AUDIO_CONFIGURATION_STEREO 0
// Specifies that the audio stream should be in 5.1 surround sound if the PC is able
#define AUDIO_CONFIGURATION_51_SURROUND 1
// Passed to DecoderRendererSetup to indicate that the following video stream will be
// in H.264 format
#define VIDEO_FORMAT_H264 1
// Passed to DecoderRendererSetup to indicate that the following video stream will be
// in H.265 format
#define VIDEO_FORMAT_H265 2
// If set in the renderer capabilities field, this flag will cause audio/video data to
// be submitted directly from the receive thread. This should only be specified if the
// renderer is non-blocking. This flag is valid on both audio and video renderers.
#define CAPABILITY_DIRECT_SUBMIT 0x1
// !!! HIGHLY EXPERIMENTAL - DO NOT SET IN PRODUCTION CODE !!!
// If set in the video renderer capabilities field, this flag specifies that the renderer
// supports reference frame invalidation. This flag is only valid on video renderers.
#define CAPABILITY_REFERENCE_FRAME_INVALIDATION 0x2
// If set in the video renderer capabilities field, this macro specifies that the renderer
// supports slicing to increase decoding performance. The parameter specifies the desired
// number of slices per frame. This capability is only valid on video renderers.
#define CAPABILITY_SLICES_PER_FRAME(x) (((unsigned char)(x)) << 24)
// This callback is invoked to provide details about the video stream and allow configuration of the decoder
typedef void(*DecoderRendererSetup)(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags);
// This callback performs the teardown of the video decoder
typedef void(*DecoderRendererCleanup)(void);
// This callback provides Annex B formatted elementary stream data to the
// decoder. If the decoder is unable to process the submitted data for some reason,
// it must return DR_NEED_IDR to generate a keyframe.
#define DR_OK 0
#define DR_NEED_IDR -1
typedef int(*DecoderRendererSubmitDecodeUnit)(PDECODE_UNIT decodeUnit);
typedef struct _DECODER_RENDERER_CALLBACKS {
DecoderRendererSetup setup;
DecoderRendererCleanup cleanup;
DecoderRendererSubmitDecodeUnit submitDecodeUnit;
int capabilities;
} DECODER_RENDERER_CALLBACKS, *PDECODER_RENDERER_CALLBACKS;
// Use this function to zero the video callbacks when allocated on the stack or heap
void LiInitializeVideoCallbacks(PDECODER_RENDERER_CALLBACKS drCallbacks);
// This structure provides the Opus multistream decoder parameters required to successfully
// decode the audio stream being sent from the computer. See opus_multistream_decoder_init docs
// for details about these fields.
//
// The supplied mapping array is indexed according to the following output channel order:
// 0 - Front Left
// 1 - Front Right
// 2 - Center
// 3 - LFE
// 4 - Surround Left
// 5 - Surround Right
//
// If the mapping order does not match the channel order of the audio renderer, you may swap
// the values in the mismatched indices until the mapping array matches the desired channel order.
typedef struct _OPUS_MULTISTREAM_CONFIGURATION {
int sampleRate;
int channelCount;
int streams;
int coupledStreams;
const unsigned char mapping[6];
} OPUS_MULTISTREAM_CONFIGURATION, *POPUS_MULTISTREAM_CONFIGURATION;
// This callback initializes the audio renderer. The audio configuration parameter
// provides the negotiated audio configuration. This may differ from the one
// specified in the stream configuration.
typedef void(*AudioRendererInit)(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig);
// This callback performs the final teardown of the audio decoder
typedef void(*AudioRendererCleanup)(void);
// This callback provides Opus audio data to be decoded and played. sampleLength is in bytes.
typedef void(*AudioRendererDecodeAndPlaySample)(char* sampleData, int sampleLength);
typedef struct _AUDIO_RENDERER_CALLBACKS {
AudioRendererInit init;
AudioRendererCleanup cleanup;
AudioRendererDecodeAndPlaySample decodeAndPlaySample;
int capabilities;
} AUDIO_RENDERER_CALLBACKS, *PAUDIO_RENDERER_CALLBACKS;
// Use this function to zero the audio callbacks when allocated on the stack or heap
void LiInitializeAudioCallbacks(PAUDIO_RENDERER_CALLBACKS arCallbacks);
// Subject to change in future releases
// Use LiGetStageName() for stable stage names
#define STAGE_NONE 0
#define STAGE_PLATFORM_INIT 1
#define STAGE_NAME_RESOLUTION 2
#define STAGE_RTSP_HANDSHAKE 3
#define STAGE_CONTROL_STREAM_INIT 4
#define STAGE_VIDEO_STREAM_INIT 5
#define STAGE_AUDIO_STREAM_INIT 6
#define STAGE_INPUT_STREAM_INIT 7
#define STAGE_CONTROL_STREAM_START 8
#define STAGE_VIDEO_STREAM_START 9
#define STAGE_AUDIO_STREAM_START 10
#define STAGE_INPUT_STREAM_START 11
#define STAGE_MAX 12
// This callback is invoked to indicate that a stage of initialization is about to begin
typedef void(*ConnListenerStageStarting)(int stage);
// This callback is invoked to indicate that a stage of initialization has completed
typedef void(*ConnListenerStageComplete)(int stage);
// This callback is invoked to indicate that a stage of initialization has failed
typedef void(*ConnListenerStageFailed)(int stage, long errorCode);
// This callback is invoked after initialization has finished
typedef void(*ConnListenerConnectionStarted)(void);
// This callback is invoked when a connection failure occurs. It will not
// occur as a result of a call to LiStopConnection()
typedef void(*ConnListenerConnectionTerminated)(long errorCode);
// This callback is invoked to display a dialog-type message to the user
typedef void(*ConnListenerDisplayMessage)(char* message);
// This callback is invoked to display a transient message for the user
// while streaming
typedef void(*ConnListenerDisplayTransientMessage)(char* message);
typedef struct _CONNECTION_LISTENER_CALLBACKS {
ConnListenerStageStarting stageStarting;
ConnListenerStageComplete stageComplete;
ConnListenerStageFailed stageFailed;
ConnListenerConnectionStarted connectionStarted;
ConnListenerConnectionTerminated connectionTerminated;
ConnListenerDisplayMessage displayMessage;
ConnListenerDisplayTransientMessage displayTransientMessage;
} CONNECTION_LISTENER_CALLBACKS, *PCONNECTION_LISTENER_CALLBACKS;
// Use this function to zero the connection callbacks when allocated on the stack or heap
void LiInitializeConnectionCallbacks(PCONNECTION_LISTENER_CALLBACKS clCallbacks);
// This function begins streaming.
//
// Callbacks are all optional. Pass NULL for individual callbacks within each struct or pass NULL for the entire struct
// to use the defaults for all callbacks.
//
// _serverMajorVersion is the major version number of the 'appversion' tag in the /serverinfo request
//
int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags, int _serverMajorVersion);
// This function stops streaming.
void LiStopConnection(void);
// Use to get a user-visible string to display initialization progress
// from the integer passed to the ConnListenerStageXXX callbacks
const char* LiGetStageName(int stage);
// This function queues a mouse move event to be sent to the remote server.
int LiSendMouseMoveEvent(short deltaX, short deltaY);
// This function queues a mouse button event to be sent to the remote server.
#define BUTTON_ACTION_PRESS 0x07
#define BUTTON_ACTION_RELEASE 0x08
#define BUTTON_LEFT 0x01
#define BUTTON_MIDDLE 0x02
#define BUTTON_RIGHT 0x03
int LiSendMouseButtonEvent(char action, int button);
// This function queues a keyboard event to be sent to the remote server.
#define KEY_ACTION_DOWN 0x03
#define KEY_ACTION_UP 0x04
#define MODIFIER_SHIFT 0x01
#define MODIFIER_CTRL 0x02
#define MODIFIER_ALT 0x04
int LiSendKeyboardEvent(short keyCode, char keyAction, char modifiers);
// Button flags
#define A_FLAG 0x1000
#define B_FLAG 0x2000
#define X_FLAG 0x4000
#define Y_FLAG 0x8000
#define UP_FLAG 0x0001
#define DOWN_FLAG 0x0002
#define LEFT_FLAG 0x0004
#define RIGHT_FLAG 0x0008
#define LB_FLAG 0x0100
#define RB_FLAG 0x0200
#define PLAY_FLAG 0x0010
#define BACK_FLAG 0x0020
#define LS_CLK_FLAG 0x0040
#define RS_CLK_FLAG 0x0080
#define SPECIAL_FLAG 0x0400
// This function queues a controller event to be sent to the remote server. It will
// be seen by the computer as the first controller.
int LiSendControllerEvent(short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY);
// This function queues a controller event to be sent to the remote server. The controllerNumber
// parameter is a zero-based index of which controller this event corresponds to. The largest legal
// controller number is 3 (for a total of 4 controllers, the Xinput maximum). On generation 3 servers (GFE 2.1.x),
// these will be sent as controller 0 regardless of the controllerNumber parameter.
int LiSendMultiControllerEvent(short controllerNumber, short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY);
// This function queues a vertical scroll event to the remote server.
int LiSendScrollEvent(signed char scrollClicks);
#ifdef __cplusplus
}
#endif

209
src/LinkedBlockingQueue.c Normal file
View File

@@ -0,0 +1,209 @@
#include "LinkedBlockingQueue.h"
// Destroy the linked blocking queue and associated mutex and event
PLINKED_BLOCKING_QUEUE_ENTRY LbqDestroyLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead) {
LC_ASSERT(queueHead->shutdown);
PltDeleteMutex(&queueHead->mutex);
PltCloseEvent(&queueHead->containsDataEvent);
return queueHead->head;
}
// Flush the queue
PLINKED_BLOCKING_QUEUE_ENTRY LbqFlushQueueItems(PLINKED_BLOCKING_QUEUE queueHead) {
PLINKED_BLOCKING_QUEUE_ENTRY head;
PltLockMutex(&queueHead->mutex);
// Save the old head
head = queueHead->head;
// Reinitialize the queue to empty
queueHead->head = NULL;
queueHead->tail = NULL;
queueHead->currentSize = 0;
PltClearEvent(&queueHead->containsDataEvent);
PltUnlockMutex(&queueHead->mutex);
return head;
}
// Linked blocking queue init
int LbqInitializeLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead, int sizeBound) {
int err;
err = PltCreateEvent(&queueHead->containsDataEvent);
if (err != 0) {
return err;
}
err = PltCreateMutex(&queueHead->mutex);
if (err != 0) {
return err;
}
queueHead->head = NULL;
queueHead->tail = NULL;
queueHead->sizeBound = sizeBound;
queueHead->currentSize = 0;
queueHead->shutdown = 0;
return 0;
}
void LbqSignalQueueShutdown(PLINKED_BLOCKING_QUEUE queueHead) {
queueHead->shutdown = 1;
PltSetEvent(&queueHead->containsDataEvent);
}
int LbqOfferQueueItem(PLINKED_BLOCKING_QUEUE queueHead, void* data, PLINKED_BLOCKING_QUEUE_ENTRY entry) {
if (queueHead->shutdown) {
return LBQ_INTERRUPTED;
}
entry->flink = NULL;
entry->data = data;
PltLockMutex(&queueHead->mutex);
if (queueHead->currentSize == queueHead->sizeBound) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_BOUND_EXCEEDED;
}
if (queueHead->head == NULL) {
LC_ASSERT(queueHead->currentSize == 0);
LC_ASSERT(queueHead->tail == NULL);
queueHead->head = entry;
queueHead->tail = entry;
entry->blink = NULL;
}
else {
LC_ASSERT(queueHead->currentSize >= 1);
LC_ASSERT(queueHead->head != NULL);
queueHead->tail->flink = entry;
entry->blink = queueHead->tail;
queueHead->tail = entry;
}
queueHead->currentSize++;
PltUnlockMutex(&queueHead->mutex);
PltSetEvent(&queueHead->containsDataEvent);
return LBQ_SUCCESS;
}
// This must be synchronized with LbqFlushQueueItems by the caller
int LbqPeekQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
if (queueHead->shutdown) {
return LBQ_INTERRUPTED;
}
if (queueHead->head == NULL) {
return LBQ_NO_ELEMENT;
}
PltLockMutex(&queueHead->mutex);
if (queueHead->head == NULL) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_NO_ELEMENT;
}
*data = queueHead->head->data;
PltUnlockMutex(&queueHead->mutex);
return LBQ_SUCCESS;
}
int LbqPollQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
PLINKED_BLOCKING_QUEUE_ENTRY entry;
if (queueHead->shutdown) {
return LBQ_INTERRUPTED;
}
if (queueHead->head == NULL) {
return LBQ_NO_ELEMENT;
}
PltLockMutex(&queueHead->mutex);
if (queueHead->head == NULL) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_NO_ELEMENT;
}
entry = queueHead->head;
queueHead->head = entry->flink;
queueHead->currentSize--;
if (queueHead->head == NULL) {
LC_ASSERT(queueHead->currentSize == 0);
queueHead->tail = NULL;
PltClearEvent(&queueHead->containsDataEvent);
}
else {
LC_ASSERT(queueHead->currentSize != 0);
queueHead->head->blink = NULL;
}
*data = entry->data;
PltUnlockMutex(&queueHead->mutex);
return LBQ_SUCCESS;
}
int LbqWaitForQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
PLINKED_BLOCKING_QUEUE_ENTRY entry;
int err;
if (queueHead->shutdown) {
return LBQ_INTERRUPTED;
}
for (;;) {
err = PltWaitForEvent(&queueHead->containsDataEvent);
if (err != PLT_WAIT_SUCCESS) {
return LBQ_INTERRUPTED;
}
if (queueHead->shutdown) {
return LBQ_INTERRUPTED;
}
PltLockMutex(&queueHead->mutex);
if (queueHead->head == NULL) {
PltClearEvent(&queueHead->containsDataEvent);
PltUnlockMutex(&queueHead->mutex);
continue;
}
entry = queueHead->head;
queueHead->head = entry->flink;
queueHead->currentSize--;
if (queueHead->head == NULL) {
LC_ASSERT(queueHead->currentSize == 0);
queueHead->tail = NULL;
PltClearEvent(&queueHead->containsDataEvent);
}
else {
LC_ASSERT(queueHead->currentSize != 0);
queueHead->head->blink = NULL;
}
*data = entry->data;
PltUnlockMutex(&queueHead->mutex);
break;
}
return LBQ_SUCCESS;
}

34
src/LinkedBlockingQueue.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include "Platform.h"
#include "PlatformThreads.h"
#define LBQ_SUCCESS 0
#define LBQ_INTERRUPTED 1
#define LBQ_BOUND_EXCEEDED 2
#define LBQ_NO_ELEMENT 3
typedef struct _LINKED_BLOCKING_QUEUE_ENTRY {
struct _LINKED_BLOCKING_QUEUE_ENTRY* flink;
struct _LINKED_BLOCKING_QUEUE_ENTRY* blink;
void* data;
} LINKED_BLOCKING_QUEUE_ENTRY, *PLINKED_BLOCKING_QUEUE_ENTRY;
typedef struct _LINKED_BLOCKING_QUEUE {
PLT_MUTEX mutex;
PLT_EVENT containsDataEvent;
int sizeBound;
int currentSize;
int shutdown;
PLINKED_BLOCKING_QUEUE_ENTRY head;
PLINKED_BLOCKING_QUEUE_ENTRY tail;
} LINKED_BLOCKING_QUEUE, *PLINKED_BLOCKING_QUEUE;
int LbqInitializeLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead, int sizeBound);
int LbqOfferQueueItem(PLINKED_BLOCKING_QUEUE queueHead, void* data, PLINKED_BLOCKING_QUEUE_ENTRY entry);
int LbqWaitForQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data);
int LbqPollQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data);
int LbqPeekQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data);
PLINKED_BLOCKING_QUEUE_ENTRY LbqDestroyLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead);
PLINKED_BLOCKING_QUEUE_ENTRY LbqFlushQueueItems(PLINKED_BLOCKING_QUEUE queueHead);
void LbqSignalQueueShutdown(PLINKED_BLOCKING_QUEUE queueHead);

34
src/Misc.c Normal file
View File

@@ -0,0 +1,34 @@
#include "Limelight-internal.h"
int isBeforeSignedInt(int numA, int numB, int ambiguousCase) {
// This should be the common case for most callers
if (numA == numB) {
return 0;
}
// If numA and numB have the same signs,
// we can just do a regular comparison.
if ((numA < 0 && numB < 0) || (numA >= 0 && numB >= 0)) {
return numA < numB;
}
else {
// The sign switch is ambiguous
return ambiguousCase;
}
}
void LiInitializeStreamConfiguration(PSTREAM_CONFIGURATION streamConfig) {
memset(streamConfig, 0, sizeof(*streamConfig));
}
void LiInitializeVideoCallbacks(PDECODER_RENDERER_CALLBACKS drCallbacks) {
memset(drCallbacks, 0, sizeof(*drCallbacks));
}
void LiInitializeAudioCallbacks(PAUDIO_RENDERER_CALLBACKS arCallbacks) {
memset(arCallbacks, 0, sizeof(*arCallbacks));
}
void LiInitializeConnectionCallbacks(PCONNECTION_LISTENER_CALLBACKS clCallbacks) {
memset(clCallbacks, 0, sizeof(*clCallbacks));
}

27
src/OpenAES/LICENSE Normal file
View File

@@ -0,0 +1,27 @@
---------------------------------------------------------------------------
OpenAES Licence
---------------------------------------------------------------------------
Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------

85
src/OpenAES/README Normal file
View File

@@ -0,0 +1,85 @@
---------------------------------------------------------------------------
OpenAES-0.9.0
Nabil S. Al Ramli
www.nalramli.com
---------------------------------------------------------------------------
License Terms
-------------
---------------------------------------------------------------------------
OpenAES Licence
---------------------------------------------------------------------------
Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
Introduction
------------
OpenAES is an open source implementation of the Advanced Encryption
Standard. It is distributed as a portable, lightweight C library that can
be easily integrated into applications.
Compiling
---------
OpenAES has been tested with the GCC as well as VC compilers. It is
necessary to compile the source files located in ./src, and to add ./inc to
the include paths.
If you are building with OAES_HAVE_ISAAC defined (true by default), then
you also need to link in the source files under ./src/isaac and also add
./src/isaac to the include paths.
You may edit ./inc/oaes_config.h to modify build options.
CMake 2.8.0 can be used to build the test programs on different platforms.
In a Linux command line terminal type:
cmake .
make
In Windows, in a Visual Studio command line window type:
cmake . -G "NMake Makefiles"
nmake
Usage
-----
oaes_lib usage is described in the header file ./inc/oaes_lib.h.
The oaes command line application help manual can be obtained by using the
--help command line options.
The oaes_setup Windows installer integrates with the Windows shell. It can be
used by right clicking a file in Windows Explorer and then selecting a
subcommand from the OpenAES menu.
Samples
-------
Samples applications are provided in the /test folder.

1
src/OpenAES/VERSION Normal file
View File

@@ -0,0 +1 @@
OpenAES-0.9.0

178
src/OpenAES/oaes_base64.c Normal file
View File

@@ -0,0 +1,178 @@
/*
* ---------------------------------------------------------------------------
* OpenAES License
* ---------------------------------------------------------------------------
* Copyright (c) 2013, Nabil S. Al Ramli, www.nalramli.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ---------------------------------------------------------------------------
*/
#if 0 // LIMELIGHT
static const char _NR[] = {
0x4e,0x61,0x62,0x69,0x6c,0x20,0x53,0x2e,0x20,
0x41,0x6c,0x20,0x52,0x61,0x6d,0x6c,0x69,0x00 };
#endif
#include <stdlib.h>
#include <string.h>
#include "oaes_config.h"
#include "oaes_base64.h"
static const char _oaes_base64_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
OAES_RET oaes_base64_encode(
const uint8_t *in, size_t in_len, char *out, size_t *out_len)
{
size_t _i = 0, _j = 0;
unsigned char _buf1[3];
unsigned char _buf2[4];
size_t _out_len_req = 4 * ( in_len / 3 + ( in_len % 3 ? 1 : 0 ) ) + 1;
if( NULL == in || 0 == in_len || NULL == out_len )
return OAES_RET_ERROR;
if( NULL == out )
{
*out_len = _out_len_req;
return OAES_RET_SUCCESS;
}
if( _out_len_req > *out_len )
return OAES_RET_ERROR;
memset(out, 0, *out_len);
*out_len = 0;
while( in_len-- )
{
_buf1[_i++] = *(in++);
if( _i == 3 )
{
_buf2[0] = (_buf1[0] & 0xfc) >> 2;
_buf2[1] = ((_buf1[0] & 0x03) << 4) + ((_buf1[1] & 0xf0) >> 4);
_buf2[2] = ((_buf1[1] & 0x0f) << 2) + ((_buf1[2] & 0xc0) >> 6);
_buf2[3] = _buf1[2] & 0x3f;
for( _i = 0; _i < 4; _i++ )
{
*(out++) = _oaes_base64_table[_buf2[_i]];
(*out_len)++;
}
_i = 0;
}
}
if( _i )
{
for( _j = _i; _j < 3; _j++ )
_buf1[_j] = '\0';
_buf2[0] = (_buf1[0] & 0xfc) >> 2;
_buf2[1] = ((_buf1[0] & 0x03) << 4) + ((_buf1[1] & 0xf0) >> 4);
_buf2[2] = ((_buf1[1] & 0x0f) << 2) + ((_buf1[2] & 0xc0) >> 6);
_buf2[3] = _buf1[2] & 0x3f;
for( _j = 0; (_j < _i + 1); _j++ )
{
*(out++) = _oaes_base64_table[_buf2[_j]];
(*out_len)++;
}
while( _i++ < 3 )
{
*(out++) = '=';
(*out_len)++;
}
}
return OAES_RET_SUCCESS;
}
OAES_RET oaes_base64_decode(
const char *in, size_t in_len, uint8_t *out, size_t *out_len )
{
size_t _i = 0, _j = 0, _idx = 0;
uint8_t _buf2[4], _buf1[3];
size_t _out_len_req = 3 * ( in_len / 4 + ( in_len % 4 ? 1 : 0 ) );
if( NULL == in || 0 == in_len || NULL == out_len )
return OAES_RET_ERROR;
if( NULL == out )
{
*out_len = _out_len_req;
return OAES_RET_SUCCESS;
}
if( _out_len_req > *out_len )
return OAES_RET_ERROR;
// Satisfy overzealous compiler
memset(_buf1, 0, sizeof(_buf1));
memset(out, 0, *out_len);
*out_len = 0;
while( in_len-- && strchr(_oaes_base64_table, in[_idx++]) )
{
_buf2[_i++] = in[_idx - 1];
if( _i ==4 )
{
for (_i = 0; _i < 4; _i++)
_buf2[_i] = strchr(_oaes_base64_table, _buf2[_i]) - _oaes_base64_table;
_buf1[0] = (_buf2[0] << 2) + ((_buf2[1] & 0x30) >> 4);
_buf1[1] = ((_buf2[1] & 0xf) << 4) + ((_buf2[2] & 0x3c) >> 2);
_buf1[2] = ((_buf2[2] & 0x3) << 6) + _buf2[3];
for( _i = 0; (_i < 3); _i++ )
{
*(out++) = _buf1[_i];
(*out_len)++;
}
_i = 0;
}
}
if( _i )
{
for( _j = _i; _j <4; _j++ )
_buf2[_j] = 0;
for( _j = 0; _j <4; _j++ )
_buf2[_j] = strchr(_oaes_base64_table, _buf2[_j]) - _oaes_base64_table;
_buf1[0] = (_buf2[0] << 2) + ((_buf2[1] & 0x30) >> 4);
_buf1[1] = ((_buf2[1] & 0xf) << 4) + ((_buf2[2] & 0x3c) >> 2);
_buf1[2] = ((_buf2[2] & 0x3) << 6) + _buf2[3];
for( _j = 0; (_j < _i - 1); _j++ )
{
*(out++) = _buf1[_j];
(*out_len)++;
}
}
return OAES_RET_SUCCESS;
}

50
src/OpenAES/oaes_base64.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* ---------------------------------------------------------------------------
* OpenAES License
* ---------------------------------------------------------------------------
* Copyright (c) 2013, Nabil S. Al Ramli, www.nalramli.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ---------------------------------------------------------------------------
*/
#ifndef _OAES_BASE64_H
#define _OAES_BASE64_H
#include "oaes_common.h"
#ifdef __cplusplus
extern "C" {
#endif
OAES_API OAES_RET oaes_base64_encode(
const uint8_t *in, size_t in_len, char *out, size_t *out_len );
OAES_API OAES_RET oaes_base64_decode(
const char *in, size_t in_len, uint8_t *out, size_t *out_len );
#ifdef __cplusplus
}
#endif
#endif // _OAES_BASE64_H

77
src/OpenAES/oaes_common.h Normal file
View File

@@ -0,0 +1,77 @@
/*
* ---------------------------------------------------------------------------
* OpenAES License
* ---------------------------------------------------------------------------
* Copyright (c) 2013, Nabil S. Al Ramli, www.nalramli.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ---------------------------------------------------------------------------
*/
#ifndef _OAES_COMMON_H
#define _OAES_COMMON_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
# ifdef OAES_SHARED
# ifdef oaes_lib_EXPORTS
# define OAES_API __declspec(dllexport)
# else
# define OAES_API __declspec(dllimport)
# endif
# else
# define OAES_API
# endif
#else
# define OAES_API
#endif // WIN32
#define OAES_VERSION "0.9.0"
typedef enum
{
OAES_RET_FIRST = 0,
OAES_RET_SUCCESS = 0,
OAES_RET_ERROR,
OAES_RET_ARG1,
OAES_RET_ARG2,
OAES_RET_ARG3,
OAES_RET_ARG4,
OAES_RET_ARG5,
OAES_RET_NOKEY,
OAES_RET_MEM,
OAES_RET_BUF,
OAES_RET_HEADER,
OAES_RET_COUNT
} OAES_RET;
#ifdef __cplusplus
}
#endif
#endif // _OAES_COMMON_H

42
src/OpenAES/oaes_config.h Normal file
View File

@@ -0,0 +1,42 @@
/*
* ---------------------------------------------------------------------------
* OpenAES License
* ---------------------------------------------------------------------------
* Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ---------------------------------------------------------------------------
*/
#ifndef _OAES_CONFIG_H
#define _OAES_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif // _OAES_CONFIG_H

1413
src/OpenAES/oaes_lib.c Normal file

File diff suppressed because it is too large Load Diff

168
src/OpenAES/oaes_lib.h Normal file
View File

@@ -0,0 +1,168 @@
/*
* ---------------------------------------------------------------------------
* OpenAES License
* ---------------------------------------------------------------------------
* Copyright (c) 2013, Nabil S. Al Ramli, www.nalramli.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ---------------------------------------------------------------------------
*/
#ifndef _OAES_LIB_H
#define _OAES_LIB_H
#include "oaes_common.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
# ifdef OAES_SHARED
# ifdef oaes_lib_EXPORTS
# define OAES_API __declspec(dllexport)
# else
# define OAES_API __declspec(dllimport)
# endif
# else
# define OAES_API
# endif
#else
# define OAES_API
#endif // WIN32
#define OAES_BLOCK_SIZE 16
typedef void OAES_CTX;
/*
* oaes_set_option() takes one of these values for its [option] parameter
* some options accept either an optional or a required [value] parameter
*/
// no option
#define OAES_OPTION_NONE 0
// enable ECB mode, disable CBC mode
#define OAES_OPTION_ECB 1
// enable CBC mode, disable ECB mode
// value is optional, may pass uint8_t iv[OAES_BLOCK_SIZE] to specify
// the value of the initialization vector, iv
#define OAES_OPTION_CBC 2
#ifdef OAES_DEBUG
typedef int ( * oaes_step_cb ) (
const uint8_t state[OAES_BLOCK_SIZE],
const char * step_name,
int step_count,
void * user_data );
// enable state stepping mode
// value is required, must pass oaes_step_cb to receive the state at each step
#define OAES_OPTION_STEP_ON 4
// disable state stepping mode
#define OAES_OPTION_STEP_OFF 8
#endif // OAES_DEBUG
typedef uint16_t OAES_OPTION;
/*
* // usage:
*
* OAES_CTX * ctx = oaes_alloc();
* .
* .
* .
* {
* oaes_gen_key_xxx( ctx );
* {
* oaes_key_export( ctx, _buf, &_buf_len );
* // or
* oaes_key_export_data( ctx, _buf, &_buf_len );\
* }
* }
* // or
* {
* oaes_key_import( ctx, _buf, _buf_len );
* // or
* oaes_key_import_data( ctx, _buf, _buf_len );
* }
* .
* .
* .
* oaes_encrypt( ctx, m, m_len, c, &c_len );
* .
* .
* .
* oaes_decrypt( ctx, c, c_len, m, &m_len );
* .
* .
* .
* oaes_free( &ctx );
*/
OAES_API OAES_CTX * oaes_alloc();
OAES_API OAES_RET oaes_free( OAES_CTX ** ctx );
OAES_API OAES_RET oaes_set_option( OAES_CTX * ctx,
OAES_OPTION option, const void * value );
OAES_API OAES_RET oaes_key_gen_128( OAES_CTX * ctx );
OAES_API OAES_RET oaes_key_gen_192( OAES_CTX * ctx );
OAES_API OAES_RET oaes_key_gen_256( OAES_CTX * ctx );
// export key with header information
// set data == NULL to get the required data_len
OAES_API OAES_RET oaes_key_export( OAES_CTX * ctx,
uint8_t * data, size_t * data_len );
// directly export the data from key
// set data == NULL to get the required data_len
OAES_API OAES_RET oaes_key_export_data( OAES_CTX * ctx,
uint8_t * data, size_t * data_len );
// import key with header information
OAES_API OAES_RET oaes_key_import( OAES_CTX * ctx,
const uint8_t * data, size_t data_len );
// directly import data into key
OAES_API OAES_RET oaes_key_import_data( OAES_CTX * ctx,
const uint8_t * data, size_t data_len );
// set c == NULL to get the required c_len
OAES_API OAES_RET oaes_encrypt( OAES_CTX * ctx,
const uint8_t * m, size_t m_len, uint8_t * c, size_t * c_len );
// set m == NULL to get the required m_len
OAES_API OAES_RET oaes_decrypt( OAES_CTX * ctx,
const uint8_t * c, size_t c_len, uint8_t * m, size_t * m_len );
// set buf == NULL to get the required buf_len
OAES_API OAES_RET oaes_sprintf(
char * buf, size_t * buf_len, const uint8_t * data, size_t data_len );
#ifdef __cplusplus
}
#endif
#endif // _OAES_LIB_H

256
src/Platform.c Executable file
View File

@@ -0,0 +1,256 @@
#include "PlatformThreads.h"
#include "Platform.h"
#include <enet/enet.h>
int initializePlatformSockets(void);
void cleanupPlatformSockets(void);
struct thread_context {
ThreadEntry entry;
void* context;
};
static int running_threads = 0;
#if defined(LC_WINDOWS)
void LimelogWindows(char* Format, ...) {
va_list va;
char buffer[1024];
va_start(va, Format);
vsprintf(buffer, Format, va);
va_end(va);
OutputDebugStringA(buffer);
}
#endif
#if defined(LC_WINDOWS)
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
struct thread_context* ctx = (struct thread_context*)lpParameter;
#else
void* ThreadProc(void* context) {
struct thread_context* ctx = (struct thread_context*)context;
#endif
ctx->entry(ctx->context);
free(ctx);
#if defined(LC_WINDOWS)
return 0;
#else
return NULL;
#endif
}
void PltSleepMs(int ms) {
#if defined(LC_WINDOWS)
WaitForSingleObjectEx(GetCurrentThread(), ms, FALSE);
#else
useconds_t usecs = ms * 1000;
usleep(usecs);
#endif
}
int PltCreateMutex(PLT_MUTEX* mutex) {
#if defined(LC_WINDOWS)
*mutex = CreateMutexEx(NULL, NULL, 0, MUTEX_ALL_ACCESS);
if (!*mutex) {
return -1;
}
return 0;
#else
return pthread_mutex_init(mutex, NULL);
#endif
}
void PltDeleteMutex(PLT_MUTEX* mutex) {
#if defined(LC_WINDOWS)
CloseHandle(*mutex);
#else
pthread_mutex_destroy(mutex);
#endif
}
void PltLockMutex(PLT_MUTEX* mutex) {
#if defined(LC_WINDOWS)
int err;
err = WaitForSingleObjectEx(*mutex, INFINITE, FALSE);
if (err != WAIT_OBJECT_0) {
LC_ASSERT(FALSE);
}
#else
pthread_mutex_lock(mutex);
#endif
}
void PltUnlockMutex(PLT_MUTEX* mutex) {
#if defined(LC_WINDOWS)
ReleaseMutex(*mutex);
#else
pthread_mutex_unlock(mutex);
#endif
}
void PltJoinThread(PLT_THREAD* thread) {
LC_ASSERT(thread->cancelled);
#if defined(LC_WINDOWS)
WaitForSingleObjectEx(thread->handle, INFINITE, FALSE);
#else
pthread_join(thread->thread, NULL);
#endif
}
void PltCloseThread(PLT_THREAD* thread) {
running_threads--;
#if defined(LC_WINDOWS)
CloseHandle(thread->handle);
#endif
}
int PltIsThreadInterrupted(PLT_THREAD* thread) {
return thread->cancelled;
}
void PltInterruptThread(PLT_THREAD* thread) {
thread->cancelled = 1;
}
int PltCreateThread(ThreadEntry entry, void* context, PLT_THREAD* thread) {
struct thread_context* ctx;
ctx = (struct thread_context*)malloc(sizeof(*ctx));
if (ctx == NULL) {
return -1;
}
ctx->entry = entry;
ctx->context = context;
thread->cancelled = 0;
#if defined(LC_WINDOWS)
{
thread->handle = CreateThread(NULL, 0, ThreadProc, ctx, 0, NULL);
if (thread->handle == NULL) {
free(ctx);
return -1;
}
}
#else
{
int err = pthread_create(&thread->thread, NULL, ThreadProc, ctx);
if (err != 0) {
free(ctx);
return err;
}
}
#endif
running_threads++;
return 0;
}
int PltCreateEvent(PLT_EVENT* event) {
#if defined(LC_WINDOWS)
*event = CreateEventEx(NULL, NULL, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);
if (!*event) {
return -1;
}
return 0;
#else
pthread_mutex_init(&event->mutex, NULL);
pthread_cond_init(&event->cond, NULL);
event->signalled = 0;
return 0;
#endif
}
void PltCloseEvent(PLT_EVENT* event) {
#if defined(LC_WINDOWS)
CloseHandle(*event);
#else
pthread_mutex_destroy(&event->mutex);
pthread_cond_destroy(&event->cond);
#endif
}
void PltSetEvent(PLT_EVENT* event) {
#if defined(LC_WINDOWS)
SetEvent(*event);
#else
event->signalled = 1;
pthread_cond_broadcast(&event->cond);
#endif
}
void PltClearEvent(PLT_EVENT* event) {
#if defined(LC_WINDOWS)
ResetEvent(*event);
#else
event->signalled = 0;
#endif
}
int PltWaitForEvent(PLT_EVENT* event) {
#if defined(LC_WINDOWS)
DWORD error;
error = WaitForSingleObjectEx(*event, INFINITE, FALSE);
if (error == WAIT_OBJECT_0) {
return PLT_WAIT_SUCCESS;
}
else {
LC_ASSERT(0);
return -1;
}
#else
pthread_mutex_lock(&event->mutex);
while (!event->signalled) {
pthread_cond_wait(&event->cond, &event->mutex);
}
pthread_mutex_unlock(&event->mutex);
return PLT_WAIT_SUCCESS;
#endif
}
uint64_t PltGetMillis(void) {
#if defined(LC_WINDOWS)
return GetTickCount64();
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
#endif
}
int initializePlatform(void) {
int err;
err = initializePlatformSockets();
if (err != 0) {
return err;
}
err = enet_initialize();
if (err != 0) {
return err;
}
return 0;
}
void cleanupPlatform(void) {
cleanupPlatformSockets();
enet_deinitialize();
LC_ASSERT(running_threads == 0);
}

63
src/Platform.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Winsock2.h>
#include <ws2tcpip.h>
#else
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
#ifdef _WIN32
# define LC_WINDOWS
#else
# define LC_POSIX
# if defined(__APPLE__)
# define LC_DARWIN
# endif
#endif
#include <stdio.h>
#include "Limelight.h"
#if defined(LC_WINDOWS)
void LimelogWindows(char* Format, ...);
#define Limelog(s, ...) \
LimelogWindows(s, ##__VA_ARGS__)
#else
#define Limelog(s, ...) \
fprintf(stderr, s, ##__VA_ARGS__)
#endif
#if defined(LC_WINDOWS)
#include <crtdbg.h>
#ifdef LC_DEBUG
#define LC_ASSERT(x) __analysis_assume(x); \
_ASSERTE(x)
#else
#define LC_ASSERT(x)
#endif
#else
#ifndef LC_DEBUG
#ifndef NDEBUG
#define NDEBUG
#endif
#endif
#include <assert.h>
#define LC_ASSERT(x) assert(x)
#endif
int initializePlatform(void);
void cleanupPlatform(void);
uint64_t PltGetMillis(void);

246
src/PlatformSockets.c Normal file
View File

@@ -0,0 +1,246 @@
#include "PlatformSockets.h"
#include "Limelight-internal.h"
void addrToUrlSafeString(struct sockaddr_storage* addr, char* string)
{
char addrstr[INET6_ADDRSTRLEN];
if (addr->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr;
inet_ntop(addr->ss_family, &sin6->sin6_addr, addrstr, sizeof(addrstr));
// IPv6 addresses need to be enclosed in brackets for URLs
sprintf(string, "[%s]", addrstr);
}
else {
struct sockaddr_in* sin = (struct sockaddr_in*)addr;
inet_ntop(addr->ss_family, &sin->sin_addr, addrstr, sizeof(addrstr));
// IPv4 addresses are returned without changes
sprintf(string, "%s", addrstr);
}
}
void shutdownTcpSocket(SOCKET s) {
// Calling shutdown() prior to close wakes up callers
// blocked in connect(), recv(), and friends.
shutdown(s, SHUT_RDWR);
}
void setRecvTimeout(SOCKET s, int timeoutSec) {
#if defined(LC_WINDOWS)
int val = timeoutSec * 1000;
#else
struct timeval val;
val.tv_sec = timeoutSec;
val.tv_usec = 0;
#endif
if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&val, sizeof(val)) < 0) {
Limelog("setsockopt(SO_RCVTIMEO) failed: %d\n", (int)LastSocketError());
}
}
int recvUdpSocket(SOCKET s, char* buffer, int size) {
fd_set readfds;
int err;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(s, &readfds);
// Wait up to 500 ms for the socket to be readable
tv.tv_sec = 0;
tv.tv_usec = 500 * 1000;
err = select((int)(s) + 1, &readfds, NULL, NULL, &tv);
if (err <= 0) {
// Return if an error or timeout occurs
return err;
}
// This won't block since the socket is readable
return (int)recv(s, buffer, size, 0);
}
void closeSocket(SOCKET s) {
#if defined(LC_WINDOWS)
closesocket(s);
#else
close(s);
#endif
}
SOCKET bindUdpSocket(int addrfamily, int bufferSize) {
SOCKET s;
struct sockaddr_storage addr;
int err;
LC_ASSERT(addrfamily == AF_INET || addrfamily == AF_INET6);
s = socket(addrfamily, SOCK_DGRAM, IPPROTO_UDP);
if (s == INVALID_SOCKET) {
Limelog("socket() failed: %d\n", (int)LastSocketError());
return INVALID_SOCKET;
}
memset(&addr, 0, sizeof(addr));
addr.ss_family = addrfamily;
if (bind(s, (struct sockaddr*) &addr,
addrfamily == AF_INET ?
sizeof(struct sockaddr_in) :
sizeof(struct sockaddr_in6)) == SOCKET_ERROR) {
err = LastSocketError();
Limelog("bind() failed: %d\n", err);
closeSocket(s);
SetLastSocketError(err);
return INVALID_SOCKET;
}
#ifdef LC_DARWIN
{
// Disable SIGPIPE on iOS
int val = 1;
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (char*)&val, sizeof(val));
}
#endif
setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char*)&bufferSize, sizeof(bufferSize));
return s;
}
SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen, unsigned short port, int timeoutSec) {
SOCKET s;
struct sockaddr_in6 addr;
int err;
#if defined(LC_DARWIN) || defined(FIONBIO)
int val;
#endif
s = socket(dstaddr->ss_family, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET) {
Limelog("socket() failed: %d\n", (int)LastSocketError());
return INVALID_SOCKET;
}
#ifdef LC_DARWIN
// Disable SIGPIPE on iOS
val = 1;
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (char*)&val, sizeof(val));
#endif
#ifdef FIONBIO
// Enable non-blocking I/O for connect timeout support
val = 1;
ioctlsocket(s, FIONBIO, &val);
#endif
// Start connection
memcpy(&addr, dstaddr, sizeof(addr));
addr.sin6_port = htons(port);
err = connect(s, (struct sockaddr*) &addr, addrlen);
if (err < 0) {
err = (int)LastSocketError();
}
#ifdef FIONBIO
{
fd_set writefds, exceptfds;
struct timeval tv;
FD_ZERO(&writefds);
FD_ZERO(&exceptfds);
FD_SET(s, &writefds);
FD_SET(s, &exceptfds);
tv.tv_sec = timeoutSec;
tv.tv_usec = 0;
// Wait for the connection to complete or the timeout to elapse
err = select(s + 1, NULL, &writefds, &exceptfds, &tv);
if (err < 0) {
// select() failed
err = LastSocketError();
Limelog("select() failed: %d\n", err);
closeSocket(s);
SetLastSocketError(err);
return INVALID_SOCKET;
}
else if (err == 0) {
// select() timed out
Limelog("select() timed out after %d seconds\n", timeoutSec);
closeSocket(s);
#if defined(LC_WINDOWS)
SetLastSocketError(WSAEWOULDBLOCK);
#else
SetLastSocketError(EWOULDBLOCK);
#endif
return INVALID_SOCKET;
}
else if (FD_ISSET(s, &writefds) || FD_ISSET(s, &exceptfds)) {
// The socket was signalled
SOCKADDR_LEN len = sizeof(err);
getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&err, &len);
if (err != 0 || FD_ISSET(s, &exceptfds)) {
// Get the error code
err = (err != 0) ? err : LastSocketFail();
}
}
// Disable non-blocking I/O now that the connection is established
val = 0;
ioctlsocket(s, FIONBIO, &val);
}
#endif
if (err != 0) {
Limelog("connect() failed: %d\n", err);
closeSocket(s);
SetLastSocketError(err);
return INVALID_SOCKET;
}
return s;
}
int enableNoDelay(SOCKET s) {
int err;
int val;
val = 1;
err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val));
if (err == SOCKET_ERROR) {
return LastSocketError();
}
return 0;
}
int initializePlatformSockets(void) {
#if defined(LC_WINDOWS)
WSADATA data;
return WSAStartup(MAKEWORD(2, 0), &data);
#elif defined(LC_POSIX) && !defined(LC_CHROME)
// Disable SIGPIPE signals to avoid us getting
// killed when a socket gets an EPIPE error
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
if (sigaction(SIGPIPE, &sa, 0) == -1) {
perror("sigaction");
return -1;
}
return 0;
#else
return 0;
#endif
}
void cleanupPlatformSockets(void) {
#if defined(LC_WINDOWS)
WSACleanup();
#else
#endif
}

51
src/PlatformSockets.h Normal file
View File

@@ -0,0 +1,51 @@
#pragma once
#include "Limelight.h"
#include "Platform.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#define SetLastSocketError(x) WSASetLastError(x)
#define LastSocketError() WSAGetLastError()
#define SHUT_RDWR SD_BOTH
typedef int SOCK_RET;
typedef int SOCKADDR_LEN;
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#define ioctlsocket ioctl
#define LastSocketError() errno
#define SetLastSocketError(x) errno = x
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
typedef int SOCKET;
typedef ssize_t SOCK_RET;
typedef socklen_t SOCKADDR_LEN;
#endif
#define LastSocketFail() ((LastSocketError() != 0) ? LastSocketError() : -1)
// IPv6 addresses have 2 extra characters for URL escaping
#define URLSAFESTRING_LEN (INET6_ADDRSTRLEN+2)
void addrToUrlSafeString(struct sockaddr_storage* addr, char* string);
SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen, unsigned short port, int timeoutSec);
SOCKET bindUdpSocket(int addrfamily, int bufferSize);
int enableNoDelay(SOCKET s);
int recvUdpSocket(SOCKET s, char* buffer, int size);
void shutdownTcpSocket(SOCKET s);
void setRecvTimeout(SOCKET s, int timeoutSec);
void closeSocket(SOCKET s);

52
src/PlatformThreads.h Executable file
View File

@@ -0,0 +1,52 @@
#pragma once
#include "Limelight.h"
#include "Platform.h"
typedef void(*ThreadEntry)(void* context);
#if defined(LC_WINDOWS)
typedef HANDLE PLT_MUTEX;
typedef HANDLE PLT_EVENT;
typedef struct _PLT_THREAD {
HANDLE handle;
int cancelled;
} PLT_THREAD;
#elif defined (LC_POSIX)
typedef pthread_mutex_t PLT_MUTEX;
typedef struct _PLT_EVENT {
pthread_mutex_t mutex;
pthread_cond_t cond;
int signalled;
} PLT_EVENT;
typedef struct _PLT_THREAD {
pthread_t thread;
int cancelled;
} PLT_THREAD;
#else
#error Unsupported platform
#endif
int PltCreateMutex(PLT_MUTEX* mutex);
void PltDeleteMutex(PLT_MUTEX* mutex);
void PltLockMutex(PLT_MUTEX* mutex);
void PltUnlockMutex(PLT_MUTEX* mutex);
int PltCreateThread(ThreadEntry entry, void* context, PLT_THREAD*thread);
void PltCloseThread(PLT_THREAD*thread);
void PltInterruptThread(PLT_THREAD*thread);
int PltIsThreadInterrupted(PLT_THREAD*thread);
void PltJoinThread(PLT_THREAD*thread);
int PltCreateEvent(PLT_EVENT* event);
void PltCloseEvent(PLT_EVENT* event);
void PltSetEvent(PLT_EVENT* event);
void PltClearEvent(PLT_EVENT* event);
int PltWaitForEvent(PLT_EVENT* event);
void PltRunThreadProc(void);
#define PLT_WAIT_SUCCESS 0
#define PLT_WAIT_INTERRUPTED 1
void PltSleepMs(int ms);

265
src/RtpReorderQueue.c Normal file
View File

@@ -0,0 +1,265 @@
#include "Limelight-internal.h"
#include "RtpReorderQueue.h"
void RtpqInitializeQueue(PRTP_REORDER_QUEUE queue, int maxSize, int maxQueueTimeMs) {
queue->maxSize = maxSize;
queue->maxQueueTimeMs = maxQueueTimeMs;
queue->queueHead = NULL;
queue->queueTail = NULL;
queue->nextRtpSequenceNumber = UINT16_MAX;
queue->oldestQueuedTimeMs = UINT64_MAX;
queue->oldestQueuedEntry = NULL;
}
void RtpqCleanupQueue(PRTP_REORDER_QUEUE queue) {
while (queue->queueHead != NULL) {
PRTP_QUEUE_ENTRY entry = queue->queueHead;
queue->queueHead = entry->next;
free(entry->packet);
}
}
// newEntry is contained within the packet buffer so we free the whole entry by freeing entry->packet
static int queuePacket(PRTP_REORDER_QUEUE queue, PRTP_QUEUE_ENTRY newEntry, int head, PRTP_PACKET packet) {
if (queue->nextRtpSequenceNumber != UINT16_MAX) {
PRTP_QUEUE_ENTRY entry;
// Don't queue packets we're already ahead of
if (isBeforeSignedInt(packet->sequenceNumber, queue->nextRtpSequenceNumber, 0)) {
return 0;
}
// Don't queue duplicates either
entry = queue->queueHead;
while (entry != NULL) {
if (entry->packet->sequenceNumber == packet->sequenceNumber) {
return 0;
}
entry = entry->next;
}
}
newEntry->packet = packet;
newEntry->queueTimeMs = PltGetMillis();
newEntry->prev = NULL;
newEntry->next = NULL;
if (queue->oldestQueuedTimeMs == UINT64_MAX) {
queue->oldestQueuedTimeMs = newEntry->queueTimeMs;
queue->oldestQueuedEntry = newEntry;
}
if (queue->queueHead == NULL) {
LC_ASSERT(queue->queueSize == 0);
queue->queueHead = queue->queueTail = newEntry;
}
else if (head) {
LC_ASSERT(queue->queueSize > 0);
PRTP_QUEUE_ENTRY oldHead = queue->queueHead;
newEntry->next = oldHead;
LC_ASSERT(oldHead->prev == NULL);
oldHead->prev = newEntry;
queue->queueHead = newEntry;
}
else {
LC_ASSERT(queue->queueSize > 0);
PRTP_QUEUE_ENTRY oldTail = queue->queueTail;
newEntry->prev = oldTail;
LC_ASSERT(oldTail->next == NULL);
oldTail->next = newEntry;
queue->queueTail = newEntry;
}
queue->queueSize++;
return 1;
}
static void updateOldestQueued(PRTP_REORDER_QUEUE queue) {
PRTP_QUEUE_ENTRY entry;
queue->oldestQueuedTimeMs = UINT64_MAX;
queue->oldestQueuedEntry = NULL;
entry = queue->queueHead;
while (entry != NULL) {
if (entry->queueTimeMs < queue->oldestQueuedTimeMs) {
queue->oldestQueuedEntry = entry;
queue->oldestQueuedTimeMs = entry->queueTimeMs;
}
entry = entry->next;
}
}
static PRTP_QUEUE_ENTRY getEntryByLowestSeq(PRTP_REORDER_QUEUE queue) {
PRTP_QUEUE_ENTRY lowestSeqEntry, entry;
lowestSeqEntry = queue->queueHead;
entry = queue->queueHead;
while (entry != NULL) {
if (isBeforeSignedInt(entry->packet->sequenceNumber, lowestSeqEntry->packet->sequenceNumber, 1)) {
lowestSeqEntry = entry;
}
entry = entry->next;
}
// Remember the updated lowest sequence number
if (lowestSeqEntry != NULL) {
queue->nextRtpSequenceNumber = lowestSeqEntry->packet->sequenceNumber;
}
return lowestSeqEntry;
}
static void removeEntry(PRTP_REORDER_QUEUE queue, PRTP_QUEUE_ENTRY entry) {
LC_ASSERT(entry != NULL);
LC_ASSERT(queue->queueSize > 0);
LC_ASSERT(queue->queueHead != NULL);
LC_ASSERT(queue->queueTail != NULL);
if (queue->queueHead == entry) {
queue->queueHead = entry->next;
}
if (queue->queueTail == entry) {
queue->queueTail = entry->prev;
}
if (entry->prev != NULL) {
entry->prev->next = entry->next;
}
if (entry->next != NULL) {
entry->next->prev = entry->prev;
}
queue->queueSize--;
}
static PRTP_QUEUE_ENTRY validateQueueConstraints(PRTP_REORDER_QUEUE queue) {
int needsUpdate = 0;
// Empty queue is fine
if (queue->queueHead == NULL) {
return NULL;
}
// Check that the queue's time constraint is satisfied
if (PltGetMillis() - queue->oldestQueuedTimeMs > queue->maxQueueTimeMs) {
Limelog("Discarding RTP packet queued for too long\n");
removeEntry(queue, queue->oldestQueuedEntry);
free(queue->oldestQueuedEntry->packet);
needsUpdate = 1;
}
// Check that the queue's size constraint is satisfied
if (!needsUpdate && queue->queueSize == queue->maxSize) {
Limelog("Discarding RTP packet after queue overgrowth\n");
removeEntry(queue, queue->oldestQueuedEntry);
free(queue->oldestQueuedEntry->packet);
needsUpdate = 1;
}
if (needsUpdate) {
// Recalculate the oldest entry if needed
updateOldestQueued(queue);
// Return the lowest seq queued
return getEntryByLowestSeq(queue);
}
else {
return NULL;
}
}
int RtpqAddPacket(PRTP_REORDER_QUEUE queue, PRTP_PACKET packet, PRTP_QUEUE_ENTRY packetEntry) {
if (queue->nextRtpSequenceNumber != UINT16_MAX &&
isBeforeSignedInt(packet->sequenceNumber, queue->nextRtpSequenceNumber, 0)) {
// Reject packets behind our current sequence number
return RTPQ_RET_REJECTED;
}
if (queue->queueHead == NULL) {
// Return immediately for an exact match with an empty queue
if (queue->nextRtpSequenceNumber == UINT16_MAX ||
packet->sequenceNumber == queue->nextRtpSequenceNumber) {
queue->nextRtpSequenceNumber = packet->sequenceNumber + 1;
return RTPQ_RET_HANDLE_IMMEDIATELY;
}
else {
// Queue is empty currently so we'll put this packet on there
if (!queuePacket(queue, packetEntry, 0, packet)) {
return RTPQ_RET_REJECTED;
}
else {
return RTPQ_RET_QUEUED_NOTHING_READY;
}
}
}
else {
PRTP_QUEUE_ENTRY lowestEntry;
// Validate that the queue remains within our contraints
// and get the lowest element
lowestEntry = validateQueueConstraints(queue);
// If the queue is now empty after validating queue constraints,
// this packet can be returned immediately
if (lowestEntry == NULL && queue->queueHead == NULL) {
queue->nextRtpSequenceNumber = packet->sequenceNumber + 1;
return RTPQ_RET_HANDLE_IMMEDIATELY;
}
// Queue has data inside, so we need to see where this packet fits
if (packet->sequenceNumber == queue->nextRtpSequenceNumber) {
// It fits in a hole where we need a packet, now we have some ready
if (!queuePacket(queue, packetEntry, 0, packet)) {
return RTPQ_RET_REJECTED;
}
else {
return RTPQ_RET_QUEUED_PACKETS_READY;
}
}
else {
if (!queuePacket(queue, packetEntry, 0, packet)) {
return RTPQ_RET_REJECTED;
}
else {
// Constraint validation may have changed the oldest packet to one that
// matches the next sequence number
return (lowestEntry != NULL) ? RTPQ_RET_QUEUED_PACKETS_READY :
RTPQ_RET_QUEUED_NOTHING_READY;
}
}
}
}
PRTP_PACKET RtpqGetQueuedPacket(PRTP_REORDER_QUEUE queue) {
PRTP_QUEUE_ENTRY queuedEntry, entry;
// Find the next queued packet
queuedEntry = NULL;
entry = queue->queueHead;
while (entry != NULL) {
if (entry->packet->sequenceNumber == queue->nextRtpSequenceNumber) {
queue->nextRtpSequenceNumber++;
queuedEntry = entry;
removeEntry(queue, entry);
break;
}
entry = entry->next;
}
// Bail if we found nothing
if (queuedEntry == NULL) {
// Update the oldest queued packet time
updateOldestQueued(queue);
return NULL;
}
// We don't update the oldest queued entry here, because we know
// the caller will call again until it receives null
return queuedEntry->packet;
}

39
src/RtpReorderQueue.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include "Video.h"
#define RTPQ_DEFAULT_MAX_SIZE 16
#define RTPQ_DEFAULT_QUEUE_TIME 40
typedef struct _RTP_QUEUE_ENTRY {
PRTP_PACKET packet;
uint64_t queueTimeMs;
struct _RTP_QUEUE_ENTRY* next;
struct _RTP_QUEUE_ENTRY* prev;
} RTP_QUEUE_ENTRY, *PRTP_QUEUE_ENTRY;
typedef struct _RTP_REORDER_QUEUE {
int maxSize;
int maxQueueTimeMs;
PRTP_QUEUE_ENTRY queueHead;
PRTP_QUEUE_ENTRY queueTail;
int queueSize;
unsigned short nextRtpSequenceNumber;
uint64_t oldestQueuedTimeMs;
PRTP_QUEUE_ENTRY oldestQueuedEntry;
} RTP_REORDER_QUEUE, *PRTP_REORDER_QUEUE;
#define RTPQ_RET_HANDLE_IMMEDIATELY 0
#define RTPQ_RET_QUEUED_NOTHING_READY 1
#define RTPQ_RET_QUEUED_PACKETS_READY 2
#define RTPQ_RET_REJECTED 3
void RtpqInitializeQueue(PRTP_REORDER_QUEUE queue, int maxSize, int maxQueueTimeMs);
void RtpqCleanupQueue(PRTP_REORDER_QUEUE queue);
int RtpqAddPacket(PRTP_REORDER_QUEUE queue, PRTP_PACKET packet, PRTP_QUEUE_ENTRY packetEntry);
PRTP_PACKET RtpqGetQueuedPacket(PRTP_REORDER_QUEUE queue);

66
src/Rtsp.h Normal file
View File

@@ -0,0 +1,66 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define TYPE_REQUEST 0
#define TYPE_RESPONSE 1
#define TOKEN_OPTION 0
#define RTSP_ERROR_SUCCESS 0
#define RTSP_ERROR_NO_MEMORY -1
#define RTSP_ERROR_MALFORMED -2
#define SEQ_INVALID -1
#define FLAG_ALLOCATED_OPTION_FIELDS 0x1
#define FLAG_ALLOCATED_MESSAGE_BUFFER 0x2
#define FLAG_ALLOCATED_OPTION_ITEMS 0x4
#define FLAG_ALLOCATED_PAYLOAD 0x8
#define CRLF_LENGTH 2
#define MESSAGE_END_LENGTH (2 + CRLF_LENGTH)
typedef struct _OPTION_ITEM {
char flags;
char* option;
char* content;
struct _OPTION_ITEM* next;
} OPTION_ITEM, *POPTION_ITEM;
// In this implementation, a flag indicates the message type:
// TYPE_REQUEST = 0
// TYPE_RESPONSE = 1
typedef struct _RTSP_MESSAGE {
char type;
char flags;
int sequenceNumber;
char* protocol;
POPTION_ITEM options;
char* payload;
int payloadLength;
char* messageBuffer;
union {
struct {
// Request fields
char* command;
char* target;
} request;
struct {
// Response fields
char* statusString;
int statusCode;
} response;
} message;
} RTSP_MESSAGE, *PRTSP_MESSAGE;
int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length);
void freeMessage(PRTSP_MESSAGE msg);
void createRtspResponse(PRTSP_MESSAGE msg, char* messageBuffer, int flags, char* protocol, int statusCode, char* statusString, int sequenceNumber, POPTION_ITEM optionsHead, char* payload, int payloadLength);
void createRtspRequest(PRTSP_MESSAGE msg, char* messageBuffer, int flags, char* command, char* target, char* protocol, int sequenceNumber, POPTION_ITEM optionsHead, char* payload, int payloadLength);
char* getOptionContent(POPTION_ITEM optionsHead, char* option);
void insertOption(POPTION_ITEM* optionsHead, POPTION_ITEM opt);
void freeOptionList(POPTION_ITEM optionsHead);
char* serializeRtspMessage(PRTSP_MESSAGE msg, int* serializedLength);

715
src/RtspConnection.c Normal file
View File

@@ -0,0 +1,715 @@
#include "Limelight-internal.h"
#include "Rtsp.h"
#include <enet/enet.h>
#define RTSP_MAX_RESP_SIZE 32768
#define RTSP_TIMEOUT_SEC 10
static int currentSeqNumber;
static char rtspTargetUrl[256];
static char sessionIdString[16];
static int hasSessionId;
static char responseBuffer[RTSP_MAX_RESP_SIZE];
static int rtspClientVersion;
static SOCKET sock = INVALID_SOCKET;
static ENetHost* client;
static ENetPeer* peer;
// Create RTSP Option
static POPTION_ITEM createOptionItem(char* option, char* content)
{
POPTION_ITEM item = malloc(sizeof(*item));
if (item == NULL) {
return NULL;
}
item->option = malloc(strlen(option) + 1);
if (item->option == NULL) {
free(item);
return NULL;
}
strcpy(item->option, option);
item->content = malloc(strlen(content) + 1);
if (item->content == NULL) {
free(item->option);
free(item);
return NULL;
}
strcpy(item->content, content);
item->next = NULL;
item->flags = FLAG_ALLOCATED_OPTION_FIELDS;
return item;
}
// Add an option to the RTSP Message
static int addOption(PRTSP_MESSAGE msg, char* option, char* content)
{
POPTION_ITEM item = createOptionItem(option, content);
if (item == NULL) {
return 0;
}
insertOption(&msg->options, item);
msg->flags |= FLAG_ALLOCATED_OPTION_ITEMS;
return 1;
}
// Create an RTSP Request
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", clientVersionStr)) {
freeMessage(msg);
return 0;
}
return 1;
}
// Send RTSP message and get response over ENet
static int transactRtspMessageEnet(PRTSP_MESSAGE request, PRTSP_MESSAGE response, int expectingPayload, int* error) {
ENetEvent event;
char* serializedMessage;
int messageLen;
int offset;
ENetPacket* packet;
char* payload;
int payloadLength;
int ret;
// We're going to handle the payload separately, so temporarily set the payload to NULL
payload = request->payload;
payloadLength = request->payloadLength;
request->payload = NULL;
request->payloadLength = 0;
// Serialize the RTSP message into a message buffer
serializedMessage = serializeRtspMessage(request, &messageLen);
if (serializedMessage == NULL) {
ret = 0;
goto Exit;
}
// Create the reliable packet that describes our outgoing message
packet = enet_packet_create(serializedMessage, messageLen, ENET_PACKET_FLAG_RELIABLE);
if (packet == NULL) {
ret = 0;
goto Exit;
}
// Send the message
if (enet_peer_send(peer, 0, packet) < 0) {
enet_packet_destroy(packet);
ret = 0;
goto Exit;
}
enet_host_flush(client);
// If we have a payload to send, we'll need to send that separately
if (payload != NULL) {
packet = enet_packet_create(payload, payloadLength, ENET_PACKET_FLAG_RELIABLE);
if (packet == NULL) {
ret = 0;
goto Exit;
}
// Send the payload
if (enet_peer_send(peer, 0, packet) < 0) {
enet_packet_destroy(packet);
ret = 0;
goto Exit;
}
enet_host_flush(client);
}
// Wait for a reply
if (enet_host_service(client, &event, RTSP_TIMEOUT_SEC * 1000) <= 0 ||
event.type != ENET_EVENT_TYPE_RECEIVE) {
Limelog("Failed to receive RTSP reply\n");
ret = 0;
goto Exit;
}
if (event.packet->dataLength > RTSP_MAX_RESP_SIZE) {
Limelog("RTSP message too long\n");
ret = 0;
goto Exit;
}
// Copy the data out and destroy the packet
memcpy(responseBuffer, event.packet->data, event.packet->dataLength);
offset = (int) event.packet->dataLength;
enet_packet_destroy(event.packet);
// Wait for the payload if we're expecting some
if (expectingPayload) {
// The payload comes in a second packet
if (enet_host_service(client, &event, RTSP_TIMEOUT_SEC * 1000) <= 0 ||
event.type != ENET_EVENT_TYPE_RECEIVE) {
Limelog("Failed to receive RTSP reply payload\n");
ret = 0;
goto Exit;
}
if (event.packet->dataLength + offset > RTSP_MAX_RESP_SIZE) {
Limelog("RTSP message payload too long\n");
ret = 0;
goto Exit;
}
// Copy the payload out to the end of the response buffer and destroy the packet
memcpy(&responseBuffer[offset], event.packet->data, event.packet->dataLength);
offset += (int) event.packet->dataLength;
enet_packet_destroy(event.packet);
}
if (parseRtspMessage(response, responseBuffer, offset) == RTSP_ERROR_SUCCESS) {
// Successfully parsed response
ret = 1;
}
else {
Limelog("Failed to parse RTSP response\n");
ret = 0;
}
Exit:
// Swap back the payload pointer to avoid leaking memory later
request->payload = payload;
request->payloadLength = payloadLength;
// Free the serialized buffer
if (serializedMessage != NULL) {
free(serializedMessage);
}
return ret;
}
// Send RTSP message and get response over TCP
static int transactRtspMessageTcp(PRTSP_MESSAGE request, PRTSP_MESSAGE response, int expectingPayload, int* error) {
SOCK_RET err;
int ret = 0;
int offset;
char* serializedMessage = NULL;
int messageLen;
*error = -1;
sock = connectTcpSocket(&RemoteAddr, RemoteAddrLen, 48010, RTSP_TIMEOUT_SEC);
if (sock == INVALID_SOCKET) {
*error = LastSocketError();
return ret;
}
enableNoDelay(sock);
setRecvTimeout(sock, RTSP_TIMEOUT_SEC);
serializedMessage = serializeRtspMessage(request, &messageLen);
if (serializedMessage == NULL) {
closeSocket(sock);
sock = INVALID_SOCKET;
return ret;
}
// Send our message
err = send(sock, serializedMessage, messageLen, 0);
if (err == SOCKET_ERROR) {
*error = LastSocketError();
Limelog("Failed to send RTSP message: %d\n", *error);
goto Exit;
}
// Read the response until the server closes the connection
offset = 0;
for (;;) {
err = recv(sock, &responseBuffer[offset], RTSP_MAX_RESP_SIZE - offset, 0);
if (err <= 0) {
// Done reading
break;
}
offset += err;
// Warn if the RTSP message is too big
if (offset == RTSP_MAX_RESP_SIZE) {
Limelog("RTSP message too long\n");
goto Exit;
}
}
if (parseRtspMessage(response, responseBuffer, offset) == RTSP_ERROR_SUCCESS) {
// Successfully parsed response
ret = 1;
}
else {
Limelog("Failed to parse RTSP response\n");
}
Exit:
if (serializedMessage != NULL) {
free(serializedMessage);
}
closeSocket(sock);
sock = INVALID_SOCKET;
return ret;
}
static int transactRtspMessage(PRTSP_MESSAGE request, PRTSP_MESSAGE response, int expectingPayload, int* error) {
// Gen 5+ does RTSP over ENet not TCP
if (ServerMajorVersion >= 5) {
return transactRtspMessageEnet(request, response, expectingPayload, error);
}
else {
return transactRtspMessageTcp(request, response, expectingPayload, error);
}
}
// Terminate the RTSP Handshake process by shutting down the socket.
// The thread waiting on RTSP will close the socket.
void terminateRtspHandshake(void) {
if (sock != INVALID_SOCKET) {
shutdownTcpSocket(sock);
}
if (peer != NULL) {
enet_peer_disconnect_now(peer, 0);
}
}
// Send RTSP OPTIONS request
static int requestOptions(PRTSP_MESSAGE response, int* error) {
RTSP_MESSAGE request;
int ret;
*error = -1;
ret = initializeRtspRequest(&request, "OPTIONS", rtspTargetUrl);
if (ret != 0) {
ret = transactRtspMessage(&request, response, 0, error);
freeMessage(&request);
}
return ret;
}
// Send RTSP DESCRIBE request
static int requestDescribe(PRTSP_MESSAGE response, int* error) {
RTSP_MESSAGE request;
int ret;
*error = -1;
ret = initializeRtspRequest(&request, "DESCRIBE", rtspTargetUrl);
if (ret != 0) {
if (addOption(&request, "Accept",
"application/sdp") &&
addOption(&request, "If-Modified-Since",
"Thu, 01 Jan 1970 00:00:00 GMT")) {
ret = transactRtspMessage(&request, response, 1, error);
}
else {
ret = 0;
}
freeMessage(&request);
}
return ret;
}
// Send RTSP SETUP request
static int setupStream(PRTSP_MESSAGE response, char* target, int* error) {
RTSP_MESSAGE request;
int ret;
char* transportValue;
*error = -1;
ret = initializeRtspRequest(&request, "SETUP", target);
if (ret != 0) {
if (hasSessionId) {
if (!addOption(&request, "Session", sessionIdString)) {
ret = 0;
goto FreeMessage;
}
}
if (ServerMajorVersion >= 6) {
// It looks like GFE doesn't care what we say our port is but
// we need to give it some port to successfully complete the
// handshake process.
transportValue = "unicast;X-GS-ClientPort=50000-50001";
}
else {
transportValue = " ";
}
if (addOption(&request, "Transport", transportValue) &&
addOption(&request, "If-Modified-Since",
"Thu, 01 Jan 1970 00:00:00 GMT")) {
ret = transactRtspMessage(&request, response, 0, error);
}
else {
ret = 0;
}
FreeMessage:
freeMessage(&request);
}
return ret;
}
// Send RTSP PLAY request
static int playStream(PRTSP_MESSAGE response, char* target, int* error) {
RTSP_MESSAGE request;
int ret;
*error = -1;
ret = initializeRtspRequest(&request, "PLAY", target);
if (ret != 0) {
if (addOption(&request, "Session", sessionIdString)) {
ret = transactRtspMessage(&request, response, 0, error);
}
else {
ret = 0;
}
freeMessage(&request);
}
return ret;
}
// Send RTSP ANNOUNCE message
static int sendVideoAnnounce(PRTSP_MESSAGE response, int* error) {
RTSP_MESSAGE request;
int ret;
int payloadLength;
char payloadLengthStr[16];
*error = -1;
ret = initializeRtspRequest(&request, "ANNOUNCE", "streamid=video");
if (ret != 0) {
ret = 0;
if (!addOption(&request, "Session", sessionIdString) ||
!addOption(&request, "Content-type", "application/sdp")) {
goto FreeMessage;
}
request.payload = getSdpPayloadForStreamConfig(rtspClientVersion, &payloadLength);
if (request.payload == NULL) {
goto FreeMessage;
}
request.flags |= FLAG_ALLOCATED_PAYLOAD;
request.payloadLength = payloadLength;
sprintf(payloadLengthStr, "%d", payloadLength);
if (!addOption(&request, "Content-length", payloadLengthStr)) {
goto FreeMessage;
}
ret = transactRtspMessage(&request, response, 0, error);
FreeMessage:
freeMessage(&request);
}
return ret;
}
// Perform RTSP Handshake with the streaming server machine as part of the connection process
int performRtspHandshake(void) {
char urlAddr[URLSAFESTRING_LEN];
int ret;
// Initialize global state
addrToUrlSafeString(&RemoteAddr, urlAddr);
sprintf(rtspTargetUrl, "rtsp://%s", urlAddr);
currentSeqNumber = 1;
hasSessionId = 0;
switch (ServerMajorVersion) {
case 3:
rtspClientVersion = 10;
break;
case 4:
rtspClientVersion = 11;
break;
case 5:
rtspClientVersion = 12;
break;
case 6:
// Gen 6 has never been seen in the wild
rtspClientVersion = 13;
break;
case 7:
default:
rtspClientVersion = 14;
break;
}
// Gen 5 servers use ENet to do the RTSP handshake
if (ServerMajorVersion >= 5) {
ENetAddress address;
ENetEvent event;
// This will do DNS resolution if required
if (enet_address_set_host(&address, RemoteAddrString) < 0) {
return -1;
}
enet_address_set_port(&address, 48010);
// Create a client that can use 1 outgoing connection and 1 channel
client = enet_host_create(address.address.ss_family, NULL, 1, 1, 0, 0);
if (client == NULL) {
return -1;
}
// Connect to the host
peer = enet_host_connect(client, &address, 1, 0);
if (peer == NULL) {
enet_host_destroy(client);
client = NULL;
return -1;
}
// Wait for the connect to complete
if (enet_host_service(client, &event, RTSP_TIMEOUT_SEC * 1000) <= 0 ||
event.type != ENET_EVENT_TYPE_CONNECT) {
Limelog("RTSP: Failed to connect to UDP port 48010\n");
enet_peer_reset(peer);
peer = NULL;
enet_host_destroy(client);
client = NULL;
return -1;
}
// Ensure the connect verify ACK is sent immediately
enet_host_flush(client);
}
{
RTSP_MESSAGE response;
int error = -1;
if (!requestOptions(&response, &error)) {
Limelog("RTSP OPTIONS request failed: %d\n", error);
ret = error;
goto Exit;
}
if (response.message.response.statusCode != 200) {
Limelog("RTSP OPTIONS request failed: %d\n",
response.message.response.statusCode);
ret = response.message.response.statusCode;
goto Exit;
}
freeMessage(&response);
}
{
RTSP_MESSAGE response;
int error = -1;
if (!requestDescribe(&response, &error)) {
Limelog("RTSP DESCRIBE request failed: %d\n", error);
ret = error;
goto Exit;
}
if (response.message.response.statusCode != 200) {
Limelog("RTSP DESCRIBE request failed: %d\n",
response.message.response.statusCode);
ret = response.message.response.statusCode;
goto Exit;
}
// The RTSP DESCRIBE reply will contain a collection of SDP media attributes that
// describe the various supported video stream formats and include the SPS, PPS,
// and VPS (if applicable). We will use this information to determine whether the
// server can support HEVC. For some reason, they still set the MIME type of the HEVC
// format to H264, so we can't just look for the HEVC MIME type. What we'll do instead is
// look for the base 64 encoded VPS NALU prefix that is unique to the HEVC bitstream.
if (StreamConfig.supportsHevc && strstr(response.payload, "sprop-parameter-sets=AAAAAU")) {
NegotiatedVideoFormat = VIDEO_FORMAT_H265;
}
else {
NegotiatedVideoFormat = VIDEO_FORMAT_H264;
}
freeMessage(&response);
}
{
RTSP_MESSAGE response;
char* sessionId;
int error = -1;
if (!setupStream(&response,
ServerMajorVersion >= 5 ? "streamid=audio/0/0" : "streamid=audio",
&error)) {
Limelog("RTSP SETUP streamid=audio request failed: %d\n", error);
ret = error;
goto Exit;
}
if (response.message.response.statusCode != 200) {
Limelog("RTSP SETUP streamid=audio request failed: %d\n",
response.message.response.statusCode);
ret = response.message.response.statusCode;
goto Exit;
}
sessionId = getOptionContent(response.options, "Session");
if (sessionId == NULL) {
Limelog("RTSP SETUP streamid=audio is missing session attribute");
ret = -1;
goto Exit;
}
strcpy(sessionIdString, sessionId);
hasSessionId = 1;
freeMessage(&response);
}
{
RTSP_MESSAGE response;
int error = -1;
if (!setupStream(&response,
ServerMajorVersion >= 5 ? "streamid=video/0/0" : "streamid=video",
&error)) {
Limelog("RTSP SETUP streamid=video request failed: %d\n", error);
ret = error;
goto Exit;
}
if (response.message.response.statusCode != 200) {
Limelog("RTSP SETUP streamid=video request failed: %d\n",
response.message.response.statusCode);
ret = response.message.response.statusCode;
goto Exit;
}
freeMessage(&response);
}
if (ServerMajorVersion >= 5) {
RTSP_MESSAGE response;
int error = -1;
if (!setupStream(&response, "streamid=control/1/0", &error)) {
Limelog("RTSP SETUP streamid=control request failed: %d\n", error);
ret = error;
goto Exit;
}
if (response.message.response.statusCode != 200) {
Limelog("RTSP SETUP streamid=control request failed: %d\n",
response.message.response.statusCode);
ret = response.message.response.statusCode;
goto Exit;
}
freeMessage(&response);
}
{
RTSP_MESSAGE response;
int error = -1;
if (!sendVideoAnnounce(&response, &error)) {
Limelog("RTSP ANNOUNCE request failed: %d\n", error);
ret = error;
goto Exit;
}
if (response.message.response.statusCode != 200) {
Limelog("RTSP ANNOUNCE request failed: %d\n",
response.message.response.statusCode);
ret = response.message.response.statusCode;
goto Exit;
}
freeMessage(&response);
}
{
RTSP_MESSAGE response;
int error = -1;
if (!playStream(&response, "streamid=video", &error)) {
Limelog("RTSP PLAY streamid=video request failed: %d\n", error);
ret = error;
goto Exit;
}
if (response.message.response.statusCode != 200) {
Limelog("RTSP PLAY streamid=video failed: %d\n",
response.message.response.statusCode);
ret = response.message.response.statusCode;
goto Exit;
}
freeMessage(&response);
}
{
RTSP_MESSAGE response;
int error = -1;
if (!playStream(&response, "streamid=audio", &error)) {
Limelog("RTSP PLAY streamid=audio request failed: %d\n", error);
ret = error;
goto Exit;
}
if (response.message.response.statusCode != 200) {
Limelog("RTSP PLAY streamid=audio failed: %d\n",
response.message.response.statusCode);
ret = response.message.response.statusCode;
goto Exit;
}
freeMessage(&response);
}
ret = 0;
Exit:
// Cleanup the ENet stuff
if (ServerMajorVersion >= 5) {
if (peer != NULL) {
enet_peer_disconnect_now(peer, 0);
peer = NULL;
}
if (client != NULL) {
enet_host_destroy(client);
client = NULL;
}
}
return ret;
}

398
src/RtspParser.c Normal file
View File

@@ -0,0 +1,398 @@
#include "Rtsp.h"
// Check if String s begins with the given prefix
static int startsWith(const char* s, const char* prefix) {
if (strncmp(s, prefix, strlen(prefix)) == 0) {
return 1;
}
else {
return 0;
}
}
// Gets the length of the message
static int getMessageLength(PRTSP_MESSAGE msg) {
POPTION_ITEM current;
// Initialize to 1 for null terminator
size_t count = 1;
// Add the length of the protocol
count += strlen(msg->protocol);
// Add length of request-specific strings
if (msg->type == TYPE_REQUEST) {
count += strlen(msg->message.request.command);
count += strlen(msg->message.request.target);
// two spaces and \r\n
count += MESSAGE_END_LENGTH;
}
// Add length of response-specific strings
else {
char statusCodeStr[16];
sprintf(statusCodeStr, "%d", msg->message.response.statusCode);
count += strlen(statusCodeStr);
count += strlen(msg->message.response.statusString);
// two spaces and \r\n
count += MESSAGE_END_LENGTH;
}
// Count the size of the options
current = msg->options;
while (current != NULL) {
count += strlen(current->option);
count += strlen(current->content);
// :[space] and \r\n
count += MESSAGE_END_LENGTH;
current = current->next;
}
// /r/n ending
count += CRLF_LENGTH;
count += msg->payloadLength;
return (int)count;
}
// Given an RTSP message string rtspMessage, parse it into an RTSP_MESSAGE struct msg
int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
char* token;
char* protocol;
char* endCheck;
char* target;
char* statusStr;
char* command;
char* sequence;
char flag;
char messageEnded = 0;
char* payload = NULL;
char* opt = NULL;
int statusCode = 0;
int sequenceNum;
int exitCode;
POPTION_ITEM options = NULL;
POPTION_ITEM newOpt;
// Delimeter sets for strtok()
char* delim = " \r\n";
char* end = "\r\n";
char* optDelim = " :\r\n";
char typeFlag = TOKEN_OPTION;
// Put the raw message into a string we can use
char* messageBuffer = malloc(length + 1);
if (messageBuffer == NULL) {
exitCode = RTSP_ERROR_NO_MEMORY;
goto ExitFailure;
}
memcpy(messageBuffer, rtspMessage, length);
// The payload logic depends on a null-terminator at the end
messageBuffer[length] = 0;
// Get the first token of the message
token = strtok(messageBuffer, delim);
if (token == NULL) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
}
// The message is a response
if (startsWith(token, "RTSP")) {
flag = TYPE_RESPONSE;
// The current token is the protocol
protocol = token;
// Get the status code
token = strtok(NULL, delim);
statusCode = atoi(token);
if (token == NULL) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
}
// Get the status string
statusStr = strtok(NULL, end);
if (statusStr == NULL) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
}
// Request fields - we don't care about them here
target = NULL;
command = NULL;
}
// The message is a request
else {
flag = TYPE_REQUEST;
command = token;
target = strtok(NULL, delim);
if (target == NULL) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
}
protocol = strtok(NULL, delim);
if (protocol == NULL) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
}
// Response field - we don't care about it here
statusStr = NULL;
}
if (strcmp(protocol, "RTSP/1.0")) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
}
// Parse remaining options
while (token != NULL)
{
token = strtok(NULL, typeFlag == TOKEN_OPTION ? optDelim : end);
if (token != NULL) {
if (typeFlag == TOKEN_OPTION) {
opt = token;
}
// The token is content
else {
// Create a new node containing the option and content
newOpt = (POPTION_ITEM)malloc(sizeof(OPTION_ITEM));
if (newOpt == NULL) {
freeOptionList(options);
exitCode = RTSP_ERROR_NO_MEMORY;
goto ExitFailure;
}
newOpt->flags = 0;
newOpt->option = opt;
newOpt->content = token;
newOpt->next = NULL;
insertOption(&options, newOpt);
// Check if we're at the end of the message portion marked by \r\n\r\n
// endCheck points to the remainder of messageBuffer after the token
endCheck = &token[0] + strlen(token) + 1;
// See if we've hit the end of the message. The first \r is missing because it's been tokenized
if (startsWith(endCheck, "\n") && endCheck[1] == '\0') {
// RTSP over ENet doesn't always have the second CRLF for some reason
messageEnded = 1;
break;
}
else if (startsWith(endCheck, "\n\r\n")) {
// We've encountered the end of the message - mark it thus
messageEnded = 1;
// The payload is the remainder of messageBuffer. If none, then payload = null
if (endCheck[3] != '\0')
payload = &endCheck[3];
break;
}
}
}
typeFlag ^= 1; // flip the flag
}
// If we never encountered the double CRLF, then the message is malformed!
if (!messageEnded) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
}
// Get sequence number as an integer
sequence = getOptionContent(options, "CSeq");
if (sequence != NULL) {
sequenceNum = atoi(sequence);
}
else {
sequenceNum = SEQ_INVALID;
}
// Package the new parsed message into the struct
if (flag == TYPE_REQUEST) {
createRtspRequest(msg, messageBuffer, FLAG_ALLOCATED_MESSAGE_BUFFER | FLAG_ALLOCATED_OPTION_ITEMS, command, target,
protocol, sequenceNum, options, payload, payload ? length - (int)(messageBuffer - payload) : 0);
}
else {
createRtspResponse(msg, messageBuffer, FLAG_ALLOCATED_MESSAGE_BUFFER | FLAG_ALLOCATED_OPTION_ITEMS, protocol, statusCode,
statusStr, sequenceNum, options, payload, payload ? length - (int)(messageBuffer - payload) : 0);
}
return RTSP_ERROR_SUCCESS;
ExitFailure:
if (options) {
free(options);
}
if (messageBuffer) {
free(messageBuffer);
}
return exitCode;
}
// Create new RTSP message struct with response data
void createRtspResponse(PRTSP_MESSAGE msg, char* message, int flags, char* protocol,
int statusCode, char* statusString, int sequenceNumber, POPTION_ITEM optionsHead, char* payload, int payloadLength) {
msg->type = TYPE_RESPONSE;
msg->flags = flags;
msg->messageBuffer = message;
msg->protocol = protocol;
msg->options = optionsHead;
msg->payload = payload;
msg->payloadLength = payloadLength;
msg->sequenceNumber = sequenceNumber;
msg->message.response.statusString = statusString;
msg->message.response.statusCode = statusCode;
}
// Create new RTSP message struct with request data
void createRtspRequest(PRTSP_MESSAGE msg, char* message, int flags,
char* command, char* target, char* protocol, int sequenceNumber, POPTION_ITEM optionsHead, char* payload, int payloadLength) {
msg->type = TYPE_REQUEST;
msg->flags = flags;
msg->protocol = protocol;
msg->messageBuffer = message;
msg->options = optionsHead;
msg->payload = payload;
msg->payloadLength = payloadLength;
msg->sequenceNumber = sequenceNumber;
msg->message.request.command = command;
msg->message.request.target = target;
}
// Retrieves option content from the linked list given the option title
char* getOptionContent(POPTION_ITEM optionsHead, char* option) {
POPTION_ITEM current = optionsHead;
while (current != NULL) {
// Check if current node is what we're looking for
if (!strcmp(current->option, option)) {
return current->content;
}
current = current->next;
}
// Not found
return NULL;
}
// Adds new option opt to the struct's option list
void insertOption(POPTION_ITEM* optionsHead, POPTION_ITEM opt) {
POPTION_ITEM current = *optionsHead;
opt->next = NULL;
// Empty options list
if (*optionsHead == NULL) {
*optionsHead = opt;
return;
}
// Traverse the list and insert the new option at the end
while (current != NULL) {
// Check for duplicate option; if so, replace the option currently there
if (!strcmp(current->option, opt->option)) {
current->content = opt->content;
return;
}
if (current->next == NULL) {
current->next = opt;
return;
}
current = current->next;
}
}
// Free every node in the message's option list
void freeOptionList(POPTION_ITEM optionsHead) {
POPTION_ITEM current = optionsHead;
POPTION_ITEM temp;
while (current != NULL) {
temp = current;
current = current->next;
if (temp->flags & FLAG_ALLOCATED_OPTION_FIELDS) {
free(temp->option);
free(temp->content);
}
free(temp);
}
}
// Serialize the message struct into a string containing the RTSP message
char* serializeRtspMessage(PRTSP_MESSAGE msg, int* serializedLength) {
int size = getMessageLength(msg);
char* serializedMessage;
POPTION_ITEM current = msg->options;
char statusCodeStr[16];
serializedMessage = malloc(size);
if (serializedMessage == NULL) {
return NULL;
}
if (msg->type == TYPE_REQUEST) {
// command [space]
strcpy(serializedMessage, msg->message.request.command);
strcat(serializedMessage, " ");
// target [space]
strcat(serializedMessage, msg->message.request.target);
strcat(serializedMessage, " ");
// protocol \r\n
strcat(serializedMessage, msg->protocol);
strcat(serializedMessage, "\r\n");
}
else {
// protocol [space]
strcpy(serializedMessage, msg->protocol);
strcat(serializedMessage, " ");
// status code [space]
sprintf(statusCodeStr, "%d", msg->message.response.statusCode);
strcat(serializedMessage, statusCodeStr);
strcat(serializedMessage, " ");
// status str\r\n
strcat(serializedMessage, msg->message.response.statusString);
strcat(serializedMessage, "\r\n");
}
// option content\r\n
while (current != NULL) {
strcat(serializedMessage, current->option);
strcat(serializedMessage, ": ");
strcat(serializedMessage, current->content);
strcat(serializedMessage, "\r\n");
current = current->next;
}
// Final \r\n
strcat(serializedMessage, "\r\n");
// payload
if (msg->payload != NULL) {
int offset;
// Find end of the RTSP message header
for (offset = 0; serializedMessage[offset] != 0; offset++);
// Add the payload after
memcpy(&serializedMessage[offset], msg->payload, msg->payloadLength);
*serializedLength = offset + msg->payloadLength;
}
else {
*serializedLength = (int)strlen(serializedMessage);
}
return serializedMessage;
}
// Free everything in a msg struct
void freeMessage(PRTSP_MESSAGE msg) {
// If we've allocated the message buffer
if (msg->flags & FLAG_ALLOCATED_MESSAGE_BUFFER) {
free(msg->messageBuffer);
}
// If we've allocated any option items
if (msg->flags & FLAG_ALLOCATED_OPTION_ITEMS) {
freeOptionList(msg->options);
}
// If we've allocated the payload
if (msg->flags & FLAG_ALLOCATED_PAYLOAD) {
free(msg->payload);
}
}

332
src/SdpGenerator.c Normal file
View File

@@ -0,0 +1,332 @@
#include "Limelight-internal.h"
#define MAX_OPTION_NAME_LEN 128
#define MAX_SDP_HEADER_LEN 128
#define MAX_SDP_TAIL_LEN 128
#define CHANNEL_COUNT_STEREO 2
#define CHANNEL_COUNT_51_SURROUND 6
#define CHANNEL_MASK_STEREO 0x3
#define CHANNEL_MASK_51_SURROUND 0xFC
typedef struct _SDP_OPTION {
char name[MAX_OPTION_NAME_LEN + 1];
void* payload;
int payloadLen;
struct _SDP_OPTION* next;
} SDP_OPTION, *PSDP_OPTION;
// Cleanup the attribute list
static void freeAttributeList(PSDP_OPTION head) {
PSDP_OPTION next;
while (head != NULL) {
next = head->next;
free(head);
head = next;
}
}
// Get the size of the attribute list
static int getSerializedAttributeListSize(PSDP_OPTION head) {
PSDP_OPTION currentEntry = head;
size_t size = 0;
while (currentEntry != NULL) {
size += strlen("a=");
size += strlen(currentEntry->name);
size += strlen(":");
size += currentEntry->payloadLen;
size += strlen(" \r\n");
currentEntry = currentEntry->next;
}
return (int)size;
}
// Populate the serialized attribute list into a string
static int fillSerializedAttributeList(char* buffer, PSDP_OPTION head) {
PSDP_OPTION currentEntry = head;
int offset = 0;
while (currentEntry != NULL) {
offset += sprintf(&buffer[offset], "a=%s:", currentEntry->name);
memcpy(&buffer[offset], currentEntry->payload, currentEntry->payloadLen);
offset += currentEntry->payloadLen;
offset += sprintf(&buffer[offset], " \r\n");
currentEntry = currentEntry->next;
}
return offset;
}
// Add an attribute
static int addAttributeBinary(PSDP_OPTION* head, char* name, const void* payload, int payloadLen) {
PSDP_OPTION option, currentOption;
option = malloc(sizeof(*option) + payloadLen);
if (option == NULL) {
return -1;
}
option->next = NULL;
option->payloadLen = payloadLen;
strcpy(option->name, name);
option->payload = (void*)(option + 1);
memcpy(option->payload, payload, payloadLen);
if (*head == NULL) {
*head = option;
}
else {
currentOption = *head;
while (currentOption->next != NULL) {
currentOption = currentOption->next;
}
currentOption->next = option;
}
return 0;
}
// Add an attribute string
static int addAttributeString(PSDP_OPTION* head, char* name, const char* payload) {
// We purposefully omit the null terminating character
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);
return err;
}
static int addGen5Options(PSDP_OPTION* head) {
int err = 0;
// We want to use the new ENet connections for control and input
err |= addAttributeString(head, "x-nv-general.useReliableUdp", "1");
err |= addAttributeString(head, "x-nv-ri.useControlChannel", "1");
// Disable dynamic resolution switching
err |= addAttributeString(head, "x-nv-vqos[0].drc.enable", "0");
return err;
}
static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
PSDP_OPTION optionHead;
char payloadStr[92];
int audioChannelCount;
int audioChannelMask;
int err;
optionHead = NULL;
err = 0;
sprintf(payloadStr, "%d", StreamConfig.width);
err |= addAttributeString(&optionHead, "x-nv-video[0].clientViewportWd", payloadStr);
sprintf(payloadStr, "%d", StreamConfig.height);
err |= addAttributeString(&optionHead, "x-nv-video[0].clientViewportHt", payloadStr);
sprintf(payloadStr, "%d", StreamConfig.fps);
err |= addAttributeString(&optionHead, "x-nv-video[0].maxFPS", payloadStr);
sprintf(payloadStr, "%d", StreamConfig.packetSize);
err |= addAttributeString(&optionHead, "x-nv-video[0].packetSize", payloadStr);
err |= addAttributeString(&optionHead, "x-nv-video[0].rateControlMode", "4");
err |= addAttributeString(&optionHead, "x-nv-video[0].timeoutLengthMs", "7000");
err |= addAttributeString(&optionHead, "x-nv-video[0].framesWithInvalidRefThreshold", "0");
sprintf(payloadStr, "%d", StreamConfig.bitrate);
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
// distinguishing padding from valid sequences. Since we can only perform
// execute an FEC recovery on a 1 packet frame, we'll just turn it off completely.
err |= addAttributeString(&optionHead, "x-nv-vqos[0].fec.enable", "0");
err |= addAttributeString(&optionHead, "x-nv-vqos[0].videoQualityScoreUpdateTime", "5000");
if (StreamConfig.streamingRemotely) {
err |= addAttributeString(&optionHead, "x-nv-vqos[0].qosTrafficType", "0");
err |= addAttributeString(&optionHead, "x-nv-aqos.qosTrafficType", "0");
}
else {
err |= addAttributeString(&optionHead, "x-nv-vqos[0].qosTrafficType", "5");
err |= addAttributeString(&optionHead, "x-nv-aqos.qosTrafficType", "4");
}
if (ServerMajorVersion == 3) {
err |= addGen3Options(&optionHead, urlSafeAddr);
}
else if (ServerMajorVersion == 4) {
err |= addGen4Options(&optionHead, urlSafeAddr);
}
else {
err |= addGen5Options(&optionHead);
}
if (ServerMajorVersion >= 4) {
if (NegotiatedVideoFormat == VIDEO_FORMAT_H265) {
err |= addAttributeString(&optionHead, "x-nv-clientSupportHevc", "1");
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bitStreamFormat", "1");
// Disable slicing on HEVC
err |= addAttributeString(&optionHead, "x-nv-video[0].videoEncoderSlicesPerFrame", "1");
}
else {
unsigned char slicesPerFrame;
err |= addAttributeString(&optionHead, "x-nv-clientSupportHevc", "0");
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bitStreamFormat", "0");
// Use slicing for increased performance on some decoders
slicesPerFrame = (unsigned char)(VideoCallbacks.capabilities >> 24);
if (slicesPerFrame == 0) {
// If not using slicing, we request 1 slice per frame
slicesPerFrame = 1;
}
sprintf(payloadStr, "%d", slicesPerFrame);
err |= addAttributeString(&optionHead, "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;
}
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;
}
freeAttributeList(optionHead);
return NULL;
}
// Populate the SDP header with required information
static int fillSdpHeader(char* buffer, int rtspClientVersion, char*urlSafeAddr) {
return sprintf(buffer,
"v=0\r\n"
"o=android 0 %d IN %s %s\r\n"
"s=NVIDIA Streaming Client\r\n",
rtspClientVersion,
RemoteAddr.ss_family == AF_INET ? "IPv4" : "IPv6",
urlSafeAddr);
}
// Populate the SDP tail with required information
static int fillSdpTail(char* buffer) {
return sprintf(buffer,
"t=0 0\r\n"
"m=video %d \r\n",
ServerMajorVersion < 4 ? 47996 : 47998);
}
// Get the SDP attributes for the stream config
char* getSdpPayloadForStreamConfig(int rtspClientVersion, int* length) {
PSDP_OPTION attributeList;
int offset;
char* payload;
char urlSafeAddr[URLSAFESTRING_LEN];
addrToUrlSafeString(&RemoteAddr, urlSafeAddr);
attributeList = getAttributesList(urlSafeAddr);
if (attributeList == NULL) {
return NULL;
}
payload = malloc(MAX_SDP_HEADER_LEN + MAX_SDP_TAIL_LEN +
getSerializedAttributeListSize(attributeList));
if (payload == NULL) {
freeAttributeList(attributeList);
return NULL;
}
offset = fillSdpHeader(payload, rtspClientVersion, urlSafeAddr);
offset += fillSerializedAttributeList(&payload[offset], attributeList);
offset += fillSdpTail(&payload[offset]);
freeAttributeList(attributeList);
*length = offset;
return payload;
}

39
src/Video.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include "LinkedBlockingQueue.h"
typedef struct _QUEUED_DECODE_UNIT {
DECODE_UNIT decodeUnit;
LINKED_BLOCKING_QUEUE_ENTRY entry;
} QUEUED_DECODE_UNIT, *PQUEUED_DECODE_UNIT;
void freeQueuedDecodeUnit(PQUEUED_DECODE_UNIT qdu);
int getNextQueuedDecodeUnit(PQUEUED_DECODE_UNIT* qdu);
#pragma pack(push, 1)
#define FLAG_CONTAINS_PIC_DATA 0x1
#define FLAG_EOF 0x2
#define FLAG_SOF 0x4
typedef struct _NV_VIDEO_PACKET {
int streamPacketIndex;
int frameIndex;
char flags;
char reserved[3];
int reserved2;
} NV_VIDEO_PACKET, *PNV_VIDEO_PACKET;
#define FLAG_EXTENSION 0x10
#define FIXED_RTP_HEADER_SIZE 12
#define MAX_RTP_HEADER_SIZE 16
typedef struct _RTP_PACKET {
char header;
char packetType;
unsigned short sequenceNumber;
char reserved[8];
} RTP_PACKET, *PRTP_PACKET;
#pragma pack(pop)

559
src/VideoDepacketizer.c Normal file
View File

@@ -0,0 +1,559 @@
#include "Platform.h"
#include "Limelight-internal.h"
#include "LinkedBlockingQueue.h"
#include "Video.h"
static PLENTRY nalChainHead;
static int nalChainDataLength;
static int nextFrameNumber;
static int nextPacketNumber;
static int startFrameNumber;
static int waitingForNextSuccessfulFrame;
static int waitingForIdrFrame;
static int gotNextFrameStart;
static int lastPacketInStream;
static int decodingFrame;
static int strictIdrFrameWait;
#define CONSECUTIVE_DROP_LIMIT 120
static int consecutiveFrameDrops;
static LINKED_BLOCKING_QUEUE decodeUnitQueue;
static unsigned int nominalPacketDataLength;
typedef struct _BUFFER_DESC {
char* data;
unsigned int offset;
unsigned int length;
} BUFFER_DESC, *PBUFFER_DESC;
// Init
void initializeVideoDepacketizer(int pktSize) {
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
LbqInitializeLinkedBlockingQueue(&decodeUnitQueue, 15);
}
nominalPacketDataLength = pktSize - sizeof(NV_VIDEO_PACKET);
nextFrameNumber = 1;
nextPacketNumber = 0;
startFrameNumber = 0;
waitingForNextSuccessfulFrame = 0;
waitingForIdrFrame = 1;
gotNextFrameStart = 0;
lastPacketInStream = -1;
decodingFrame = 0;
strictIdrFrameWait = !(VideoCallbacks.capabilities & CAPABILITY_REFERENCE_FRAME_INVALIDATION);
}
// Free the NAL chain
static void cleanupFrameState(void) {
PLENTRY lastEntry;
while (nalChainHead != NULL) {
lastEntry = nalChainHead;
nalChainHead = lastEntry->next;
free(lastEntry);
}
nalChainDataLength = 0;
}
// Cleanup frame state and set that we're waiting for an IDR Frame
static void dropFrameState(void) {
// We'll need an IDR frame now if we're in strict mode
if (strictIdrFrameWait) {
waitingForIdrFrame = 1;
}
// Count the number of consecutive frames dropped
consecutiveFrameDrops++;
// If we reach our limit, immediately request an IDR frame and reset
if (consecutiveFrameDrops == CONSECUTIVE_DROP_LIMIT) {
Limelog("Reached consecutive drop limit\n");
// Restart the count
consecutiveFrameDrops = 0;
// Request an IDR frame
waitingForIdrFrame = 1;
requestIdrOnDemand();
}
cleanupFrameState();
}
// Cleanup the list of decode units
static void freeDecodeUnitList(PLINKED_BLOCKING_QUEUE_ENTRY entry) {
PLINKED_BLOCKING_QUEUE_ENTRY nextEntry;
while (entry != NULL) {
nextEntry = entry->flink;
freeQueuedDecodeUnit((PQUEUED_DECODE_UNIT)entry->data);
entry = nextEntry;
}
}
void stopVideoDepacketizer(void) {
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
LbqSignalQueueShutdown(&decodeUnitQueue);
}
}
// Cleanup video depacketizer and free malloced memory
void destroyVideoDepacketizer(void) {
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
freeDecodeUnitList(LbqDestroyLinkedBlockingQueue(&decodeUnitQueue));
}
cleanupFrameState();
}
// Returns 1 if candidate is a frame start and 0 otherwise
static int isSeqFrameStart(PBUFFER_DESC candidate) {
return (candidate->length == 4 && candidate->data[candidate->offset + candidate->length - 1] == 1);
}
// Returns 1 if candidate is an Annex B start and 0 otherwise
static int isSeqAnnexBStart(PBUFFER_DESC candidate) {
return (candidate->data[candidate->offset + candidate->length - 1] == 1);
}
// Returns 1 if candidate is padding and 0 otherwise
static int isSeqPadding(PBUFFER_DESC candidate) {
return (candidate->data[candidate->offset + candidate->length - 1] == 0);
}
// Returns 1 on success, 0 otherwise
static int getSpecialSeq(PBUFFER_DESC current, PBUFFER_DESC candidate) {
if (current->length < 3) {
return 0;
}
if (current->data[current->offset] == 0 &&
current->data[current->offset + 1] == 0) {
// Padding or frame start
if (current->data[current->offset + 2] == 0) {
if (current->length >= 4 && current->data[current->offset + 3] == 1) {
// Frame start
candidate->data = current->data;
candidate->offset = current->offset;
candidate->length = 4;
return 1;
}
else {
// Padding
candidate->data = current->data;
candidate->offset = current->offset;
candidate->length = 3;
return 1;
}
}
else if (current->data[current->offset + 2] == 1) {
// NAL start
candidate->data = current->data;
candidate->offset = current->offset;
candidate->length = 3;
return 1;
}
}
return 0;
}
// Get the first decode unit available
int getNextQueuedDecodeUnit(PQUEUED_DECODE_UNIT* qdu) {
int err = LbqWaitForQueueElement(&decodeUnitQueue, (void**)qdu);
if (err == LBQ_SUCCESS) {
return 1;
}
else {
return 0;
}
}
// Cleanup a decode unit by freeing the buffer chain and the holder
void freeQueuedDecodeUnit(PQUEUED_DECODE_UNIT qdu) {
PLENTRY lastEntry;
while (qdu->decodeUnit.bufferList != NULL) {
lastEntry = qdu->decodeUnit.bufferList;
qdu->decodeUnit.bufferList = lastEntry->next;
free(lastEntry);
}
free(qdu);
}
// Returns 1 if the special sequence describes an I-frame
static int isSeqReferenceFrameStart(PBUFFER_DESC specialSeq) {
switch (specialSeq->data[specialSeq->offset + specialSeq->length]) {
case 0x20:
case 0x22:
case 0x24:
case 0x26:
case 0x28:
case 0x2A:
// H265
return 1;
case 0x65:
// H264
return 1;
default:
return 0;
}
}
// Returns 1 if this buffer describes an IDR frame
static int isIdrFrameStart(PBUFFER_DESC buffer) {
BUFFER_DESC specialSeq;
return getSpecialSeq(buffer, &specialSeq) &&
isSeqFrameStart(&specialSeq) &&
(specialSeq.data[specialSeq.offset + specialSeq.length] == 0x67 || // H264 SPS
specialSeq.data[specialSeq.offset + specialSeq.length] == 0x40); // H265 VPS
}
// Reassemble the frame with the given frame number
static void reassembleFrame(int frameNumber) {
if (nalChainHead != NULL) {
PQUEUED_DECODE_UNIT qdu = (PQUEUED_DECODE_UNIT)malloc(sizeof(*qdu));
if (qdu != NULL) {
qdu->decodeUnit.bufferList = nalChainHead;
qdu->decodeUnit.fullLength = nalChainDataLength;
nalChainHead = NULL;
nalChainDataLength = 0;
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
if (LbqOfferQueueItem(&decodeUnitQueue, qdu, &qdu->entry) == LBQ_BOUND_EXCEEDED) {
Limelog("Video decode unit queue overflow\n");
// Clear frame state and wait for an IDR
nalChainHead = qdu->decodeUnit.bufferList;
nalChainDataLength = qdu->decodeUnit.fullLength;
dropFrameState();
// Free the DU
free(qdu);
// Flush the decode unit queue
freeDecodeUnitList(LbqFlushQueueItems(&decodeUnitQueue));
// FIXME: Get proper lower bound
connectionSinkTooSlow(0, frameNumber);
return;
}
}
else {
int ret = VideoCallbacks.submitDecodeUnit(&qdu->decodeUnit);
freeQueuedDecodeUnit(qdu);
if (ret == DR_NEED_IDR) {
Limelog("Requesting IDR frame on behalf of DR\n");
requestDecoderRefresh();
}
}
// Notify the control connection
connectionReceivedCompleteFrame(frameNumber);
// Clear frame drops
consecutiveFrameDrops = 0;
}
}
}
static void queueFragment(char*data, int offset, int length) {
PLENTRY entry = (PLENTRY)malloc(sizeof(*entry) + length);
if (entry != NULL) {
entry->next = NULL;
entry->length = length;
entry->data = (char*)(entry + 1);
memcpy(entry->data, &data[offset], entry->length);
nalChainDataLength += entry->length;
if (nalChainHead == NULL) {
nalChainHead = entry;
}
else {
PLENTRY currentEntry = nalChainHead;
while (currentEntry->next != NULL) {
currentEntry = currentEntry->next;
}
currentEntry->next = entry;
}
}
}
// Process an RTP Payload
static void processRtpPayloadSlow(PNV_VIDEO_PACKET videoPacket, PBUFFER_DESC currentPos) {
BUFFER_DESC specialSeq;
int decodingVideo = 0;
while (currentPos->length != 0) {
int start = currentPos->offset;
if (getSpecialSeq(currentPos, &specialSeq)) {
if (isSeqAnnexBStart(&specialSeq)) {
// Now we're decoding video
decodingVideo = 1;
if (isSeqFrameStart(&specialSeq)) {
// Now we're working on a frame
decodingFrame = 1;
// Reassemble any pending frame
reassembleFrame(videoPacket->frameIndex);
if (isSeqReferenceFrameStart(&specialSeq)) {
// No longer waiting for an IDR frame
waitingForIdrFrame = 0;
}
}
// Skip the start sequence
currentPos->length -= specialSeq.length;
currentPos->offset += specialSeq.length;
}
else {
// Check if this is padding after a full frame
if (decodingVideo && isSeqPadding(currentPos)) {
reassembleFrame(videoPacket->frameIndex);
}
// Not decoding video
decodingVideo = 0;
// Just skip this byte
currentPos->length--;
currentPos->offset++;
}
}
// Move to the next special sequence
while (currentPos->length != 0) {
// Check if this should end the current NAL
if (getSpecialSeq(currentPos, &specialSeq)) {
if (decodingVideo || !isSeqPadding(&specialSeq)) {
break;
}
}
// This byte is part of the NAL data
currentPos->offset++;
currentPos->length--;
}
if (decodingVideo) {
queueFragment(currentPos->data, start, currentPos->offset - start);
}
}
}
// Dumps the decode unit queue and ensures the next frame submitted to the decoder will be
// an IDR frame
void requestDecoderRefresh(void) {
// Wait for the next IDR frame
waitingForIdrFrame = 1;
// Flush the decode unit queue and pending state
dropFrameState();
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
freeDecodeUnitList(LbqFlushQueueItems(&decodeUnitQueue));
}
// Request the IDR frame
requestIdrOnDemand();
}
// Return 1 if packet is the first one in the frame
static int isFirstPacket(char flags) {
// Clear the picture data flag
flags &= ~FLAG_CONTAINS_PIC_DATA;
// Check if it's just the start or both start and end of a frame
return (flags == (FLAG_SOF | FLAG_EOF) ||
flags == FLAG_SOF);
}
// Adds a fragment directly to the queue
static void processRtpPayloadFast(BUFFER_DESC location) {
queueFragment(location.data, location.offset, location.length);
}
// Process an RTP Payload
void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length) {
BUFFER_DESC currentPos;
int frameIndex;
char flags;
int firstPacket;
int streamPacketIndex;
// Mask the top 8 bits from the SPI
videoPacket->streamPacketIndex >>= 8;
videoPacket->streamPacketIndex &= 0xFFFFFF;
currentPos.data = (char*)(videoPacket + 1);
currentPos.offset = 0;
currentPos.length = length - sizeof(*videoPacket);
frameIndex = videoPacket->frameIndex;
flags = videoPacket->flags;
firstPacket = isFirstPacket(flags);
// Drop duplicates or re-ordered packets
streamPacketIndex = videoPacket->streamPacketIndex;
if (isBeforeSignedInt((short)streamPacketIndex, (short)(lastPacketInStream + 1), 0)) {
return;
}
// Drop packets from a previously completed frame
if (isBeforeSignedInt(frameIndex, nextFrameNumber, 0)) {
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)
{
Limelog("Network dropped end of a frame\n");
nextFrameNumber = frameIndex;
// Unexpected start of next frame before terminating the last
waitingForNextSuccessfulFrame = 1;
dropFrameState();
}
// Look for a non-frame start before a frame start
else if (!firstPacket && !decodingFrame) {
// Check if this looks like a real frame
if (flags == FLAG_CONTAINS_PIC_DATA ||
flags == FLAG_EOF ||
currentPos.length < nominalPacketDataLength)
{
Limelog("Network dropped beginning of a frame\n");
nextFrameNumber = frameIndex + 1;
waitingForNextSuccessfulFrame = 1;
dropFrameState();
decodingFrame = 0;
return;
}
else {
// FEC data
return;
}
}
// Check sequencing of this frame to ensure we didn't
// miss one in between
else if (firstPacket) {
// Make sure this is the next consecutive frame
if (isBeforeSignedInt(nextFrameNumber, frameIndex, 1)) {
Limelog("Network dropped an entire frame\n");
nextFrameNumber = frameIndex;
// Wait until next complete frame
waitingForNextSuccessfulFrame = 1;
dropFrameState();
}
else if (nextFrameNumber != frameIndex) {
// Duplicate packet or FEC dup
decodingFrame = 0;
return;
}
// We're now decoding a frame
decodingFrame = 1;
}
// If it's not the first packet of a frame
// we need to drop it if the stream packet index
// doesn't match
if (!firstPacket && decodingFrame) {
if (streamPacketIndex != (int)(lastPacketInStream + 1)) {
Limelog("Network dropped middle of a frame\n");
nextFrameNumber = frameIndex + 1;
waitingForNextSuccessfulFrame = 1;
dropFrameState();
decodingFrame = 0;
return;
}
}
// Notify the server of any packet losses
if (streamPacketIndex != (int)(lastPacketInStream + 1)) {
// Packets were lost so report this to the server
connectionLostPackets(lastPacketInStream, streamPacketIndex);
}
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 && isIdrFrameStart(&currentPos))
{
// SPS and PPS prefix is padded between NALs, so we must decode it with the slow path
processRtpPayloadSlow(videoPacket, &currentPos);
}
else
{
processRtpPayloadFast(currentPos);
}
if (flags & FLAG_EOF) {
// Move on to the next frame
decodingFrame = 0;
nextFrameNumber = frameIndex + 1;
// If waiting for next successful frame and we got here
// with an end flag, we can send a message to the server
if (waitingForNextSuccessfulFrame) {
// This is the next successful frame after a loss event
connectionDetectedFrameLoss(startFrameNumber, nextFrameNumber - 1);
waitingForNextSuccessfulFrame = 0;
}
// If we need an IDR frame first, then drop this frame
if (waitingForIdrFrame) {
Limelog("Waiting for IDR frame\n");
dropFrameState();
return;
}
reassembleFrame(frameIndex);
startFrameNumber = nextFrameNumber;
}
}
// Add an RTP Packet to the queue
void queueRtpPacket(PRTP_PACKET rtpPacket, int length) {
int dataOffset;
dataOffset = sizeof(*rtpPacket);
if (rtpPacket->header & FLAG_EXTENSION) {
dataOffset += 4; // 2 additional fields
}
processRtpPayload((PNV_VIDEO_PACKET)(((char*)rtpPacket) + dataOffset), length - dataOffset);
}

246
src/VideoStream.c Normal file
View File

@@ -0,0 +1,246 @@
#include "Limelight-internal.h"
#include "PlatformSockets.h"
#include "PlatformThreads.h"
#include "RtpReorderQueue.h"
#define FIRST_FRAME_MAX 1500
#define FIRST_FRAME_TIMEOUT_SEC 10
#define RTP_PORT 47998
#define FIRST_FRAME_PORT 47996
#define RTP_RECV_BUFFER (64 * 1024)
static RTP_REORDER_QUEUE rtpQueue;
static SOCKET rtpSocket = INVALID_SOCKET;
static SOCKET firstFrameSocket = INVALID_SOCKET;
static PLT_THREAD udpPingThread;
static PLT_THREAD receiveThread;
static PLT_THREAD decoderThread;
// We can't request an IDR frame until the depacketizer knows
// that a packet was lost. This timeout bounds the time that
// the RTP queue will wait for missing/reordered packets.
#define RTP_QUEUE_DELAY 10
// Initialize the video stream
void initializeVideoStream(void) {
initializeVideoDepacketizer(StreamConfig.packetSize);
RtpqInitializeQueue(&rtpQueue, RTPQ_DEFAULT_MAX_SIZE, RTP_QUEUE_DELAY);
}
// Clean up the video stream
void destroyVideoStream(void) {
destroyVideoDepacketizer();
RtpqCleanupQueue(&rtpQueue);
}
// UDP Ping proc
static void UdpPingThreadProc(void* context) {
char pingData[] = { 0x50, 0x49, 0x4E, 0x47 };
struct sockaddr_in6 saddr;
SOCK_RET err;
memcpy(&saddr, &RemoteAddr, sizeof(saddr));
saddr.sin6_port = htons(RTP_PORT);
while (!PltIsThreadInterrupted(&udpPingThread)) {
err = sendto(rtpSocket, pingData, sizeof(pingData), 0, (struct sockaddr*)&saddr, RemoteAddrLen);
if (err != sizeof(pingData)) {
Limelog("Video Ping: send() failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketError());
return;
}
PltSleepMs(500);
}
}
// Receive thread proc
static void ReceiveThreadProc(void* context) {
int err;
int bufferSize, receiveSize;
char* buffer;
int queueStatus;
receiveSize = StreamConfig.packetSize + MAX_RTP_HEADER_SIZE;
bufferSize = receiveSize + sizeof(int) + sizeof(RTP_QUEUE_ENTRY);
buffer = NULL;
while (!PltIsThreadInterrupted(&receiveThread)) {
PRTP_PACKET packet;
if (buffer == NULL) {
buffer = (char*)malloc(bufferSize);
if (buffer == NULL) {
Limelog("Video Receive: malloc() failed\n");
ListenerCallbacks.connectionTerminated(-1);
return;
}
}
err = recvUdpSocket(rtpSocket, buffer, receiveSize);
if (err < 0) {
Limelog("Video Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketError());
break;
}
else if (err == 0) {
// Receive timed out; try again
continue;
}
memcpy(&buffer[receiveSize], &err, sizeof(int));
// RTP sequence number must be in host order for the RTP queue
packet = (PRTP_PACKET)&buffer[0];
packet->sequenceNumber = htons(packet->sequenceNumber);
queueStatus = RtpqAddPacket(&rtpQueue, packet, (PRTP_QUEUE_ENTRY)&buffer[receiveSize + sizeof(int)]);
if (queueStatus == RTPQ_RET_HANDLE_IMMEDIATELY) {
// queueRtpPacket() copies the data it needs to we can reuse the buffer
queueRtpPacket(packet, err);
}
else if (queueStatus == RTPQ_RET_QUEUED_PACKETS_READY) {
// The packet queue now has packets ready
while ((buffer = (char*)RtpqGetQueuedPacket(&rtpQueue)) != NULL) {
memcpy(&err, &buffer[receiveSize], sizeof(int));
queueRtpPacket((PRTP_PACKET)buffer, err);
free(buffer);
}
}
else if (queueStatus == RTPQ_RET_QUEUED_NOTHING_READY) {
// The queue owns the buffer
buffer = NULL;
}
}
if (buffer != NULL) {
free(buffer);
}
}
// Decoder thread proc
static void DecoderThreadProc(void* context) {
PQUEUED_DECODE_UNIT qdu;
while (!PltIsThreadInterrupted(&decoderThread)) {
if (!getNextQueuedDecodeUnit(&qdu)) {
return;
}
int ret = VideoCallbacks.submitDecodeUnit(&qdu->decodeUnit);
freeQueuedDecodeUnit(qdu);
if (ret == DR_NEED_IDR) {
Limelog("Requesting IDR frame on behalf of DR\n");
requestDecoderRefresh();
}
}
}
// 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) {
// Wake up client code that may be waiting on the decode unit queue
stopVideoDepacketizer();
PltInterruptThread(&udpPingThread);
PltInterruptThread(&receiveThread);
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltInterruptThread(&decoderThread);
}
if (firstFrameSocket != INVALID_SOCKET) {
shutdownTcpSocket(firstFrameSocket);
}
PltJoinThread(&udpPingThread);
PltJoinThread(&receiveThread);
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltJoinThread(&decoderThread);
}
PltCloseThread(&udpPingThread);
PltCloseThread(&receiveThread);
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltCloseThread(&decoderThread);
}
if (firstFrameSocket != INVALID_SOCKET) {
closeSocket(firstFrameSocket);
firstFrameSocket = INVALID_SOCKET;
}
if (rtpSocket != INVALID_SOCKET) {
closeSocket(rtpSocket);
rtpSocket = INVALID_SOCKET;
}
VideoCallbacks.cleanup();
}
// Start the video stream
int startVideoStream(void* rendererContext, int drFlags) {
int err;
// This must be called before the decoder thread starts submitting
// decode units
LC_ASSERT(NegotiatedVideoFormat != 0);
VideoCallbacks.setup(NegotiatedVideoFormat, StreamConfig.width,
StreamConfig.height, StreamConfig.fps, rendererContext, drFlags);
rtpSocket = bindUdpSocket(RemoteAddr.ss_family, RTP_RECV_BUFFER);
if (rtpSocket == INVALID_SOCKET) {
return LastSocketError();
}
err = PltCreateThread(ReceiveThreadProc, NULL, &receiveThread);
if (err != 0) {
return err;
}
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
err = PltCreateThread(DecoderThreadProc, NULL, &decoderThread);
if (err != 0) {
return err;
}
}
if (ServerMajorVersion == 3) {
// Connect this socket to open port 47998 for our ping thread
firstFrameSocket = connectTcpSocket(&RemoteAddr, RemoteAddrLen,
FIRST_FRAME_PORT, FIRST_FRAME_TIMEOUT_SEC);
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;
}