From 64bc189010b6550e7da90307a065157a0ecbda89 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 13 Sep 2018 07:46:01 -0700 Subject: [PATCH] Fixes to SDL audio renderer and autodetection support --- app/app.pro | 2 +- app/gui/SettingsView.qml | 8 +- app/settings/streamingpreferences.cpp | 2 +- app/streaming/audio/audio.cpp | 26 ++++-- app/streaming/audio/renderers/sdlaud.cpp | 100 +++++++---------------- app/streaming/session.cpp | 6 +- app/streaming/session.hpp | 3 + 7 files changed, 59 insertions(+), 88 deletions(-) diff --git a/app/app.pro b/app/app.pro index 2e571dae..1ad56018 100644 --- a/app/app.pro +++ b/app/app.pro @@ -1,4 +1,4 @@ -QT += core quick network quickcontrols2 svg +QT += core quick network quickcontrols2 svg multimedia CONFIG += c++11 unix:!macx { diff --git a/app/gui/SettingsView.qml b/app/gui/SettingsView.qml index 488ca9c6..d7a1375e 100644 --- a/app/gui/SettingsView.qml +++ b/app/gui/SettingsView.qml @@ -386,14 +386,10 @@ ScrollView { textRole: "text" model: ListModel { id: audioListModel - // Detection of audio channels only works on Windows - // and even so, it's still unreliable because audio devices - // lie about how many channels they can support. Hide the - // option to autodetect until this is resolved. - /*ListElement { + ListElement { text: "Autodetect" val: StreamingPreferences.AC_AUTO - }*/ + } ListElement { text: "Stereo" val: StreamingPreferences.AC_FORCE_STEREO diff --git a/app/settings/streamingpreferences.cpp b/app/settings/streamingpreferences.cpp index 016ece44..2d5b2a9e 100644 --- a/app/settings/streamingpreferences.cpp +++ b/app/settings/streamingpreferences.cpp @@ -43,7 +43,7 @@ void StreamingPreferences::reload() enableMdns = settings.value(SER_MDNS, true).toBool(); mouseAcceleration = settings.value(SER_MOUSEACCELERATION, false).toBool(); audioConfig = static_cast(settings.value(SER_AUDIOCFG, - static_cast(AudioConfig::AC_FORCE_STEREO)).toInt()); + static_cast(AudioConfig::AC_AUTO)).toInt()); videoCodecConfig = static_cast(settings.value(SER_VIDEOCFG, static_cast(VideoCodecConfig::VCC_AUTO)).toInt()); videoDecoderSelection = static_cast(settings.value(SER_VIDEODEC, diff --git a/app/streaming/audio/audio.cpp b/app/streaming/audio/audio.cpp index 1df4e7af..ba6c2f59 100644 --- a/app/streaming/audio/audio.cpp +++ b/app/streaming/audio/audio.cpp @@ -70,8 +70,7 @@ int Session::arInit(int /* audioConfiguration */, return -2; } - s_ActiveSession->m_AudioRenderer->prepareForPlayback(opusConfig); - if (s_ActiveSession->m_AudioRenderer == nullptr) { + if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(opusConfig)) { delete s_ActiveSession->m_AudioRenderer; opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder); return -3; @@ -86,12 +85,21 @@ int Session::arInit(int /* audioConfiguration */, void Session::arCleanup() { - delete s_ActiveSession->m_AudioRenderer; + // m_AudioRenderer is deleted in cleanupAudioRenderer() opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder); s_ActiveSession->m_OpusDecoder = nullptr; } +// This is called on the main thread +void Session::cleanupAudioRendererOnMainThread() +{ + SDL_AtomicLock(&m_AudioRendererLock); + delete m_AudioRenderer; + m_AudioRenderer = nullptr; + SDL_AtomicUnlock(&m_AudioRendererLock); +} + void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength) { int samplesDecoded; @@ -103,9 +111,13 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength) 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)); + SDL_AtomicLock(&s_ActiveSession->m_AudioRendererLock); + if (s_ActiveSession->m_AudioRenderer != nullptr) { + s_ActiveSession->m_AudioRenderer->submitAudio(s_ActiveSession->m_OpusDecodeBuffer, + static_cast(sizeof(short) * + samplesDecoded * + s_ActiveSession->m_AudioConfig.channelCount)); + } + SDL_AtomicUnlock(&s_ActiveSession->m_AudioRendererLock); } } diff --git a/app/streaming/audio/renderers/sdlaud.cpp b/app/streaming/audio/renderers/sdlaud.cpp index a0c36adf..a414819d 100644 --- a/app/streaming/audio/renderers/sdlaud.cpp +++ b/app/streaming/audio/renderers/sdlaud.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #define MIN_QUEUED_FRAMES 2 @@ -10,69 +11,31 @@ #define STOP_THE_WORLD_LIMIT 20 #define DROP_RATIO_DENOM 32 +// Detecting this with SDL is quite problematic, so we'll use Qt's +// multimedia framework to do so. It appears to be actually +// accurate on Linux and macOS, unlike using SDL and relying +// on a channel change in the format received. int SdlAudioRenderer::detectAudioConfiguration() { - SDL_AudioSpec want, have; - SDL_AudioDeviceID dev; - int ret; - - if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s", - SDL_GetError()); - return AUDIO_CONFIGURATION_STEREO; - } - - 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 - ret = AUDIO_CONFIGURATION_STEREO; - goto Exit; - } - - SDL_CloseAudioDevice(dev); + int preferredChannelCount = QAudioDeviceInfo::defaultOutputDevice().preferredFormat().channelCount(); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, - "Audio device has %d channels", have.channels); + "Audio output device prefers %d channel configuration", + preferredChannelCount); - 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. - ret = AUDIO_CONFIGURATION_51_SURROUND; + // We can better downmix 5.1 to quad than we can upmix stereo + if (preferredChannelCount > 2) { + return AUDIO_CONFIGURATION_51_SURROUND; } else { - ret = AUDIO_CONFIGURATION_STEREO; + return AUDIO_CONFIGURATION_STEREO; } - -Exit: - SDL_QuitSubSystem(SDL_INIT_AUDIO); - return ret; } bool SdlAudioRenderer::testAudio(int audioConfiguration) { SDL_AudioSpec want, have; SDL_AudioDeviceID dev; - bool ret; - - if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "Audio test - SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s", - SDL_GetError()); - return false; - } SDL_zero(want); want.freq = 48000; @@ -88,8 +51,7 @@ bool SdlAudioRenderer::testAudio(int audioConfiguration) break; default: SDL_assert(false); - ret = false; - goto Exit; + return false; } // Test audio device for functionality @@ -98,8 +60,7 @@ bool SdlAudioRenderer::testAudio(int audioConfiguration) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Audio test - Failed to open audio device: %s", SDL_GetError()); - ret = false; - goto Exit; + return false; } SDL_CloseAudioDevice(dev); @@ -107,12 +68,7 @@ bool SdlAudioRenderer::testAudio(int audioConfiguration) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Audio test - Successful with %d channels", want.channels); - - ret = true; - -Exit: - SDL_QuitSubSystem(SDL_INIT_AUDIO); - return ret; + return true; } SdlAudioRenderer::SdlAudioRenderer() @@ -123,20 +79,18 @@ SdlAudioRenderer::SdlAudioRenderer() m_SampleIndex(0), m_BaselinePendingData(0) { - -} - -bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) -{ - SDL_AudioSpec want, have; - SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO)); if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s", SDL_GetError()); - return -1; + SDL_assert(SDL_WasInit(SDL_INIT_AUDIO)); } +} + +bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) +{ + SDL_AudioSpec want, have; SDL_zero(want); want.freq = opusConfig->sampleRate; @@ -155,7 +109,7 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* "Failed to open audio device: %s", SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); - return -1; + return false; } // SDL counts pending samples in the queued @@ -182,14 +136,16 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* // Start playback SDL_PauseAudioDevice(m_AudioDevice, 0); - return 0; + return true; } SdlAudioRenderer::~SdlAudioRenderer() { - // Stop playback - SDL_PauseAudioDevice(m_AudioDevice, 1); - SDL_CloseAudioDevice(m_AudioDevice); + if (m_AudioDevice != 0) { + // Stop playback + SDL_PauseAudioDevice(m_AudioDevice, 1); + SDL_CloseAudioDevice(m_AudioDevice); + } SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO)); diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 8d98e358..a6204650 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -279,7 +279,8 @@ Session::Session(NvComputer* computer, NvApp& app) m_DisplayOriginY(0), m_PendingWindowedTransition(false), m_OpusDecoder(nullptr), - m_AudioRenderer(nullptr) + m_AudioRenderer(nullptr), + m_AudioRendererLock(0) { } @@ -1110,6 +1111,9 @@ DispatchDeferredCleanup: m_VideoDecoder = nullptr; SDL_AtomicUnlock(&m_DecoderLock); + // Destroy the audio renderer which must also be done on the main thread + cleanupAudioRendererOnMainThread(); + SDL_DestroyWindow(m_Window); if (iconSurface != nullptr) { SDL_FreeSurface(iconSurface); diff --git a/app/streaming/session.hpp b/app/streaming/session.hpp index 8e3038ba..55209bba 100644 --- a/app/streaming/session.hpp +++ b/app/streaming/session.hpp @@ -54,6 +54,8 @@ private: int detectAudioConfiguration(); + void cleanupAudioRendererOnMainThread(); + bool testAudio(int audioConfiguration); void getWindowDimensions(int& x, int& y, @@ -124,6 +126,7 @@ private: short m_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME]; IAudioRenderer* m_AudioRenderer; OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig; + SDL_SpinLock m_AudioRendererLock; static AUDIO_RENDERER_CALLBACKS k_AudioCallbacks; static CONNECTION_LISTENER_CALLBACKS k_ConnCallbacks;