From 30f275768c7a21293272553b6dcf1ebb76254bda Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 2 Feb 2014 05:34:12 -0500 Subject: [PATCH] Add audio support --- limelight-common/AudioStream.c | 177 ++++++++++++++++++ limelight-common/Connection.c | 37 +++- limelight-common/Limelight-internal.h | 7 +- limelight-common/Limelight.h | 17 +- limelight-common/limelight-common.vcxproj | 1 + .../limelight-common.vcxproj.filters | 3 + 6 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 limelight-common/AudioStream.c 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 +