From 3dc049ee5fed5ec45221f85ff2e854020c86631f Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 2 Oct 2018 01:21:42 -0700 Subject: [PATCH] Remove automatic audio configuration permanently due to brokenness and allow renderers to request reinitialization on demand --- app/cli/commandlineparser.cpp | 1 - app/gui/SettingsView.qml | 4 -- app/streaming/audio/audio.cpp | 70 ++++++++++--------- .../audio/renderers/portaudiorenderer.cpp | 28 ++------ .../audio/renderers/portaudiorenderer.h | 4 +- app/streaming/audio/renderers/renderer.h | 5 +- app/streaming/audio/renderers/sdl.h | 4 +- app/streaming/audio/renderers/sdlaud.cpp | 49 ++----------- app/streaming/session.cpp | 7 +- app/streaming/session.h | 1 + 10 files changed, 52 insertions(+), 121 deletions(-) diff --git a/app/cli/commandlineparser.cpp b/app/cli/commandlineparser.cpp index 6dc43efb..bfd128a3 100644 --- a/app/cli/commandlineparser.cpp +++ b/app/cli/commandlineparser.cpp @@ -194,7 +194,6 @@ StreamCommandLineParser::StreamCommandLineParser() {"borderless", StreamingPreferences::WM_FULLSCREEN_DESKTOP}, }; m_AudioConfigMap = { - {"auto", StreamingPreferences::AC_AUTO}, {"stereo", StreamingPreferences::AC_FORCE_STEREO}, {"surround", StreamingPreferences::AC_FORCE_SURROUND}, }; diff --git a/app/gui/SettingsView.qml b/app/gui/SettingsView.qml index d81d302c..964e02ee 100644 --- a/app/gui/SettingsView.qml +++ b/app/gui/SettingsView.qml @@ -406,10 +406,6 @@ ScrollView { textRole: "text" model: ListModel { id: audioListModel - ListElement { - text: "Autodetect" - val: StreamingPreferences.AC_AUTO - } ListElement { text: "Stereo" val: StreamingPreferences.AC_FORCE_STEREO diff --git a/app/streaming/audio/audio.cpp b/app/streaming/audio/audio.cpp index 32059fc5..05587018 100644 --- a/app/streaming/audio/audio.cpp +++ b/app/streaming/audio/audio.cpp @@ -52,23 +52,6 @@ bool Session::testAudio(int audioConfiguration) return ret; } -int Session::detectAudioConfiguration() -{ - IAudioRenderer* audioRenderer; - - audioRenderer = createAudioRenderer(); - if (audioRenderer == nullptr) { - // Hope for the best - return AUDIO_CONFIGURATION_STEREO; - } - - int audioConfig = audioRenderer->detectAudioConfiguration(); - - delete audioRenderer; - - return audioConfig; -} - int Session::arInit(int /* audioConfiguration */, const POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* /* arContext */, int /* arFlags */) @@ -92,15 +75,10 @@ int Session::arInit(int /* audioConfiguration */, } s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(); - if (s_ActiveSession->m_AudioRenderer == nullptr) { - opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder); - return -2; - } - if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(opusConfig)) { delete s_ActiveSession->m_AudioRenderer; opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder); - return -3; + return -2; } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, @@ -123,16 +101,40 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength) { int samplesDecoded; - samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder, - (unsigned char*)sampleData, - sampleLength, - s_ActiveSession->m_OpusDecodeBuffer, - SAMPLES_PER_FRAME, - 0); - if (samplesDecoded > 0) { - s_ActiveSession->m_AudioRenderer->submitAudio(s_ActiveSession->m_OpusDecodeBuffer, - static_cast(sizeof(short) * - samplesDecoded * - s_ActiveSession->m_AudioConfig.channelCount)); + s_ActiveSession->m_AudioSampleCount++; + + if (s_ActiveSession->m_AudioRenderer != nullptr) { + samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder, + (unsigned char*)sampleData, + sampleLength, + s_ActiveSession->m_OpusDecodeBuffer, + SAMPLES_PER_FRAME, + 0); + if (samplesDecoded > 0) { + if (!s_ActiveSession->m_AudioRenderer->submitAudio(s_ActiveSession->m_OpusDecodeBuffer, + static_cast( + sizeof(short) * + samplesDecoded * + s_ActiveSession->m_AudioConfig.channelCount))) { + + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Reinitializing audio renderer after failure"); + + delete s_ActiveSession->m_AudioRenderer; + s_ActiveSession->m_AudioRenderer = nullptr; + } + } + } + + // Only try to recreate the audio renderer every 200 samples (1 second) + // to avoid thrashing if the audio device is unavailable. It is + // safe to reinitialize here because we can't be torn down while + // the audio decoder/playback thread is still alive. + if (s_ActiveSession->m_AudioRenderer == nullptr && (s_ActiveSession->m_AudioSampleCount % 200) == 0) { + s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(); + if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(&s_ActiveSession->m_AudioConfig)) { + delete s_ActiveSession->m_AudioRenderer; + s_ActiveSession->m_AudioRenderer = nullptr; + } } } diff --git a/app/streaming/audio/renderers/portaudiorenderer.cpp b/app/streaming/audio/renderers/portaudiorenderer.cpp index 22532dcf..fdd6b3dc 100644 --- a/app/streaming/audio/renderers/portaudiorenderer.cpp +++ b/app/streaming/audio/renderers/portaudiorenderer.cpp @@ -74,14 +74,14 @@ bool PortAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* return true; } -void PortAudioRenderer::submitAudio(short* audioBuffer, int audioSize) +bool PortAudioRenderer::submitAudio(short* audioBuffer, int audioSize) { SDL_assert(audioSize == SAMPLES_PER_FRAME * m_ChannelCount * 2); // Check if there is space for this sample in the buffer. Again, this can race // but in the worst case, we'll not see the sample callback having consumed a sample. if (((m_WriteIndex + 1) % CIRCULAR_BUFFER_SIZE) == m_ReadIndex) { - return; + return true; } SDL_memcpy(&m_AudioBuffer[m_WriteIndex * CIRCULAR_BUFFER_STRIDE], audioBuffer, audioSize); @@ -102,33 +102,13 @@ void PortAudioRenderer::submitAudio(short* audioBuffer, int audioSize) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Pa_StartStream() failed: %s", Pa_GetErrorText(error)); - return; + return false; } m_Started = true; } -} -int PortAudioRenderer::detectAudioConfiguration() const -{ - const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(Pa_GetDefaultOutputDevice()); - if (deviceInfo == nullptr) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "Pa_GetDeviceInfo() failed"); - return false; - } - - // PulseAudio reports max output channels that don't - // correspond to any output devices (32 channels), so - // only use 5.1 surround sound if the output channel count - // is reasonable. Additionally, PortAudio doesn't do remixing - // for quadraphonic, so only use 5.1 if we have 6 or more channels. - if (deviceInfo->maxOutputChannels == 6 || deviceInfo->maxOutputChannels == 8) { - return AUDIO_CONFIGURATION_51_SURROUND; - } - else { - return AUDIO_CONFIGURATION_STEREO; - } + return true; } int PortAudioRenderer::paStreamCallback(const void*, void* output, unsigned long frameCount, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* userData) diff --git a/app/streaming/audio/renderers/portaudiorenderer.h b/app/streaming/audio/renderers/portaudiorenderer.h index 758642dc..4288fff7 100644 --- a/app/streaming/audio/renderers/portaudiorenderer.h +++ b/app/streaming/audio/renderers/portaudiorenderer.h @@ -18,9 +18,7 @@ public: virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig); - virtual void submitAudio(short* audioBuffer, int audioSize); - - virtual int detectAudioConfiguration() const; + virtual bool submitAudio(short* audioBuffer, int audioSize); static int paStreamCallback(const void *input, void *output, diff --git a/app/streaming/audio/renderers/renderer.h b/app/streaming/audio/renderers/renderer.h index 11ee64b8..0fbdad4f 100644 --- a/app/streaming/audio/renderers/renderer.h +++ b/app/streaming/audio/renderers/renderer.h @@ -12,7 +12,6 @@ public: virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) = 0; - virtual void submitAudio(short* audioBuffer, int audioSize) = 0; - - virtual int detectAudioConfiguration() const = 0; + // Return false if an unrecoverable error has occurred and the renderer must be reinitialized + virtual bool submitAudio(short* audioBuffer, int audioSize) = 0; }; diff --git a/app/streaming/audio/renderers/sdl.h b/app/streaming/audio/renderers/sdl.h index f1eb42af..ea8602e8 100644 --- a/app/streaming/audio/renderers/sdl.h +++ b/app/streaming/audio/renderers/sdl.h @@ -17,9 +17,7 @@ public: virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig); - virtual void submitAudio(short* audioBuffer, int audioSize); - - virtual int detectAudioConfiguration() const; + virtual bool submitAudio(short* audioBuffer, int audioSize); private: SDL_AudioDeviceID m_AudioDevice; diff --git a/app/streaming/audio/renderers/sdlaud.cpp b/app/streaming/audio/renderers/sdlaud.cpp index 17928191..bb3a4dd7 100644 --- a/app/streaming/audio/renderers/sdlaud.cpp +++ b/app/streaming/audio/renderers/sdlaud.cpp @@ -10,47 +10,6 @@ #define STOP_THE_WORLD_LIMIT 20 #define DROP_RATIO_DENOM 32 -// This isn't accurate on macOS and Linux (PulseAudio), -// since they both report supporting a large number of -// channels, regardless of the actual output device. -int SdlAudioRenderer::detectAudioConfiguration() const -{ - SDL_AudioSpec want, have; - SDL_AudioDeviceID dev; - - SDL_zero(want); - want.freq = 48000; - want.format = AUDIO_S16; - want.channels = 6; - want.samples = 1024; - - // Try to open for 5.1 surround sound, but allow SDL to tell us that's - // not available. - dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_CHANNELS_CHANGE); - if (dev == 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "Failed to open audio device"); - // We'll probably have issues during audio stream init, but we'll - // try anyway - return AUDIO_CONFIGURATION_STEREO; - } - - SDL_CloseAudioDevice(dev); - - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, - "Audio device has %d channels", have.channels); - - if (have.channels > 2) { - // We don't support quadraphonic or 7.1 surround, but SDL - // should be able to downmix or upmix better from 5.1 than - // from stereo, so use 5.1 in non-stereo cases. - return AUDIO_CONFIGURATION_51_SURROUND; - } - else { - return AUDIO_CONFIGURATION_STEREO; - } -} - SdlAudioRenderer::SdlAudioRenderer() : m_AudioDevice(0), m_ChannelCount(0), @@ -141,7 +100,7 @@ SdlAudioRenderer::~SdlAudioRenderer() SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO)); } -void SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize) +bool SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize) { m_SampleIndex++; @@ -181,13 +140,13 @@ void SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize) // Hard drops happen all at once to forcefully // resync with the source. m_PendingHardDrops--; - return; + return true; } else if (m_PendingDrops != 0 && m_SampleIndex % DROP_RATIO_DENOM == 0) { // Normal drops are interspersed with the audio data // to hide the glitches. m_PendingDrops--; - return; + return true; } if (SDL_QueueAudio(m_AudioDevice, audioBuffer, audioSize) < 0) { @@ -195,4 +154,6 @@ void SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize) "Failed to queue audio sample: %s", SDL_GetError()); } + + return true; } diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index fdc98971..a99aba03 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -288,7 +288,8 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere m_DisplayOriginY(0), m_PendingWindowedTransition(false), m_OpusDecoder(nullptr), - m_AudioRenderer(nullptr) + m_AudioRenderer(nullptr), + m_AudioSampleCount(0) { } @@ -327,10 +328,6 @@ void Session::initialize() switch (m_Preferences->audioConfig) { - case StreamingPreferences::AC_AUTO: - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Autodetecting audio configuration"); - m_StreamConfig.audioConfiguration = detectAudioConfiguration(); - break; case StreamingPreferences::AC_FORCE_STEREO: m_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO; break; diff --git a/app/streaming/session.h b/app/streaming/session.h index 9918bb1e..51f2e655 100644 --- a/app/streaming/session.h +++ b/app/streaming/session.h @@ -124,6 +124,7 @@ private: short m_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME]; IAudioRenderer* m_AudioRenderer; OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig; + int m_AudioSampleCount; static AUDIO_RENDERER_CALLBACKS k_AudioCallbacks; static CONNECTION_LISTENER_CALLBACKS k_ConnCallbacks;