2019-12-26 14:45:42 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-02-27 11:57:17 -05:00
# include "GameplayMediaEncoder.h"
# include "Engine/GameEngine.h"
# include "HAL/IConsoleManager.h"
# include "Framework/Application/SlateApplication.h"
# include "Modules/ModuleManager.h"
# include "RendererInterface.h"
# include "ScreenRendering.h"
# include "ShaderCore.h"
# include "PipelineStateCache.h"
# include "ProfilingDebugging/CsvProfiler.h"
# include "IbmLiveStreaming.h"
DEFINE_LOG_CATEGORY ( GameplayMediaEncoder ) ;
CSV_DEFINE_CATEGORY ( GameplayMediaEncoder , true ) ;
2021-03-29 20:51:27 -04:00
// right now we support only 48KHz audio sample rate as it's the only config UE seems to output
2019-02-27 11:57:17 -05:00
// WMF AAC encoder supports also 44100Hz so its support can be easily added
const uint32 HardcodedAudioSamplerate = 48000 ;
// for now we downsample to stereo. WMF AAC encoder also supports 6 (5.1) channels
// so it can be added too
const uint32 HardcodedAudioNumChannels = 2 ;
// currently neither IVideoRecordingSystem neither HighlightFeature APIs allow to configure
// audio stream parameters
2019-11-26 17:28:51 -05:00
const uint32 HardcodedAudioBitrate = 192000 ;
2019-02-27 11:57:17 -05:00
// currently neither IVideoRecordingSystem neither HighlightFeature APIs allow to configure
// video stream parameters
# if PLATFORM_WINDOWS
const uint32 HardcodedVideoFPS = 60 ;
# else
const uint32 HardcodedVideoFPS = 30 ;
# endif
const uint32 HardcodedVideoBitrate = 5000000 ;
const uint32 MinVideoBitrate = 1000000 ;
const uint32 MaxVideoBitrate = 20000000 ;
const uint32 MinVideoFPS = 10 ;
const uint32 MaxVideoFPS = 60 ;
const uint32 MaxWidth = 1920 ;
const uint32 MaxHeight = 1080 ;
FAutoConsoleCommand GameplayMediaEncoderInitialize (
TEXT ( " GameplayMediaEncoder.Initialize " ) ,
TEXT ( " Constructs the audio/video encoding objects. Does not start encoding " ) ,
FConsoleCommandDelegate : : CreateStatic ( & FGameplayMediaEncoder : : InitializeCmd )
) ;
FAutoConsoleCommand GameplayMediaEncoderStart (
TEXT ( " GameplayMediaEncoder.Start " ) ,
TEXT ( " Starts encoding " ) ,
FConsoleCommandDelegate : : CreateStatic ( & FGameplayMediaEncoder : : StartCmd )
) ;
FAutoConsoleCommand GameplayMediaEncoderStop (
TEXT ( " GameplayMediaEncoder.Stop " ) ,
TEXT ( " Stops encoding " ) ,
FConsoleCommandDelegate : : CreateStatic ( & FGameplayMediaEncoder : : StopCmd )
) ;
FAutoConsoleCommand GameplayMediaEncoderShutdown (
TEXT ( " GameplayMediaEncoder.Shutdown " ) ,
TEXT ( " Releases all systems. " ) ,
FConsoleCommandDelegate : : CreateStatic ( & FGameplayMediaEncoder : : ShutdownCmd )
) ;
//////////////////////////////////////////////////////////////////////////
//
// FGameplayMediaEncoder
//
//////////////////////////////////////////////////////////////////////////
2019-11-26 17:28:51 -05:00
FGameplayMediaEncoder * FGameplayMediaEncoder : : Singleton = nullptr ;
2019-02-27 11:57:17 -05:00
FGameplayMediaEncoder * FGameplayMediaEncoder : : Get ( )
{
2019-11-26 17:28:51 -05:00
if ( ! Singleton )
{
Singleton = new FGameplayMediaEncoder ( ) ;
}
return Singleton ;
2019-02-27 11:57:17 -05:00
}
FGameplayMediaEncoder : : FGameplayMediaEncoder ( )
{
}
FGameplayMediaEncoder : : ~ FGameplayMediaEncoder ( )
{
2019-11-26 17:28:51 -05:00
Shutdown ( ) ;
2019-02-27 11:57:17 -05:00
}
bool FGameplayMediaEncoder : : RegisterListener ( IGameplayMediaEncoderListener * Listener )
{
check ( IsInGameThread ( ) ) ;
FScopeLock Lock ( & ListenersCS ) ;
if ( Listeners . Num ( ) = = 0 )
{
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " Registering the first listener " ) ) ;
if ( ! Start ( ) )
{
return false ;
}
}
Listeners . AddUnique ( Listener ) ;
return true ;
}
void FGameplayMediaEncoder : : UnregisterListener ( IGameplayMediaEncoderListener * Listener )
{
check ( IsInGameThread ( ) ) ;
2019-04-26 05:52:24 -04:00
ListenersCS . Lock ( ) ;
2019-02-27 11:57:17 -05:00
Listeners . Remove ( Listener ) ;
2019-04-26 05:52:24 -04:00
bool bAnyListenersLeft = Listeners . Num ( ) > 0 ;
ListenersCS . Unlock ( ) ;
if ( bAnyListenersLeft = = false )
2019-02-27 11:57:17 -05:00
{
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " Unregistered the last listener " ) ) ;
Stop ( ) ;
}
}
bool FGameplayMediaEncoder : : Initialize ( )
{
MemoryCheckpoint ( " Initial " ) ;
if ( VideoEncoder )
{
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " Already initialized " ) ) ;
return true ;
}
2019-11-26 17:28:51 -05:00
// If some error occurs, call Shutdown to cleanup
bool bIsOk = false ;
ON_SCOPE_EXIT
2019-02-27 11:57:17 -05:00
{
2019-11-26 17:28:51 -05:00
if ( ! bIsOk )
{
Shutdown ( ) ;
}
2019-02-27 11:57:17 -05:00
} ;
//
// Audio
2019-11-26 17:28:51 -05:00
//
AVEncoder : : FAudioEncoderFactory * AudioEncoderFactory = AVEncoder : : FAudioEncoderFactory : : FindFactory ( " aac " ) ;
if ( ! AudioEncoderFactory )
{
UE_LOG ( GameplayMediaEncoder , Error , TEXT ( " No audio encoder for aac found " ) ) ;
return false ;
}
AudioEncoder = AudioEncoderFactory - > CreateEncoder ( " aac " ) ;
if ( ! AudioEncoder )
{
UE_LOG ( GameplayMediaEncoder , Error , TEXT ( " Could not create audio encoder " ) ) ;
return false ;
}
AVEncoder : : FAudioEncoderConfig AudioConfig ;
AudioConfig . Samplerate = HardcodedAudioSamplerate ;
2019-02-27 11:57:17 -05:00
AudioConfig . NumChannels = HardcodedAudioNumChannels ;
AudioConfig . Bitrate = HardcodedAudioBitrate ;
if ( ! AudioEncoder - > Initialize ( AudioConfig ) )
{
2019-11-26 17:28:51 -05:00
UE_LOG ( GameplayMediaEncoder , Error , TEXT ( " Could not initialize audio encoder " ) ) ;
2019-02-27 11:57:17 -05:00
return false ;
}
2019-11-26 17:28:51 -05:00
AudioEncoder - > RegisterListener ( * this ) ;
2019-02-27 11:57:17 -05:00
MemoryCheckpoint ( " Audio encoder initialized " ) ;
//
// Video
//
2019-11-26 17:28:51 -05:00
AVEncoder : : FVideoEncoderConfig VideoConfig ;
2019-02-27 11:57:17 -05:00
FParse : : Value ( FCommandLine : : Get ( ) , TEXT ( " GameplayMediaEncoder.ResY= " ) , VideoConfig . Height ) ;
if ( VideoConfig . Height = = 0 | | VideoConfig . Height = = 720 )
{
VideoConfig . Width = 1280 ;
VideoConfig . Height = 720 ;
}
else if ( VideoConfig . Height = = 1080 )
{
VideoConfig . Width = 1920 ;
VideoConfig . Height = 1080 ;
}
else
{
UE_LOG ( GameplayMediaEncoder , Fatal , TEXT ( " GameplayMediaEncoder.ResY can only have a value of 720 or 1080 " ) ) ;
return false ;
}
// Specifying 0 will completely disable frame skipping (therefore encoding as many frames as possible)
FParse : : Value ( FCommandLine : : Get ( ) , TEXT ( " GameplayMediaEncoder.FPS= " ) , VideoConfig . Framerate ) ;
if ( VideoConfig . Framerate = = 0 )
{
// Note : When disabling frame skipping, we lie to the encoder when initializing.
// We still specify a framerate, but then feed frames without skipping
VideoConfig . Framerate = HardcodedVideoFPS ;
bDoFrameSkipping = false ;
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " Uncapping FPS " ) ) ;
}
else
{
VideoConfig . Framerate = FMath : : Clamp ( VideoConfig . Framerate , ( uint32 ) MinVideoFPS , ( uint32 ) MaxVideoFPS ) ;
bDoFrameSkipping = true ;
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " Capping FPS %u " ) , VideoConfig . Framerate ) ;
}
VideoConfig . Bitrate = HardcodedVideoBitrate ;
FParse : : Value ( FCommandLine : : Get ( ) , TEXT ( " GameplayMediaEncoder.Bitrate= " ) , VideoConfig . Bitrate ) ;
VideoConfig . Bitrate = FMath : : Clamp ( VideoConfig . Bitrate , ( uint32 ) MinVideoBitrate , ( uint32 ) MaxVideoBitrate ) ;
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " Using a config of {Width=%u, Height=%u, Framerate=%u, Bitrate=%u} " ) , VideoConfig . Width , VideoConfig . Height , VideoConfig . Framerate , VideoConfig . Bitrate ) ;
2019-11-26 17:28:51 -05:00
AVEncoder : : FVideoEncoderFactory * VideoEncoderFactory = AVEncoder : : FVideoEncoderFactory : : FindFactory ( " h264 " ) ;
if ( ! VideoEncoderFactory )
{
UE_LOG ( GameplayMediaEncoder , Error , TEXT ( " No encoder for h264 found " ) ) ;
return false ;
}
VideoEncoder = VideoEncoderFactory - > CreateEncoder ( " h264 " ) ;
if ( ! VideoEncoder )
{
UE_LOG ( GameplayMediaEncoder , Error , TEXT ( " Could not create video encoder " ) ) ;
return false ;
}
2019-02-27 11:57:17 -05:00
if ( ! VideoEncoder - > Initialize ( VideoConfig ) )
{
2019-11-26 17:28:51 -05:00
UE_LOG ( GameplayMediaEncoder , Error , TEXT ( " Could not initialize video encoder " ) ) ;
return false ;
2019-02-27 11:57:17 -05:00
}
2019-11-26 17:28:51 -05:00
VideoEncoder - > RegisterListener ( * this ) ;
2019-02-27 11:57:17 -05:00
MemoryCheckpoint ( " Video encoder initialized " ) ;
2019-11-26 17:28:51 -05:00
bIsOk = true ; // So Shutdown is not called due to the ON_SCOPE_EXIT
2019-02-27 11:57:17 -05:00
return true ;
}
bool FGameplayMediaEncoder : : Start ( )
{
if ( StartTime ! = 0 )
{
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " Already running " ) ) ;
return true ;
}
if ( ! VideoEncoder )
{
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " Not initialized yet , so also performing a Intialize() " ) ) ;
if ( ! Initialize ( ) )
{
return false ;
}
}
2020-08-11 01:36:57 -04:00
StartTime = FTimespan : : FromSeconds ( QueryClock ( ) ) ;
2019-09-26 07:27:04 -04:00
AudioClock = 0 ;
2019-02-27 11:57:17 -05:00
NumCapturedFrames = 0 ;
2019-11-26 17:28:51 -05:00
2019-02-27 11:57:17 -05:00
//
// subscribe to engine delegates for audio output and back buffer
//
2020-01-30 18:48:52 -05:00
FAudioDeviceHandle AudioDevice = GEngine - > GetMainAudioDevice ( ) ;
2020-01-28 05:18:04 -05:00
if ( AudioDevice )
2019-05-24 09:28:17 -04:00
{
2019-11-26 17:28:51 -05:00
bAudioFormatChecked = false ;
2019-06-06 15:04:34 -04:00
AudioDevice - > RegisterSubmixBufferListener ( this ) ;
2019-05-24 09:28:17 -04:00
}
2019-06-06 15:04:34 -04:00
2019-09-05 04:34:58 -04:00
FSlateApplication : : Get ( ) . GetRenderer ( ) - > OnBackBufferReadyToPresent ( ) . AddRaw ( this , & FGameplayMediaEncoder : : OnBackBufferReady ) ;
2019-06-06 15:04:34 -04:00
return true ;
2019-02-27 11:57:17 -05:00
}
void FGameplayMediaEncoder : : Stop ( )
{
check ( IsInGameThread ( ) ) ;
if ( StartTime = = 0 )
{
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " Not running " ) ) ;
return ;
}
if ( UGameEngine * GameEngine = Cast < UGameEngine > ( GEngine ) )
{
2020-01-30 18:48:52 -05:00
FAudioDevice * AudioDevice = GameEngine - > GetMainAudioDeviceRaw ( ) ;
2020-01-28 05:18:04 -05:00
if ( AudioDevice )
2019-02-27 11:57:17 -05:00
{
AudioDevice - > UnregisterSubmixBufferListener ( this ) ;
}
if ( FSlateApplication : : IsInitialized ( ) )
{
2019-03-07 11:25:32 -05:00
FSlateApplication : : Get ( ) . GetRenderer ( ) - > OnBackBufferReadyToPresent ( ) . RemoveAll ( this ) ;
2019-02-27 11:57:17 -05:00
}
}
StartTime = 0 ;
2019-09-26 07:27:04 -04:00
AudioClock = 0 ;
2019-02-27 11:57:17 -05:00
}
void FGameplayMediaEncoder : : Shutdown ( )
{
if ( StartTime ! = 0 )
{
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " Currently running, so also performing a Stop() " ) ) ;
Stop ( ) ;
}
{
FScopeLock Lock ( & AudioProcessingCS ) ;
2019-11-26 17:28:51 -05:00
if ( AudioEncoder )
{
AudioEncoder - > Shutdown ( ) ;
AudioEncoder . Reset ( ) ;
}
2019-02-27 11:57:17 -05:00
}
{
FScopeLock Lock ( & VideoProcessingCS ) ;
2019-11-26 17:28:51 -05:00
if ( VideoEncoder )
2019-02-27 11:57:17 -05:00
{
2019-11-26 17:28:51 -05:00
VideoEncoder - > Shutdown ( ) ;
VideoEncoder . Reset ( ) ;
2019-02-27 11:57:17 -05:00
}
}
}
FTimespan FGameplayMediaEncoder : : GetMediaTimestamp ( ) const
{
2020-08-11 01:36:57 -04:00
return FTimespan : : FromSeconds ( QueryClock ( ) ) - StartTime ;
2019-02-27 11:57:17 -05:00
}
2019-09-26 07:59:32 -04:00
void FGameplayMediaEncoder : : OnNewSubmixBuffer ( const USoundSubmix * OwningSubmix , float * AudioData , int32 NumSamples , int32 NumChannels , const int32 SampleRate , double /*AudioClock*/ )
2019-02-27 11:57:17 -05:00
{
CSV_SCOPED_TIMING_STAT ( GameplayMediaEncoder , OnNewSubmixBuffer ) ;
if ( SampleRate ! = HardcodedAudioSamplerate )
{
// Only report the problem once
if ( ! bAudioFormatChecked )
{
bAudioFormatChecked = true ;
UE_LOG ( GameplayMediaEncoder , Error , TEXT ( " Audio SampleRate needs to be %d HZ, current value is %d. VideoRecordingSystem won't record audio " ) , HardcodedAudioSamplerate , SampleRate ) ;
}
return ;
}
2019-09-26 07:27:04 -04:00
ProcessAudioFrame ( AudioData , NumSamples , NumChannels , SampleRate ) ;
2019-02-27 11:57:17 -05:00
}
2019-03-07 11:25:32 -05:00
void FGameplayMediaEncoder : : OnBackBufferReady ( SWindow & SlateWindow , const FTexture2DRHIRef & BackBuffer )
2019-02-27 11:57:17 -05:00
{
CSV_SCOPED_TIMING_STAT ( GameplayMediaEncoder , OnBackBufferReady ) ;
check ( IsInRenderingThread ( ) ) ;
ProcessVideoFrame ( BackBuffer ) ;
}
2019-11-26 17:28:51 -05:00
void FGameplayMediaEncoder : : ProcessAudioFrame ( const float * AudioData , int32 NumSamples , int32 NumChannels , int32 SampleRate )
2019-02-27 11:57:17 -05:00
{
Audio : : AlignedFloatBuffer InData ;
InData . Append ( AudioData , NumSamples ) ;
Audio : : TSampleBuffer < float > FloatBuffer ( InData , NumChannels , SampleRate ) ;
// Mix to stereo if required, since PixelStreaming only accept stereo at the moment
if ( FloatBuffer . GetNumChannels ( ) ! = HardcodedAudioNumChannels )
{
FloatBuffer . MixBufferToChannels ( HardcodedAudioNumChannels ) ;
}
2019-09-26 07:27:04 -04:00
// Adjust the AudioClock if for some reason it falls behind real time. This can happen if the game spikes, or if we break into the debugger.
FTimespan Now = GetMediaTimestamp ( ) ;
if ( AudioClock < Now . GetTotalSeconds ( ) )
{
UE_LOG ( GameplayMediaEncoder , Warning , TEXT ( " Audio clock falling behind real time clock by %.3f seconds. Ajusting audio clock " ) , Now . GetTotalSeconds ( ) - AudioClock ) ;
// Put it slightly ahead of the real time clock
AudioClock = Now . GetTotalSeconds ( ) + ( FloatBuffer . GetSampleDuration ( ) / 2 ) ;
}
2019-11-26 17:28:51 -05:00
AVEncoder : : FAudioFrame Frame ;
Frame . Timestamp = FTimespan : : FromSeconds ( AudioClock ) ;
Frame . Duration = FTimespan : : FromSeconds ( FloatBuffer . GetSampleDuration ( ) ) ;
FloatBuffer . Clamp ( ) ;
Frame . Data = FloatBuffer ;
AudioEncoder - > Encode ( Frame ) ;
2019-02-27 11:57:17 -05:00
2019-09-26 07:27:04 -04:00
AudioClock + = FloatBuffer . GetSampleDuration ( ) ;
2019-02-27 11:57:17 -05:00
}
2019-11-26 17:28:51 -05:00
void FGameplayMediaEncoder : : ProcessVideoFrame ( const FTexture2DRHIRef & BackBuffer )
2019-02-27 11:57:17 -05:00
{
FScopeLock Lock ( & VideoProcessingCS ) ;
FTimespan Now = GetMediaTimestamp ( ) ;
if ( bDoFrameSkipping )
{
uint64 NumExpectedFrames = static_cast < uint64 > ( Now . GetTotalSeconds ( ) * VideoEncoder - > GetConfig ( ) . Framerate ) ;
UE_LOG ( GameplayMediaEncoder , VeryVerbose , TEXT ( " time %.3f: captured %d, expected %d " ) , Now . GetTotalSeconds ( ) , NumCapturedFrames + 1 , NumExpectedFrames ) ;
if ( NumCapturedFrames + 1 > NumExpectedFrames )
{
UE_LOG ( GameplayMediaEncoder , Verbose , TEXT ( " Framerate control dropped captured frame " ) ) ;
2019-11-26 17:28:51 -05:00
return ;
2019-02-27 11:57:17 -05:00
}
}
if ( ! ChangeVideoConfig ( ) )
{
2019-11-26 17:28:51 -05:00
return ;
2019-02-27 11:57:17 -05:00
}
2019-11-26 17:28:51 -05:00
AVEncoder : : FBufferId BufferId ;
2021-04-08 14:32:07 -04:00
if ( ! VideoEncoder - > CopyTexture ( BackBuffer , Now , Now - LastVideoInputTimestamp , BufferId ,
FIntPoint ( VideoEncoder - > GetConfig ( ) . Width , VideoEncoder - > GetConfig ( ) . Height ) ) )
2019-02-27 11:57:17 -05:00
{
2019-11-26 17:28:51 -05:00
return ;
2019-02-27 11:57:17 -05:00
}
2019-11-26 17:28:51 -05:00
VideoEncoder - > Encode ( BufferId , false , 0 , nullptr ) ;
2019-02-27 11:57:17 -05:00
LastVideoInputTimestamp = Now ;
NumCapturedFrames + + ;
}
void FGameplayMediaEncoder : : SetVideoBitrate ( uint32 Bitrate )
{
NewVideoBitrate = Bitrate ;
bChangeBitrate = true ;
}
void FGameplayMediaEncoder : : SetVideoFramerate ( uint32 Framerate )
{
NewVideoFramerate = FMath : : Clamp ( Framerate , MinVideoFPS , MaxVideoFPS ) ;
bChangeFramerate = true ;
}
2020-08-11 01:36:57 -04:00
void FGameplayMediaEncoder : : SetFramesShouldUseAppTime ( bool bUseAppTime )
{
bShouldFramesUseAppTime = bUseAppTime ;
}
2019-02-27 11:57:17 -05:00
bool FGameplayMediaEncoder : : ChangeVideoConfig ( )
{
if ( bChangeBitrate )
{
if ( ! VideoEncoder - > SetBitrate ( NewVideoBitrate ) )
{
return false ;
}
bChangeBitrate = false ;
}
if ( bChangeFramerate )
{
UE_LOG ( GameplayMediaEncoder , Verbose , TEXT ( " framerate -> %d " ) , NewVideoFramerate . Load ( ) ) ;
if ( ! VideoEncoder - > SetFramerate ( NewVideoFramerate ) )
{
return false ;
}
bChangeFramerate = false ;
NumCapturedFrames = 0 ;
}
return true ;
}
2019-11-26 17:28:51 -05:00
void FGameplayMediaEncoder : : OnEncodedAudioFrame ( const AVEncoder : : FAVPacket & Packet )
{
OnEncodedFrame ( Packet ) ;
}
void FGameplayMediaEncoder : : OnEncodedVideoFrame ( const AVEncoder : : FAVPacket & Packet , AVEncoder : : FEncoderVideoFrameCookie * Cookie )
{
OnEncodedFrame ( Packet ) ;
}
void FGameplayMediaEncoder : : OnEncodedFrame ( const AVEncoder : : FAVPacket & Packet )
{
FScopeLock Lock ( & ListenersCS ) ;
for ( auto & & Listener : Listeners )
{
Listener - > OnMediaSample ( Packet ) ;
}
}
TPair < FString , AVEncoder : : FAudioEncoderConfig > FGameplayMediaEncoder : : GetAudioConfig ( ) const
{
if ( AudioEncoder )
{
return TPair < FString , AVEncoder : : FAudioEncoderConfig > (
AudioEncoder - > GetType ( ) ,
AudioEncoder - > GetConfig ( ) ) ;
}
else
{
return { } ;
}
}
TPair < FString , AVEncoder : : FVideoEncoderConfig > FGameplayMediaEncoder : : GetVideoConfig ( ) const
{
if ( AudioEncoder )
{
return TPair < FString , AVEncoder : : FVideoEncoderConfig > (
VideoEncoder - > GetType ( ) ,
VideoEncoder - > GetConfig ( ) ) ;
}
else
{
return { } ;
}
}