diff --git a/limelight-common/AudioStream.c b/limelight-common/AudioStream.c
new file mode 100644
index 0000000..119b977
--- /dev/null
+++ b/limelight-common/AudioStream.c
@@ -0,0 +1,177 @@
+#include "Limelight-internal.h"
+#include "PlatformSockets.h"
+#include "PlatformThreads.h"
+#include "LinkedBlockingQueue.h"
+
+static AUDIO_RENDERER_CALLBACKS callbacks;
+static IP_ADDRESS remoteHost;
+
+static SOCKET rtpSocket = INVALID_SOCKET;
+
+static LINKED_BLOCKING_QUEUE packetQueue;
+
+static PLT_THREAD udpPingThread;
+static PLT_THREAD receiveThread;
+static PLT_THREAD decoderThread;
+
+#define RTP_PORT 48000
+
+void initializeAudioStream(IP_ADDRESS host, PAUDIO_RENDERER_CALLBACKS arCallbacks) {
+ memcpy(&callbacks, arCallbacks, sizeof(callbacks));
+ remoteHost = host;
+
+ LbqInitializeLinkedBlockingQueue(&packetQueue, 30);
+}
+
+void destroyAudioStream(void) {
+ PLINKED_BLOCKING_QUEUE_ENTRY entry, nextEntry;
+
+ entry = LbqDestroyLinkedBlockingQueue(&packetQueue);
+
+ while (entry != NULL) {
+ nextEntry = entry->next;
+ free(entry->data);
+ free(entry);
+ entry = nextEntry;
+ }
+}
+
+static void UdpPingThreadProc(void *context) {
+ char pingData[] = { 0x50, 0x49, 0x4E, 0x47 };
+ struct sockaddr_in saddr;
+ int err;
+
+ memset(&saddr, 0, sizeof(saddr));
+ saddr.sin_family = AF_INET;
+ saddr.sin_port = htons(RTP_PORT);
+ memcpy(&saddr.sin_addr, &remoteHost, sizeof(remoteHost));
+
+ while (!PltIsThreadInterrupted(&udpPingThread)) {
+ err = sendto(rtpSocket, pingData, sizeof(pingData), 0, (struct sockaddr*)&saddr, sizeof(saddr));
+ if (err != sizeof(pingData)) {
+ Limelog("UDP ping thread terminating #1\n");
+ return;
+ }
+
+ PltSleepMs(100);
+ }
+}
+
+static void ReceiveThreadProc(void* context) {
+ int err;
+
+ for (;;) {
+ char* buffer = (char*) malloc(1500 + sizeof(int));
+ if (buffer == NULL) {
+ Limelog("Receive thread terminating\n");
+ return;
+ }
+
+ err = recv(rtpSocket, &buffer[sizeof(int)], 1500, 0);
+ if (err <= 0) {
+ Limelog("Receive thread terminating #2\n");
+ free(buffer);
+ return;
+ }
+
+ memcpy(buffer, &err, sizeof(err));
+
+ err = LbqOfferQueueItem(&packetQueue, buffer);
+ if (err != LBQ_SUCCESS) {
+ free(buffer);
+ }
+
+ if (err == LBQ_BOUND_EXCEEDED) {
+ Limelog("Audio packet queue overflow\n");
+ }
+ else if (err == LBQ_INTERRUPTED) {
+ Limelog("Receive thread terminating #2\n");
+ return;
+ }
+ }
+}
+
+static void DecoderThreadProc(void* context) {
+ PRTP_PACKET rtp;
+ int length;
+ int err;
+ char *data;
+ unsigned short lastSeq = 0;
+
+ for (;;) {
+ err = LbqWaitForQueueElement(&packetQueue, (void**) &data);
+ if (err != LBQ_SUCCESS) {
+ Limelog("Decoder thread terminating\n");
+ return;
+ }
+
+ memcpy(&length, data, sizeof(int));
+ rtp = (PRTP_PACKET) &data[sizeof(int)];
+
+ if (length < sizeof(RTP_PACKET)) {
+ Limelog("Runt packet\n");
+ continue;
+ }
+
+ if (rtp->packetType != 97) {
+ // Not audio
+ continue;
+ }
+
+ rtp->sequenceNumber = htons(rtp->sequenceNumber);
+
+ if (lastSeq != 0 && (unsigned short) (lastSeq + 1) != rtp->sequenceNumber) {
+ Limelog("Received OOS audio data (expected %d, but got %d)\n", lastSeq + 1, rtp->sequenceNumber);
+
+ callbacks.decodeAndPlaySample(NULL, 0);
+ }
+
+ lastSeq = rtp->sequenceNumber;
+
+ callbacks.decodeAndPlaySample((char *) (rtp + 1), length - sizeof(*rtp));
+ }
+}
+
+void stopAudioStream(void) {
+ PltInterruptThread(&udpPingThread);
+ PltInterruptThread(&receiveThread);
+ PltInterruptThread(&decoderThread);
+
+ if (rtpSocket != INVALID_SOCKET) {
+ closesocket(rtpSocket);
+ rtpSocket = INVALID_SOCKET;
+ }
+
+ PltJoinThread(&udpPingThread);
+ PltJoinThread(&receiveThread);
+ PltJoinThread(&decoderThread);
+
+ PltCloseThread(&udpPingThread);
+ PltCloseThread(&receiveThread);
+ PltCloseThread(&decoderThread);
+}
+
+int startAudioStream(void) {
+ int err;
+
+ rtpSocket = bindUdpSocket(RTP_PORT);
+
+ err = PltCreateThread(UdpPingThreadProc, NULL, &udpPingThread);
+ if (err != 0) {
+ return err;
+ }
+
+ err = PltCreateThread(ReceiveThreadProc, NULL, &receiveThread);
+ if (err != 0) {
+ return err;
+ }
+
+ err = PltCreateThread(DecoderThreadProc, NULL, &decoderThread);
+ if (err != 0) {
+ return err;
+ }
+
+ callbacks.start();
+
+ return 0;
+}
\ No newline at end of file
diff --git a/limelight-common/Connection.c b/limelight-common/Connection.c
index e7d91a1..41d591a 100644
--- a/limelight-common/Connection.c
+++ b/limelight-common/Connection.c
@@ -6,12 +6,20 @@
#define STAGE_HANDSHAKE 2
#define STAGE_CONTROL_STREAM_INIT 3
#define STAGE_VIDEO_STREAM_INIT 4
-#define STAGE_CONTROL_STREAM_START 5
-#define STAGE_VIDEO_STREAM_START 6
+#define STAGE_AUDIO_STREAM_INIT 5
+#define STAGE_CONTROL_STREAM_START 6
+#define STAGE_VIDEO_STREAM_START 7
+#define STAGE_AUDIO_STREAM_START 8
static int stage = STAGE_NONE;
void LiStopConnection(void) {
+ 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();
@@ -24,6 +32,12 @@ void LiStopConnection(void) {
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();
@@ -50,7 +64,8 @@ void LiStopConnection(void) {
LC_ASSERT(stage == STAGE_NONE);
}
-int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PDECODER_RENDERER_CALLBACKS drCallbacks, void* renderContext, int drFlags) {
+int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PDECODER_RENDERER_CALLBACKS drCallbacks,
+ PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags) {
int err;
Limelog("Initializing platform...");
@@ -89,6 +104,12 @@ int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PDECO
LC_ASSERT(stage == STAGE_VIDEO_STREAM_INIT);
Limelog("done\n");
+ Limelog("Initializing audio stream...");
+ initializeAudioStream(host, arCallbacks);
+ stage++;
+ LC_ASSERT(stage == STAGE_AUDIO_STREAM_INIT);
+ Limelog("done\n");
+
Limelog("Starting control stream...");
err = startControlStream();
if (err != 0) {
@@ -109,6 +130,16 @@ int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PDECO
LC_ASSERT(stage == STAGE_VIDEO_STREAM_START);
Limelog("done\n");
+ Limelog("Starting audio stream...");
+ err = startAudioStream();
+ if (err != 0) {
+ Limelog("Audio stream start failed: %d\n", err);
+ goto Cleanup;
+ }
+ stage++;
+ LC_ASSERT(stage == STAGE_AUDIO_STREAM_START);
+ Limelog("done\n");
+
Cleanup:
return err;
}
\ No newline at end of file
diff --git a/limelight-common/Limelight-internal.h b/limelight-common/Limelight-internal.h
index abf08e3..611bb49 100644
--- a/limelight-common/Limelight-internal.h
+++ b/limelight-common/Limelight-internal.h
@@ -27,4 +27,9 @@ void queueRtpPacket(PRTP_PACKET rtpPacket, int length);
void initializeVideoStream(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PDECODER_RENDERER_CALLBACKS drCallbacks);
void destroyVideoStream(void);
int startVideoStream(void* rendererContext, int drFlags);
-void stopVideoStream(void);
\ No newline at end of file
+void stopVideoStream(void);
+
+void initializeAudioStream(IP_ADDRESS host, PAUDIO_RENDERER_CALLBACKS arCallbacks);
+void destroyAudioStream(void);
+int startAudioStream(void);
+void stopAudioStream(void);
\ No newline at end of file
diff --git a/limelight-common/Limelight.h b/limelight-common/Limelight.h
index 93a14db..5d2dbef 100644
--- a/limelight-common/Limelight.h
+++ b/limelight-common/Limelight.h
@@ -37,7 +37,22 @@ typedef struct _DECODER_RENDERER_CALLBACKS {
DecoderRendererSubmitDecodeUnit submitDecodeUnit;
} DECODER_RENDERER_CALLBACKS, *PDECODER_RENDERER_CALLBACKS;
-int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PDECODER_RENDERER_CALLBACKS drCallbacks, void* renderContext, int drFlags);
+typedef void(*AudioRendererInit)(void);
+typedef void(*AudioRendererStart)(void);
+typedef void(*AudioRendererStop)(void);
+typedef void(*AudioRendererRelease)(void);
+typedef void(*AudioRendererDecodeAndPlaySample)(char* sampleData, int sampleLength);
+
+typedef struct _AUDIO_RENDERER_CALLBACKS {
+ AudioRendererInit init;
+ AudioRendererStart start;
+ AudioRendererStop stop;
+ AudioRendererRelease release;
+ AudioRendererDecodeAndPlaySample decodeAndPlaySample;
+} AUDIO_RENDERER_CALLBACKS, *PAUDIO_RENDERER_CALLBACKS;
+
+int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PDECODER_RENDERER_CALLBACKS drCallbacks,
+ PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags);
void LiStopConnection(void);
#ifdef __cplusplus
diff --git a/limelight-common/limelight-common.vcxproj b/limelight-common/limelight-common.vcxproj
index 263bc38..26d8ed1 100644
--- a/limelight-common/limelight-common.vcxproj
+++ b/limelight-common/limelight-common.vcxproj
@@ -130,6 +130,7 @@
+
diff --git a/limelight-common/limelight-common.vcxproj.filters b/limelight-common/limelight-common.vcxproj.filters
index 28a5895..75591bb 100644
--- a/limelight-common/limelight-common.vcxproj.filters
+++ b/limelight-common/limelight-common.vcxproj.filters
@@ -48,6 +48,9 @@
Source Files
+
+ Source Files
+