2019-12-26 14:45:42 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2018-09-25 10:11:35 -04:00
# include "AudioMixerNullDevice.h"
# include "CoreMinimal.h"
# include "HAL/PlatformProcess.h"
2019-09-26 08:12:08 -04:00
# include "HAL/PlatformTime.h"
2021-09-06 12:23:53 -04:00
# include "HAL/Event.h"
# include "AudioMixerLog.h"
# include "Misc/ScopeLock.h"
2018-09-25 10:11:35 -04:00
namespace Audio
{
uint32 FMixerNullCallback : : Run ( )
{
2019-09-26 07:27:04 -04:00
//
// To simulate an audio device requesting for more audio, we sleep between callbacks.
2021-09-06 12:23:53 -04:00
// The problem with this is that OS/Kernel Sleep is not accurate. It will always be slightly higher than requested,
2019-09-26 07:27:04 -04:00
// which means that audio will be generated slightly slower than the stated sample rate.
// To correct this, we keep track of the real time passed, and adjust the sleep time accordingly so the audio clock
// stays as close to the real time clock as possible.
double AudioClock = FPlatformTime : : Seconds ( ) ;
2021-09-06 12:23:53 -04:00
check ( SleepEvent ) ;
2019-09-26 07:27:04 -04:00
float SleepTime = CallbackTime ;
2021-09-06 12:23:53 -04:00
2018-09-25 10:11:35 -04:00
while ( ! bShouldShutdown )
{
2021-09-06 12:23:53 -04:00
// Wait here to be woken up.
if ( WakeupEvent )
{
WakeupEvent - > Wait ( MAX_uint32 ) ;
WakeupEvent - > Reset ( ) ;
2021-10-06 14:34:52 -04:00
UE_CLOG ( ! bShouldShutdown & & ! bShouldRecyle , LogAudioMixer , Display , TEXT ( " FMixerNullCallback: Simulating a h/w device callback at [%dms], ThreadID=%u " ) , ( int32 ) ( CallbackTime * 1000.f ) , CallbackThread - > GetThreadID ( ) ) ;
2021-09-06 12:23:53 -04:00
// Reset our time differential.
AudioClock = FPlatformTime : : Seconds ( ) ;
SleepTime = CallbackTime ;
}
// Simulate a null h/w device as long as we've have been asked to shutdown/recycle
while ( ! bShouldRecyle & & ! bShouldShutdown )
{
SCOPED_NAMED_EVENT ( FMixerNullCallback_Run_Working , FColor : : Blue ) ;
2021-12-17 03:41:59 -05:00
Callback ( ) ;
2021-09-06 12:23:53 -04:00
// Clamp to Maximum of 200ms.
float SleepTimeClampedMs = FMath : : Clamp < float > ( SleepTime * 1000.f , 0.f , 200.f ) ;
// Wait with a timeout of our sleep time. Triggering the event will leave the wait early.
bool bTriggered = SleepEvent - > Wait ( ( int32 ) SleepTimeClampedMs ) ;
SleepEvent - > Reset ( ) ;
2019-09-26 07:27:04 -04:00
2021-12-17 03:41:59 -05:00
AudioClock + = CallbackTime ;
double RealClock = FPlatformTime : : Seconds ( ) ;
double AudioVsReal = RealClock - AudioClock ;
2021-09-06 12:23:53 -04:00
2021-12-17 03:41:59 -05:00
// For the next sleep, we adjust the sleep duration to try and keep the audio clock as close
// to the real time clock as possible
SleepTime = CallbackTime - AudioVsReal ;
2018-09-25 10:11:35 -04:00
2021-09-06 12:23:53 -04:00
# if !NO_LOGGING
// Warn if there's any crazy deltas (limit to every 30s).
if ( RealClock - LastLog > 30.f )
{
if ( FMath : : Abs ( SleepTime ) > 0.2f )
{
2021-10-06 14:34:52 -04:00
UE_LOG ( LogAudioMixer , Warning , TEXT ( " FMixerNullCallback: Large time delta between simulated audio clock and realtime [%dms], ThreadID=%u " ) , ( int32 ) ( SleepTime * 1000.f ) , CallbackThread - > GetThreadID ( ) ) ;
2021-09-06 12:23:53 -04:00
LastLog = RealClock ;
}
}
# endif //!NO_LOGGING
}
}
2018-09-25 10:11:35 -04:00
return 0 ;
}
2021-09-06 12:23:53 -04:00
FMixerNullCallback : : FMixerNullCallback ( float InBufferDuration , TFunction < void ( ) > InCallback , EThreadPriority ThreadPriority , bool bStartPaused )
2018-09-25 10:11:35 -04:00
: Callback ( InCallback )
2021-09-06 12:23:53 -04:00
, CallbackTime ( InBufferDuration )
2018-09-25 10:11:35 -04:00
, bShouldShutdown ( false )
2021-09-06 12:23:53 -04:00
, SleepEvent ( FPlatformProcess : : GetSynchEventFromPool ( true ) )
, WakeupEvent ( FPlatformProcess : : GetSynchEventFromPool ( true ) )
2018-09-25 10:11:35 -04:00
{
2021-09-06 12:23:53 -04:00
check ( SleepEvent ) ;
check ( WakeupEvent ) ;
// Make sure we're in a waitable start on startup.
SleepEvent - > Reset ( ) ;
// If we are marked to pause on startup, make sure the event is in a waitable state.
if ( bStartPaused )
2021-12-17 03:41:59 -05:00
{
2021-09-06 12:23:53 -04:00
WakeupEvent - > Reset ( ) ;
}
else
{
WakeupEvent - > Trigger ( ) ;
}
2019-08-26 18:35:22 -04:00
CallbackThread . Reset ( FRunnableThread : : Create ( this , TEXT ( " AudioMixerNullCallbackThread " ) , 0 , ThreadPriority , FPlatformAffinity : : GetAudioThreadMask ( ) ) ) ;
2018-09-25 10:11:35 -04:00
}
2021-12-17 03:41:59 -05:00
2021-09-06 12:23:53 -04:00
void FMixerNullCallback : : Stop ( )
2018-09-25 10:11:35 -04:00
{
2021-09-06 12:23:53 -04:00
SCOPED_NAMED_EVENT ( FMixerNullCallback_Stop , FColor : : Blue ) ;
// Flag loop to exit
2018-09-25 10:11:35 -04:00
bShouldShutdown = true ;
2021-09-06 12:23:53 -04:00
// If we're waiting for wakeup event, trigger that to bail the loop.
if ( WakeupEvent )
{
WakeupEvent - > Trigger ( ) ;
}
if ( SleepEvent )
{
// Exit any sleep we're inside.
SleepEvent - > Trigger ( ) ;
if ( CallbackThread . IsValid ( ) )
{
// Wait to continue, before deleteing the events.
CallbackThread - > WaitForCompletion ( ) ;
}
FPlatformProcess : : ReturnSynchEventToPool ( SleepEvent ) ;
SleepEvent = nullptr ;
}
if ( WakeupEvent )
{
FPlatformProcess : : ReturnSynchEventToPool ( WakeupEvent ) ;
WakeupEvent = nullptr ;
}
}
void FMixerNullCallback : : Resume ( const TFunction < void ( ) > & InCallback , float InBufferDuration )
{
if ( WakeupEvent )
{
// Copy all the new state and trigger the start event.
// Note we do this without a lock, assuming we're waiting on the wakeup event.
Callback = InCallback ;
CallbackTime = InBufferDuration ;
bShouldRecyle = false ;
FPlatformMisc : : MemoryBarrier ( ) ;
WakeupEvent - > Trigger ( ) ;
}
}
void FMixerNullCallback : : Pause ( )
{
// Flag that we should recycle the thread, causing us to bail the inner loop wait on the start event.
bShouldRecyle = true ;
if ( SleepEvent )
{
// Early out the sleep.
SleepEvent - > Trigger ( ) ;
}
2018-09-25 10:11:35 -04:00
}
}