mirror of
https://github.com/moonlight-stream/moonlight-common-c.git
synced 2026-04-23 16:39:09 +00:00
Reorganize the folder structure and delete the Xcode build files
This commit is contained in:
310
src/AudioStream.c
Normal file
310
src/AudioStream.c
Normal 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
147
src/ByteBuffer.c
Normal 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
38
src/ByteBuffer.h
Normal 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
338
src/Connection.c
Normal 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
701
src/ControlStream.c
Normal 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
100
src/FakeCallbacks.c
Normal 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
92
src/Input.h
Normal 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
532
src/InputStream.c
Normal 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
62
src/Limelight-internal.h
Normal 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
287
src/Limelight.h
Normal 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
209
src/LinkedBlockingQueue.c
Normal 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
34
src/LinkedBlockingQueue.h
Normal 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
34
src/Misc.c
Normal 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
27
src/OpenAES/LICENSE
Normal 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
85
src/OpenAES/README
Normal 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
1
src/OpenAES/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
OpenAES-0.9.0
|
||||
178
src/OpenAES/oaes_base64.c
Normal file
178
src/OpenAES/oaes_base64.c
Normal 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
50
src/OpenAES/oaes_base64.h
Normal 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
77
src/OpenAES/oaes_common.h
Normal 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
42
src/OpenAES/oaes_config.h
Normal 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
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
168
src/OpenAES/oaes_lib.h
Normal 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
256
src/Platform.c
Executable 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
63
src/Platform.h
Normal 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
246
src/PlatformSockets.c
Normal 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
51
src/PlatformSockets.h
Normal 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
52
src/PlatformThreads.h
Executable 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
265
src/RtpReorderQueue.c
Normal 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
39
src/RtpReorderQueue.h
Normal 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
66
src/Rtsp.h
Normal 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
715
src/RtspConnection.c
Normal 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
398
src/RtspParser.c
Normal 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
332
src/SdpGenerator.c
Normal 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
39
src/Video.h
Normal 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
559
src/VideoDepacketizer.c
Normal 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(¤tPos))
|
||||
{
|
||||
// SPS and PPS prefix is padded between NALs, so we must decode it with the slow path
|
||||
processRtpPayloadSlow(videoPacket, ¤tPos);
|
||||
}
|
||||
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
246
src/VideoStream.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user