diff --git a/app/streaming/audio/audio.cpp b/app/streaming/audio/audio.cpp index 4668eec9..2cbec50b 100644 --- a/app/streaming/audio/audio.cpp +++ b/app/streaming/audio/audio.cpp @@ -8,6 +8,7 @@ #include "renderers/sdl.h" #include +#include #define TRY_INIT_RENDERER(renderer, opusConfig) \ { \ @@ -183,7 +184,7 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength) s_ActiveSession->m_AudioSampleCount++; // If audio is muted, don't decode or play the audio - if (s_ActiveSession->m_AudioMuted) { + if (s_ActiveSession->m_AudioMuted.load(std::memory_order_relaxed)) { return; } @@ -217,6 +218,27 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength) if (samplesDecoded > 0) { SDL_assert(desiredBufferSize >= frameSize * samplesDecoded); desiredBufferSize = frameSize * samplesDecoded; + + const float volume = s_ActiveSession->getAudioVolumeScalar(); + if (volume <= 0.0f) { + SDL_memset(buffer, 0, desiredBufferSize); + } + else if (volume < 1.0f) { + const int totalSamples = samplesDecoded * s_ActiveSession->m_ActiveAudioConfig.channelCount; + if (s_ActiveSession->m_AudioRenderer->getAudioBufferFormat() == IAudioRenderer::AudioFormat::Float32NE) { + float* output = (float*)buffer; + for (int i = 0; i < totalSamples; i++) { + output[i] = SDL_clamp(output[i] * volume, -1.0f, 1.0f); + } + } + else { + short* output = (short*)buffer; + for (int i = 0; i < totalSamples; i++) { + const int scaled = (int) std::lround((float)output[i] * volume); + output[i] = (short)SDL_clamp(scaled, -32768, 32767); + } + } + } } else { desiredBufferSize = 0; diff --git a/app/streaming/input/input.cpp b/app/streaming/input/input.cpp index 844e0ab4..ddeebec4 100644 --- a/app/streaming/input/input.cpp +++ b/app/streaming/input/input.cpp @@ -116,6 +116,21 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i m_SpecialKeyCombos[KeyComboToggleGamepadInput].scanCode = SDL_SCANCODE_G; m_SpecialKeyCombos[KeyComboToggleGamepadInput].enabled = true; + m_SpecialKeyCombos[KeyComboVolumeUp].keyCombo = KeyComboVolumeUp; + m_SpecialKeyCombos[KeyComboVolumeUp].keyCode = SDLK_u; + m_SpecialKeyCombos[KeyComboVolumeUp].scanCode = SDL_SCANCODE_U; + m_SpecialKeyCombos[KeyComboVolumeUp].enabled = true; + + m_SpecialKeyCombos[KeyComboVolumeDown].keyCombo = KeyComboVolumeDown; + m_SpecialKeyCombos[KeyComboVolumeDown].keyCode = SDLK_j; + m_SpecialKeyCombos[KeyComboVolumeDown].scanCode = SDL_SCANCODE_J; + m_SpecialKeyCombos[KeyComboVolumeDown].enabled = true; + + m_SpecialKeyCombos[KeyComboToggleMute].keyCombo = KeyComboToggleMute; + m_SpecialKeyCombos[KeyComboToggleMute].keyCode = SDLK_n; + m_SpecialKeyCombos[KeyComboToggleMute].scanCode = SDL_SCANCODE_N; + m_SpecialKeyCombos[KeyComboToggleMute].enabled = true; + m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].keyCombo = KeyComboTogglePointerRegionLock; m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].keyCode = SDLK_l; m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].scanCode = SDL_SCANCODE_L; diff --git a/app/streaming/input/input.h b/app/streaming/input/input.h index 91a020ce..050f150c 100644 --- a/app/streaming/input/input.h +++ b/app/streaming/input/input.h @@ -167,6 +167,9 @@ private: KeyComboPasteText, KeyComboToggleKeyboardMouseInput, KeyComboToggleGamepadInput, + KeyComboVolumeUp, + KeyComboVolumeDown, + KeyComboToggleMute, KeyComboTogglePointerRegionLock, KeyComboQuitAndExit, KeyComboMax diff --git a/app/streaming/input/keyboard.cpp b/app/streaming/input/keyboard.cpp index 819837c0..1f8a8214 100644 --- a/app/streaming/input/keyboard.cpp +++ b/app/streaming/input/keyboard.cpp @@ -150,6 +150,30 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo) } break; + case KeyComboVolumeUp: + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Detected volume up combo"); + if (auto session = Session::get(); session != nullptr) { + session->adjustAudioVolume(0.05f); + } + break; + + case KeyComboVolumeDown: + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Detected volume down combo"); + if (auto session = Session::get(); session != nullptr) { + session->adjustAudioVolume(-0.05f); + } + break; + + case KeyComboToggleMute: + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Detected audio mute toggle combo"); + if (auto session = Session::get(); session != nullptr) { + session->toggleAudioMute(); + } + break; + case KeyComboTogglePointerRegionLock: SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Detected pointer region lock toggle combo"); diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index c314ae83..aa18860f 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -28,6 +28,7 @@ #define SDL_CODE_GAMECONTROLLER_SET_MOTION_EVENT_STATE 103 #define SDL_CODE_GAMECONTROLLER_SET_CONTROLLER_LED 104 #define SDL_CODE_GAMECONTROLLER_SET_ADAPTIVE_TRIGGERS 105 +#define SDL_CODE_HIDE_STATUS_OVERLAY 106 #include @@ -187,12 +188,14 @@ void Session::clConnectionStatusUpdate(int connectionStatus) switch (connectionStatus) { case CONN_STATUS_POOR: + s_ActiveSession->m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed); s_ActiveSession->m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, s_ActiveSession->m_StreamConfig.bitrate > 5000 ? "Slow connection to PC\nReduce your bitrate" : "Poor connection to PC"); s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true); break; case CONN_STATUS_OKAY: + s_ActiveSession->m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed); s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false); break; } @@ -556,8 +559,11 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere m_VideoDecoder(nullptr), m_DecoderLock(SDL_CreateMutex()), m_AudioMuted(false), + m_ManualAudioMuted(false), + m_AudioVolumeScalar(1.0f), m_AllowGamepadInput(true), m_AllowKeyboardMouseInput(true), + m_StatusOverlayGeneration(0), m_QtWindow(nullptr), m_UnexpectedTermination(true), // Failure prior to streaming is unexpected m_InputHandler(nullptr), @@ -579,6 +585,43 @@ void Session::setGamepadInputAllowed(bool allowed) notifyInputPermissionState(); } +void Session::setAudioVolumeScalar(float scalar) +{ + scalar = SDL_clamp(scalar, 0.0f, 1.0f); + m_AudioVolumeScalar.store(scalar, std::memory_order_relaxed); + notifyAudioVolumeState(); +} + +void Session::adjustAudioVolume(float delta) +{ + const float current = getAudioVolumeScalar(); + const float next = SDL_clamp(current + delta, 0.0f, 1.0f); + setAudioVolumeScalar(next); +} + +void Session::toggleAudioMute() +{ + const bool muted = !m_ManualAudioMuted.load(std::memory_order_relaxed); + m_ManualAudioMuted.store(muted, std::memory_order_relaxed); + + updateEffectiveAudioMuteState(); + notifyAudioVolumeState(); +} + +void Session::notifyAudioVolumeState() +{ + char buffer[64]; + if (m_ManualAudioMuted.load(std::memory_order_relaxed)) { + SDL_snprintf(buffer, sizeof(buffer), "Volume: MUTED"); + } + else { + const int percent = (int)SDL_roundf(getAudioVolumeScalar() * 100.0f); + SDL_snprintf(buffer, sizeof(buffer), "Volume: %d%%", percent); + } + + showTemporaryStatusOverlay(buffer); +} + void Session::setKeyboardMouseInputAllowed(bool allowed) { bool previous = m_AllowKeyboardMouseInput.exchange(allowed, std::memory_order_relaxed); @@ -608,8 +651,41 @@ void Session::notifyInputPermissionState() isKeyboardMouseInputAllowed() ? "ON" : "OFF", isGamepadInputAllowed() ? "ON" : "OFF"); - m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, buffer); + showTemporaryStatusOverlay(buffer); +} + +Uint32 Session::statusOverlayTimeoutCallback(Uint32, void* param) +{ + SDL_Event event = {}; + event.type = SDL_USEREVENT; + event.user.code = SDL_CODE_HIDE_STATUS_OVERLAY; + event.user.data1 = param; + SDL_PushEvent(&event); + + return 0; +} + +void Session::showTemporaryStatusOverlay(const char* text, Uint32 timeoutMs) +{ + if (m_MouseEmulationRefCount > 0) { + return; + } + + m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, text); m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true); + + const uint32_t generation = m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed) + 1; + SDL_AddTimer(timeoutMs, statusOverlayTimeoutCallback, (void*)(uintptr_t)generation); +} + +void Session::updateEffectiveAudioMuteState() +{ + bool muted = m_ManualAudioMuted.load(std::memory_order_relaxed); + if (!muted && m_Preferences->muteOnFocusLoss && m_Window != nullptr) { + muted = SDL_GetKeyboardFocus() != m_Window; + } + + m_AudioMuted.store(muted, std::memory_order_relaxed); } Session::~Session() @@ -1584,10 +1660,12 @@ void Session::notifyMouseEmulationMode(bool enabled) // We re-use the status update overlay for mouse mode notification if (m_MouseEmulationRefCount > 0) { + m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed); m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, "Gamepad mouse mode active\nLong press Start to deactivate"); m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true); } else { + m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed); m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false); } } @@ -1788,6 +1866,13 @@ void Session::start() // We're now active s_ActiveSession = this; + m_AllowGamepadInput.store(true, std::memory_order_relaxed); + m_AllowKeyboardMouseInput.store(true, std::memory_order_relaxed); + m_ManualAudioMuted.store(false, std::memory_order_relaxed); + m_AudioMuted.store(false, std::memory_order_relaxed); + m_AudioVolumeScalar.store(1.0f, std::memory_order_relaxed); + m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed); + // Initialize the gamepad code with our preferences // NB: m_InputHandler must be initialize before starting the connection. m_InputHandler = new SdlInputHandler(*m_Preferences, m_StreamConfig.width, m_StreamConfig.height); @@ -2009,6 +2094,7 @@ void Session::exec() // issues that could cause indefinite timeouts, delayed joystick detection, // and other problems. if (!SDL_WaitEventTimeout(&event, 1000)) { + updateEffectiveAudioMuteState(); presence.runCallbacks(); continue; } @@ -2025,10 +2111,14 @@ void Session::exec() // ARM core in the Steam Link, so we will wait 10 ms instead. SDL_Delay(10); #endif + updateEffectiveAudioMuteState(); presence.runCallbacks(); continue; } #endif + + updateEffectiveAudioMuteState(); + switch (event.type) { case SDL_QUIT: SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, @@ -2070,6 +2160,12 @@ void Session::exec() m_InputHandler->setAdaptiveTriggers((uint16_t)(uintptr_t)event.user.data1, (DualSenseOutputReport *)event.user.data2); break; + case SDL_CODE_HIDE_STATUS_OVERLAY: + if ((uint32_t)(uintptr_t)event.user.data1 == m_StatusOverlayGeneration.load(std::memory_order_relaxed) && + m_MouseEmulationRefCount == 0) { + m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false); + } + break; default: SDL_assert(false); } @@ -2079,15 +2175,9 @@ void Session::exec() // Early handling of some events switch (event.window.event) { case SDL_WINDOWEVENT_FOCUS_LOST: - if (m_Preferences->muteOnFocusLoss) { - m_AudioMuted = true; - } m_InputHandler->notifyFocusLost(); break; case SDL_WINDOWEVENT_FOCUS_GAINED: - if (m_Preferences->muteOnFocusLoss) { - m_AudioMuted = false; - } m_InputHandler->notifyFocusGained(); break; case SDL_WINDOWEVENT_LEAVE: diff --git a/app/streaming/session.h b/app/streaming/session.h index d232bdbc..c0c01815 100644 --- a/app/streaming/session.h +++ b/app/streaming/session.h @@ -142,6 +142,16 @@ public: void toggleKeyboardMouseInputAllowed(); void notifyInputPermissionState(); + float getAudioVolumeScalar() const + { + return m_AudioVolumeScalar.load(std::memory_order_relaxed); + } + + void setAudioVolumeScalar(float scalar); + void adjustAudioVolume(float delta); + void toggleAudioMute(); + void notifyAudioVolumeState(); + signals: void stageStarting(QString stage); @@ -259,6 +269,12 @@ private: static int drSubmitDecodeUnit(PDECODE_UNIT du); + static + Uint32 statusOverlayTimeoutCallback(Uint32 interval, void* param); + + void showTemporaryStatusOverlay(const char* text, Uint32 timeoutMs = 1500); + void updateEffectiveAudioMuteState(); + StreamingPreferences* m_Preferences; bool m_IsFullScreen; SupportedVideoFormatList m_SupportedVideoFormats; // Sorted in order of descending priority @@ -271,9 +287,12 @@ private: IVideoDecoder* m_VideoDecoder; SDL_mutex* m_DecoderLock; bool m_AudioDisabled; - bool m_AudioMuted; + std::atomic m_AudioMuted; + std::atomic m_ManualAudioMuted; + std::atomic m_AudioVolumeScalar; std::atomic m_AllowGamepadInput; std::atomic m_AllowKeyboardMouseInput; + std::atomic m_StatusOverlayGeneration; Uint32 m_FullScreenFlag; QQuickWindow* m_QtWindow; bool m_UnexpectedTermination;