From c9e65715cb5d3ff383d617f00d4f34e894749838 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 14 Feb 2016 00:35:39 -0500 Subject: [PATCH] Add audio decoding support --- Makefile | 1 + auddec.cpp | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++ main.cpp | 2 +- moonlight.hpp | 14 ++++- 4 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 auddec.cpp diff --git a/Makefile b/Makefile index de0bd95..e1dfb53 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ SOURCES = \ gamepad.cpp \ connectionlistener.cpp \ viddec.cpp \ + auddec.cpp \ # Build rules generated by macros from common.mk: diff --git a/auddec.cpp b/auddec.cpp new file mode 100644 index 0000000..ccb0f66 --- /dev/null +++ b/auddec.cpp @@ -0,0 +1,143 @@ +#include "moonlight.hpp" + +#define MAX_CHANNEL_COUNT 6 +#define FRAME_SIZE 240 + +typedef struct decoded_sample_entry { + struct decoded_sample_entry *next; + int sampleLength; + short sampleBuffer[1]; +} decoded_sample_entry_t; + +#define MAX_QUEUE_LENGTH 14 +#define QUEUE_PRUNING_LENGTH 7 + +static int s_OpusChannelCount; +static decoded_sample_entry_t* s_SampleQueueHead; +static decoded_sample_entry_t* s_SampleQueueTail; +static int s_SampleQueueLength; +static pthread_mutex_t s_SampleQueueLock; + +static void ReapSampleQueue() { + decoded_sample_entry_t *entry; + + while (s_SampleQueueHead) { + entry = s_SampleQueueHead->next; + free(s_SampleQueueHead); + s_SampleQueueHead = entry; + } + + s_SampleQueueTail = NULL; + + s_SampleQueueLength = 0; +} + +static void AudioPlayerSampleCallback(void* samples, uint32_t buffer_size, void* data) { + unsigned char* buffer = (unsigned char *)samples; + int offset = 0; + + pthread_mutex_lock(&s_SampleQueueLock); + + while (s_SampleQueueHead && s_SampleQueueHead->sampleLength <= buffer_size - offset) { + decoded_sample_entry_t* lastEnt; + + memcpy(&buffer[offset], s_SampleQueueHead->sampleBuffer, s_SampleQueueHead->sampleLength); + offset += s_SampleQueueHead->sampleLength; + + lastEnt = s_SampleQueueHead; + s_SampleQueueHead = s_SampleQueueHead->next; + free(lastEnt); + s_SampleQueueLength--; + + // Remove another sample if we're in pruning mode + if (s_SampleQueueLength > QUEUE_PRUNING_LENGTH) { + lastEnt = s_SampleQueueHead; + s_SampleQueueHead = s_SampleQueueHead->next; + free(lastEnt); + s_SampleQueueLength--; + } + } + + if (!s_SampleQueueHead) { + s_SampleQueueTail = NULL; + } + + pthread_mutex_unlock(&s_SampleQueueLock); + + // Zero the remaining portion of the sample buffer to reduce noise when underflowing + if (buffer_size != offset) { + memset(&buffer[offset], 0, buffer_size - offset); + } +} + +void MoonlightInstance::AudDecInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig) { + int rc; + + pthread_mutex_init(&s_SampleQueueLock, NULL); + + s_OpusChannelCount = opusConfig->channelCount; + g_Instance->m_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate, + opusConfig->channelCount, + opusConfig->streams, + opusConfig->coupledStreams, + opusConfig->mapping, + &rc); + + pp::AudioConfig audioConfig = pp::AudioConfig(g_Instance, PP_AUDIOSAMPLERATE_48000, FRAME_SIZE * 3); + + g_Instance->m_AudioPlayer = pp::Audio(g_Instance, audioConfig, AudioPlayerSampleCallback, NULL); + + // Start playback now + g_Instance->m_AudioPlayer.StartPlayback(); +} + +void MoonlightInstance::AudDecCleanup(void) { + pthread_mutex_destroy(&s_SampleQueueLock); + + if (g_Instance->m_OpusDecoder) { + opus_multistream_decoder_destroy(g_Instance->m_OpusDecoder); + } + + ReapSampleQueue(); +} + +void MoonlightInstance::AudDecDecodeAndPlaySample(char* sampleData, int sampleLength) { + decoded_sample_entry_t* entry = (decoded_sample_entry_t*)malloc(sizeof(decoded_sample_entry_t) + + (s_OpusChannelCount * FRAME_SIZE * sizeof(short))); + if (entry) { + int decodeLen = opus_multistream_decode(g_Instance->m_OpusDecoder, (unsigned char *)sampleData, sampleLength, + entry->sampleBuffer, FRAME_SIZE, 0); + if (decodeLen > 0) { + entry->sampleLength = decodeLen * s_OpusChannelCount * sizeof(short); + entry->next = NULL; + + pthread_mutex_lock(&s_SampleQueueLock); + + if (s_SampleQueueLength == MAX_QUEUE_LENGTH) { + printf("Reaped sample queue\n"); + ReapSampleQueue(); + } + + if (!s_SampleQueueTail) { + s_SampleQueueHead = s_SampleQueueTail = entry; + } + else { + s_SampleQueueTail->next = entry; + s_SampleQueueTail = entry; + } + + s_SampleQueueLength++; + + pthread_mutex_unlock(&s_SampleQueueLock); + } + else { + free(entry); + } + } +} + +AUDIO_RENDERER_CALLBACKS MoonlightInstance::s_ArCallbacks = { + MoonlightInstance::AudDecInit, + MoonlightInstance::AudDecCleanup, + MoonlightInstance::AudDecDecodeAndPlaySample +}; \ No newline at end of file diff --git a/main.cpp b/main.cpp index 3b3ac1d..d381943 100644 --- a/main.cpp +++ b/main.cpp @@ -53,7 +53,7 @@ void* MoonlightInstance::ConnectionThreadFunc(void* context) { &s_StreamConfig, &MoonlightInstance::s_ClCallbacks, &MoonlightInstance::s_DrCallbacks, - NULL, + &MoonlightInstance::s_ArCallbacks, NULL, 0, 4); if (err != 0) { pp::Var response("Starting connection failed"); diff --git a/moonlight.hpp b/moonlight.hpp index 63f441d..36c168f 100644 --- a/moonlight.hpp +++ b/moonlight.hpp @@ -4,6 +4,7 @@ #include "ppapi/cpp/mouse_lock.h" #include "ppapi/cpp/graphics_3d.h" #include "ppapi/cpp/video_decoder.h" +#include "ppapi/cpp/audio.h" #include "ppapi/c/ppb_gamepad.h" #include "ppapi/c/pp_input_event.h" @@ -22,6 +23,8 @@ #include +#include + struct Shader { Shader() : program(0), texcoord_scale_location(0) {} ~Shader() {} @@ -36,6 +39,7 @@ class MoonlightInstance : public pp::Instance, public pp::MouseLock { pp::Instance(instance), pp::MouseLock(this), m_IsPainting(false), + m_OpusDecoder(NULL), m_CallbackFactory(this), m_MouseLocked(false), m_KeyModifiers(0) { @@ -89,10 +93,15 @@ class MoonlightInstance : public pp::Instance, public pp::MouseLock { static void VidDecSetup(int width, int height, int redrawRate, void* context, int drFlags); static void VidDecCleanup(void); static int VidDecSubmitDecodeUnit(PDECODE_UNIT decodeUnit); - + + static void AudDecInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig); + static void AudDecCleanup(void); + static void AudDecDecodeAndPlaySample(char* sampleData, int sampleLength); + private: static CONNECTION_LISTENER_CALLBACKS s_ClCallbacks; static DECODER_RENDERER_CALLBACKS s_DrCallbacks; + static AUDIO_RENDERER_CALLBACKS s_ArCallbacks; pp::Graphics3D m_Graphics3D; pp::VideoDecoder* m_VideoDecoder; @@ -103,6 +112,9 @@ class MoonlightInstance : public pp::Instance, public pp::MouseLock { std::queue m_PendingPictureQueue; bool m_IsPainting; + OpusMSDecoder* m_OpusDecoder; + pp::Audio m_AudioPlayer; + double m_LastPadTimestamps[4]; const PPB_Gamepad* m_GamepadApi; pp::CompletionCallbackFactory m_CallbackFactory;