mirror of
https://github.com/moonlight-stream/moonlight-common-c.git
synced 2025-08-17 17:05:50 +00:00
Add audio support
This commit is contained in:
parent
ac6412f4be
commit
30f275768c
177
limelight-common/AudioStream.c
Normal file
177
limelight-common/AudioStream.c
Normal file
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
void stopVideoStream(void);
|
||||
|
||||
void initializeAudioStream(IP_ADDRESS host, PAUDIO_RENDERER_CALLBACKS arCallbacks);
|
||||
void destroyAudioStream(void);
|
||||
int startAudioStream(void);
|
||||
void stopAudioStream(void);
|
@ -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
|
||||
|
@ -130,6 +130,7 @@
|
||||
<Text Include="ReadMe.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AudioStream.c" />
|
||||
<ClCompile Include="ByteBuffer.c" />
|
||||
<ClCompile Include="Config.c" />
|
||||
<ClCompile Include="Connection.c" />
|
||||
|
@ -48,6 +48,9 @@
|
||||
<ClCompile Include="VideoStream.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AudioStream.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="PlatformSockets.h">
|
||||
|
Loading…
x
Reference in New Issue
Block a user