From c200600c65e6091fdc5278ac35b1cc319a563258 Mon Sep 17 00:00:00 2001 From: Chris Pable Date: Thu, 2 Aug 2018 10:28:24 -0500 Subject: [PATCH] Fix audio drivers with a low sample count maximum value (#1697) --- src/GameLoop.cpp | 80 ++++++++++++------- src/GameLoop.h | 2 + src/PrefsManager.cpp | 1 + src/PrefsManager.h | 1 + src/RageSoundManager.cpp | 6 ++ src/RageSoundManager.h | 1 + src/arch/Sound/RageSoundDriver.h | 5 ++ .../RageSoundDriver_Generic_Software.cpp | 70 +++++++++++++--- 8 files changed, 123 insertions(+), 43 deletions(-) diff --git a/src/GameLoop.cpp b/src/GameLoop.cpp index f88d9c1609..a19e82d9eb 100644 --- a/src/GameLoop.cpp +++ b/src/GameLoop.cpp @@ -249,6 +249,54 @@ namespace g_NewTheme= RString(); } } +static bool m_bUpdatedDuringVBLANK = false; +void GameLoop::UpdateAllButDraw(bool bRunningFromVBLANK) +{ + //if we are running our once per frame routine and we were already run from VBLANK, we did the work already + if (!bRunningFromVBLANK && m_bUpdatedDuringVBLANK) + { + m_bUpdatedDuringVBLANK = false; + return; //would it kill us to run it again or do we want to draw asap? + } + + //if vblank called us, we will tell the game loop we received an update for the frame it wants to process + if (bRunningFromVBLANK) m_bUpdatedDuringVBLANK = true; + else m_bUpdatedDuringVBLANK = false; + + // Update our stuff + float fDeltaTime = g_GameplayTimer.GetDeltaTime(); + + if (g_fConstantUpdateDeltaSeconds > 0) + fDeltaTime = g_fConstantUpdateDeltaSeconds; + + CheckGameLoopTimerSkips(fDeltaTime); + + fDeltaTime *= g_fUpdateRate; + + // Update SOUNDMAN early (before any RageSound::GetPosition calls), to flush position data. + SOUNDMAN->Update(); + + /* Update song beat information -before- calling update on all the classes that + * depend on it. If you don't do this first, the classes are all acting on old + * information and will lag. (but no longer fatally, due to timestamping -glenn) */ + SOUND->Update(fDeltaTime); + TEXTUREMAN->Update(fDeltaTime); + GAMESTATE->Update(fDeltaTime); + SCREENMAN->Update(fDeltaTime); + MEMCARDMAN->Update(); + NSMAN->Update(fDeltaTime); + + /* Important: Process input AFTER updating game logic, or input will be + * acting on song beat from last frame */ + HandleInputEvents(fDeltaTime); + + //bandaid for low max audio sample counter + SOUNDMAN->low_sample_count_workaround(); + LIGHTSMAN->Update(fDeltaTime); + +} + + void GameLoop::RunGameLoop() { @@ -268,47 +316,19 @@ void GameLoop::RunGameLoop() DoChangeTheme(); } - // Update - float fDeltaTime = g_GameplayTimer.GetDeltaTime(); - - if( g_fConstantUpdateDeltaSeconds > 0 ) - fDeltaTime = g_fConstantUpdateDeltaSeconds; - - CheckGameLoopTimerSkips( fDeltaTime ); - - fDeltaTime *= g_fUpdateRate; - CheckFocus(); - // Update SOUNDMAN early (before any RageSound::GetPosition calls), to flush position data. - SOUNDMAN->Update(); - - /* Update song beat information -before- calling update on all the classes that - * depend on it. If you don't do this first, the classes are all acting on old - * information and will lag. (but no longer fatally, due to timestamping -glenn) */ - SOUND->Update( fDeltaTime ); - TEXTUREMAN->Update( fDeltaTime ); - GAMESTATE->Update( fDeltaTime ); - SCREENMAN->Update( fDeltaTime ); - MEMCARDMAN->Update(); - NSMAN->Update( fDeltaTime ); - - /* Important: Process input AFTER updating game logic, or input will be - * acting on song beat from last frame */ - HandleInputEvents( fDeltaTime ); + UpdateAllButDraw(false); if( INPUTMAN->DevicesChanged() ) { - INPUTFILTER->Reset(); // fix "buttons stuck" if button held while unplugged + INPUTFILTER->Reset(); // fix "buttons stuck" once per frame if button held while unplugged INPUTMAN->LoadDrivers(); RString sMessage; if( INPUTMAPPER->CheckForChangedInputDevicesAndRemap(sMessage) ) SCREENMAN->SystemMessage( sMessage ); } - LIGHTSMAN->Update( fDeltaTime ); - - // Render SCREENMAN->Draw(); } diff --git a/src/GameLoop.h b/src/GameLoop.h index b95dee6dae..97f9d1f6d4 100644 --- a/src/GameLoop.h +++ b/src/GameLoop.h @@ -4,11 +4,13 @@ namespace GameLoop { void RunGameLoop(); + void UpdateAllButDraw( bool bRunningFromVBLANK); void SetUpdateRate( float fUpdateRate ); void ChangeTheme(const RString &sNewTheme); void ChangeGame(const RString& new_game, const RString& new_theme= ""); void StartConcurrentRendering(); void FinishConcurrentRendering(); + }; #endif diff --git a/src/PrefsManager.cpp b/src/PrefsManager.cpp index 8ff5028815..ed679d2af0 100644 --- a/src/PrefsManager.cpp +++ b/src/PrefsManager.cpp @@ -274,6 +274,7 @@ PrefsManager::PrefsManager() : m_bSmoothLines ( "SmoothLines", false ), m_iSoundWriteAhead ( "SoundWriteAhead", 0 ), m_iSoundDevice ( "SoundDevice", "" ), + m_iRageSoundSampleCountClamp ("RageSoundSampleCountClamp", 0), //some sound drivers mask the sample location number, the most popular number for this is 2^27, this causes lockup after ~50 minutes at 44.1khz sample rate m_iSoundPreferredSampleRate ( "SoundPreferredSampleRate", 0 ), m_sLightsStepsDifficulty ( "LightsStepsDifficulty", "medium" ), m_bAllowUnacceleratedRenderer ( "AllowUnacceleratedRenderer", false ), diff --git a/src/PrefsManager.h b/src/PrefsManager.h index bc0e88522e..5445f06bec 100644 --- a/src/PrefsManager.h +++ b/src/PrefsManager.h @@ -282,6 +282,7 @@ public: Preference m_bSmoothLines; Preference m_iSoundWriteAhead; Preference m_iSoundDevice; + Preference m_iRageSoundSampleCountClamp; Preference m_iSoundPreferredSampleRate; Preference m_sLightsStepsDifficulty; Preference m_bAllowUnacceleratedRenderer; diff --git a/src/RageSoundManager.cpp b/src/RageSoundManager.cpp index cc42687041..bc6750a746 100644 --- a/src/RageSoundManager.cpp +++ b/src/RageSoundManager.cpp @@ -55,6 +55,12 @@ RageSoundManager::~RageSoundManager() m_mapPreloadedSounds.clear(); } + +void RageSoundManager::low_sample_count_workaround() +{ + m_pDriver->low_sample_count_workaround(); +} + void RageSoundManager::fix_bogus_sound_driver_pref(RString const& valid_setting) { g_sSoundDrivers.Set(valid_setting); diff --git a/src/RageSoundManager.h b/src/RageSoundManager.h index f6db421e74..b5677f188f 100644 --- a/src/RageSoundManager.h +++ b/src/RageSoundManager.h @@ -46,6 +46,7 @@ public: void AddLoadedSound( const RString &sPath, RageSoundReader_Preload *pSound ); void fix_bogus_sound_driver_pref(RString const& valid_setting); + void low_sample_count_workaround(); private: map m_mapPreloadedSounds; diff --git a/src/arch/Sound/RageSoundDriver.h b/src/arch/Sound/RageSoundDriver.h index f173488f50..1a51221c02 100644 --- a/src/arch/Sound/RageSoundDriver.h +++ b/src/arch/Sound/RageSoundDriver.h @@ -50,11 +50,14 @@ public: * RageSound::CommitPlayingPosition. */ int64_t GetHardwareFrame( RageTimer *pTimer ) const; virtual int64_t GetPosition() const = 0; + void low_sample_count_workaround(); /* When a sound is finished playing (GetDataToPlay returns 0) and the sound has * been completely flushed (so GetPosition is no longer meaningful), call * RageSoundBase::SoundIsFinishedPlaying(). */ + + /* Optional, if needed: */ virtual void Update(); @@ -199,6 +202,8 @@ private: int64_t ClampHardwareFrame( int64_t iHardwareFrame ) const; mutable int64_t m_iMaxHardwareFrame; + mutable int64_t m_iVMaxHardwareFrame; + mutable int32_t soundDriverMaxSamples = 0; bool m_bShutdownDecodeThread; diff --git a/src/arch/Sound/RageSoundDriver_Generic_Software.cpp b/src/arch/Sound/RageSoundDriver_Generic_Software.cpp index 5c4fd7d62f..34df51752e 100644 --- a/src/arch/Sound/RageSoundDriver_Generic_Software.cpp +++ b/src/arch/Sound/RageSoundDriver_Generic_Software.cpp @@ -1,6 +1,6 @@ #include "global.h" #include "RageSoundDriver.h" - +#include "PrefsManager.h" #include "RageLog.h" #include "RageSound.h" #include "RageUtil.h" @@ -437,14 +437,20 @@ void RageSoundDriver::SetDecodeBufferSize( int iFrames ) frames_to_buffer = iFrames; } +void RageSoundDriver::low_sample_count_workaround() +{ + if (soundDriverMaxSamples != 0) GetHardwareFrame(NULL); +} + RageSoundDriver::RageSoundDriver(): m_Mutex("RageSoundDriver"), m_SoundListMutex("SoundListMutex") { m_bShutdownDecodeThread = false; m_iMaxHardwareFrame = 0; + m_iVMaxHardwareFrame = 0; SetDecodeBufferSize( 4096 ); - + soundDriverMaxSamples = PREFSMAN->m_iRageSoundSampleCountClamp; m_DecodeThread.SetName("Decode thread"); } @@ -470,24 +476,62 @@ int64_t RageSoundDriver::ClampHardwareFrame( int64_t iHardwareFrame ) const /* It's sometimes possible for the hardware position to move backwards, usually * on underrun. We can try to prevent this in each driver, but it's an obscure * error, so let's clamp the result here instead. */ - if( iHardwareFrame < m_iMaxHardwareFrame ) + + /* New extra logic for devices and drivers that cant return large numbers in their sample position + * calculate a diff and if the current sample # is >= 0 and less than a minute based on sample rate + * check if the user set soundDriverMaxSamples to a value, if they did + * (this is usually 134217728 aka 2^27 for some reason) hndle the wrap around to a rolling counter + * otherwise do the old logic for underrun + */ + + + //iHardwareFrame %= 0x800000; // debug test a sample value max of about 3 minutes so I dont have to spend an hour per test + int64_t diff = iHardwareFrame - m_iMaxHardwareFrame; + if( diff < 0 ) { - /* Clamp the output to one per second, so one underruns don't cascade due to - * output spam. */ - static RageTimer last(RageZeroTimer); - if( last.IsZero() || last.Ago() > 1.0f ) + diff = 0; + int iMinuteSampleRate = GetSampleRate()*60; //get one minute worth of grace -- if you need more, there is very likely some other problem going on + //if we have a sample clamp and the new hardware frame is within a fresh minute of the sample rate max and have 'underrun' + if ((soundDriverMaxSamples>0) && (iHardwareFrame= 0) { - LOG->Trace( "RageSoundDriver: driver returned a lesser position (%d < %d)", - (int)iHardwareFrame, (int)m_iMaxHardwareFrame ); - last.Touch(); + LOG->Trace("RageSoundDriver: driver position mask adjustment hardware frame number: %d, last max frame number: %d, soundDriverMaxSamples: %d, iMinuteSampleRate: %d, m_iVMaxHardwareFrame: %d", + (int)iHardwareFrame, (int)m_iMaxHardwareFrame, soundDriverMaxSamples, iMinuteSampleRate, (int) m_iVMaxHardwareFrame); + diff = (soundDriverMaxSamples - m_iMaxHardwareFrame) + iHardwareFrame; + m_iMaxHardwareFrame = 0; + } + else + { + /* Clamp the output to one per second, so one underruns don't cascade due to + * output spam. */ + static RageTimer last(RageZeroTimer); + if (last.IsZero() || last.Ago() > 1.0f) + { + + //try to hand hold the user if their audio driver is possibly bad + int p = 21; // save some time, assume the buffer has at least a minute of cd quality audio -- 2^21 + while (pow(2,p) < m_iMaxHardwareFrame) + { + if (p == 31) break; //do not want to go beyond signed DWORD size + p++; + } + + LOG->Trace("RageSoundDriver: driver returned a lesser position (%d < %d). If this is a recurrent driver problem with your sound card and not an underrun, try setting the preference RageSoundSampleCountClamp to %d", + (int)iHardwareFrame, (int)m_iMaxHardwareFrame, (int)floor(pow(2.0, p))); + last.Touch(); + } + + //return m_iMaxHardwareFrame; + } - return m_iMaxHardwareFrame; } + m_iMaxHardwareFrame = iHardwareFrame = max( iHardwareFrame, m_iMaxHardwareFrame ); - return iHardwareFrame; + //return iHardwareFrame; + m_iVMaxHardwareFrame += diff; + return m_iVMaxHardwareFrame; } -int64_t RageSoundDriver::GetHardwareFrame( RageTimer *pTimestamp ) const +int64_t RageSoundDriver::GetHardwareFrame( RageTimer *pTimestamp=NULL ) const { if( pTimestamp == NULL ) return ClampHardwareFrame( GetPosition() );