Phase 4 volume
Some checks are pending
Build / setup (push) Waiting to run
Build / build-appimage (push) Blocked by required conditions
Build / build-steamlink (push) Blocked by required conditions
Build / build-windows-macos (push) Blocked by required conditions

This commit is contained in:
Joey Yakimowich-Payne 2026-02-11 21:20:48 -07:00
commit 9e52a4200b
6 changed files with 182 additions and 9 deletions

View file

@ -8,6 +8,7 @@
#include "renderers/sdl.h" #include "renderers/sdl.h"
#include <Limelight.h> #include <Limelight.h>
#include <cmath>
#define TRY_INIT_RENDERER(renderer, opusConfig) \ #define TRY_INIT_RENDERER(renderer, opusConfig) \
{ \ { \
@ -183,7 +184,7 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
s_ActiveSession->m_AudioSampleCount++; s_ActiveSession->m_AudioSampleCount++;
// If audio is muted, don't decode or play the audio // 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; return;
} }
@ -217,6 +218,27 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
if (samplesDecoded > 0) { if (samplesDecoded > 0) {
SDL_assert(desiredBufferSize >= frameSize * samplesDecoded); SDL_assert(desiredBufferSize >= frameSize * samplesDecoded);
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 { else {
desiredBufferSize = 0; desiredBufferSize = 0;

View file

@ -116,6 +116,21 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
m_SpecialKeyCombos[KeyComboToggleGamepadInput].scanCode = SDL_SCANCODE_G; m_SpecialKeyCombos[KeyComboToggleGamepadInput].scanCode = SDL_SCANCODE_G;
m_SpecialKeyCombos[KeyComboToggleGamepadInput].enabled = true; 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].keyCombo = KeyComboTogglePointerRegionLock;
m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].keyCode = SDLK_l; m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].keyCode = SDLK_l;
m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].scanCode = SDL_SCANCODE_L; m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].scanCode = SDL_SCANCODE_L;

View file

@ -167,6 +167,9 @@ private:
KeyComboPasteText, KeyComboPasteText,
KeyComboToggleKeyboardMouseInput, KeyComboToggleKeyboardMouseInput,
KeyComboToggleGamepadInput, KeyComboToggleGamepadInput,
KeyComboVolumeUp,
KeyComboVolumeDown,
KeyComboToggleMute,
KeyComboTogglePointerRegionLock, KeyComboTogglePointerRegionLock,
KeyComboQuitAndExit, KeyComboQuitAndExit,
KeyComboMax KeyComboMax

View file

@ -150,6 +150,30 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
} }
break; 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: case KeyComboTogglePointerRegionLock:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected pointer region lock toggle combo"); "Detected pointer region lock toggle combo");

View file

@ -28,6 +28,7 @@
#define SDL_CODE_GAMECONTROLLER_SET_MOTION_EVENT_STATE 103 #define SDL_CODE_GAMECONTROLLER_SET_MOTION_EVENT_STATE 103
#define SDL_CODE_GAMECONTROLLER_SET_CONTROLLER_LED 104 #define SDL_CODE_GAMECONTROLLER_SET_CONTROLLER_LED 104
#define SDL_CODE_GAMECONTROLLER_SET_ADAPTIVE_TRIGGERS 105 #define SDL_CODE_GAMECONTROLLER_SET_ADAPTIVE_TRIGGERS 105
#define SDL_CODE_HIDE_STATUS_OVERLAY 106
#include <openssl/rand.h> #include <openssl/rand.h>
@ -187,12 +188,14 @@ void Session::clConnectionStatusUpdate(int connectionStatus)
switch (connectionStatus) switch (connectionStatus)
{ {
case CONN_STATUS_POOR: 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_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate,
s_ActiveSession->m_StreamConfig.bitrate > 5000 ? s_ActiveSession->m_StreamConfig.bitrate > 5000 ?
"Slow connection to PC\nReduce your bitrate" : "Poor connection to PC"); "Slow connection to PC\nReduce your bitrate" : "Poor connection to PC");
s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true); s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
break; break;
case CONN_STATUS_OKAY: case CONN_STATUS_OKAY:
s_ActiveSession->m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed);
s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false); s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
break; break;
} }
@ -556,8 +559,11 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
m_VideoDecoder(nullptr), m_VideoDecoder(nullptr),
m_DecoderLock(SDL_CreateMutex()), m_DecoderLock(SDL_CreateMutex()),
m_AudioMuted(false), m_AudioMuted(false),
m_ManualAudioMuted(false),
m_AudioVolumeScalar(1.0f),
m_AllowGamepadInput(true), m_AllowGamepadInput(true),
m_AllowKeyboardMouseInput(true), m_AllowKeyboardMouseInput(true),
m_StatusOverlayGeneration(0),
m_QtWindow(nullptr), m_QtWindow(nullptr),
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
m_InputHandler(nullptr), m_InputHandler(nullptr),
@ -579,6 +585,43 @@ void Session::setGamepadInputAllowed(bool allowed)
notifyInputPermissionState(); 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) void Session::setKeyboardMouseInputAllowed(bool allowed)
{ {
bool previous = m_AllowKeyboardMouseInput.exchange(allowed, std::memory_order_relaxed); bool previous = m_AllowKeyboardMouseInput.exchange(allowed, std::memory_order_relaxed);
@ -608,8 +651,41 @@ void Session::notifyInputPermissionState()
isKeyboardMouseInputAllowed() ? "ON" : "OFF", isKeyboardMouseInputAllowed() ? "ON" : "OFF",
isGamepadInputAllowed() ? "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); 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() Session::~Session()
@ -1584,10 +1660,12 @@ void Session::notifyMouseEmulationMode(bool enabled)
// We re-use the status update overlay for mouse mode notification // We re-use the status update overlay for mouse mode notification
if (m_MouseEmulationRefCount > 0) { 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.updateOverlayText(Overlay::OverlayStatusUpdate, "Gamepad mouse mode active\nLong press Start to deactivate");
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true); m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
} }
else { else {
m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed);
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false); m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
} }
} }
@ -1788,6 +1866,13 @@ void Session::start()
// We're now active // We're now active
s_ActiveSession = this; 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 // Initialize the gamepad code with our preferences
// NB: m_InputHandler must be initialize before starting the connection. // NB: m_InputHandler must be initialize before starting the connection.
m_InputHandler = new SdlInputHandler(*m_Preferences, m_StreamConfig.width, m_StreamConfig.height); 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, // issues that could cause indefinite timeouts, delayed joystick detection,
// and other problems. // and other problems.
if (!SDL_WaitEventTimeout(&event, 1000)) { if (!SDL_WaitEventTimeout(&event, 1000)) {
updateEffectiveAudioMuteState();
presence.runCallbacks(); presence.runCallbacks();
continue; continue;
} }
@ -2025,10 +2111,14 @@ void Session::exec()
// ARM core in the Steam Link, so we will wait 10 ms instead. // ARM core in the Steam Link, so we will wait 10 ms instead.
SDL_Delay(10); SDL_Delay(10);
#endif #endif
updateEffectiveAudioMuteState();
presence.runCallbacks(); presence.runCallbacks();
continue; continue;
} }
#endif #endif
updateEffectiveAudioMuteState();
switch (event.type) { switch (event.type) {
case SDL_QUIT: case SDL_QUIT:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
@ -2070,6 +2160,12 @@ void Session::exec()
m_InputHandler->setAdaptiveTriggers((uint16_t)(uintptr_t)event.user.data1, m_InputHandler->setAdaptiveTriggers((uint16_t)(uintptr_t)event.user.data1,
(DualSenseOutputReport *)event.user.data2); (DualSenseOutputReport *)event.user.data2);
break; 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: default:
SDL_assert(false); SDL_assert(false);
} }
@ -2079,15 +2175,9 @@ void Session::exec()
// Early handling of some events // Early handling of some events
switch (event.window.event) { switch (event.window.event) {
case SDL_WINDOWEVENT_FOCUS_LOST: case SDL_WINDOWEVENT_FOCUS_LOST:
if (m_Preferences->muteOnFocusLoss) {
m_AudioMuted = true;
}
m_InputHandler->notifyFocusLost(); m_InputHandler->notifyFocusLost();
break; break;
case SDL_WINDOWEVENT_FOCUS_GAINED: case SDL_WINDOWEVENT_FOCUS_GAINED:
if (m_Preferences->muteOnFocusLoss) {
m_AudioMuted = false;
}
m_InputHandler->notifyFocusGained(); m_InputHandler->notifyFocusGained();
break; break;
case SDL_WINDOWEVENT_LEAVE: case SDL_WINDOWEVENT_LEAVE:

View file

@ -142,6 +142,16 @@ public:
void toggleKeyboardMouseInputAllowed(); void toggleKeyboardMouseInputAllowed();
void notifyInputPermissionState(); 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: signals:
void stageStarting(QString stage); void stageStarting(QString stage);
@ -259,6 +269,12 @@ private:
static static
int drSubmitDecodeUnit(PDECODE_UNIT du); 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; StreamingPreferences* m_Preferences;
bool m_IsFullScreen; bool m_IsFullScreen;
SupportedVideoFormatList m_SupportedVideoFormats; // Sorted in order of descending priority SupportedVideoFormatList m_SupportedVideoFormats; // Sorted in order of descending priority
@ -271,9 +287,12 @@ private:
IVideoDecoder* m_VideoDecoder; IVideoDecoder* m_VideoDecoder;
SDL_mutex* m_DecoderLock; SDL_mutex* m_DecoderLock;
bool m_AudioDisabled; bool m_AudioDisabled;
bool m_AudioMuted; std::atomic<bool> m_AudioMuted;
std::atomic<bool> m_ManualAudioMuted;
std::atomic<float> m_AudioVolumeScalar;
std::atomic<bool> m_AllowGamepadInput; std::atomic<bool> m_AllowGamepadInput;
std::atomic<bool> m_AllowKeyboardMouseInput; std::atomic<bool> m_AllowKeyboardMouseInput;
std::atomic<uint32_t> m_StatusOverlayGeneration;
Uint32 m_FullScreenFlag; Uint32 m_FullScreenFlag;
QQuickWindow* m_QtWindow; QQuickWindow* m_QtWindow;
bool m_UnexpectedTermination; bool m_UnexpectedTermination;