// Copyright Epic Games, Inc. All Rights Reserved. #include "PixelStreaming2PluginSettings.h" #include "IPixelStreaming2Streamer.h" #include "Logging.h" #include "Misc/CommandLine.h" #include "UObject/ReflectedTypeAccessors.h" namespace { template static void CheckConsoleEnum(IConsoleVariable* InConsoleVariable) { FString ConsoleString = InConsoleVariable->GetString(); if (StaticEnum()->GetIndexByNameString(ConsoleString) == INDEX_NONE) { // Legacy CVar values were the enum values but underscores (LOW_LATENCY) instead of the camel case UENUM string (LowLatency). They are still valid we just need to remove the underscores when we check them. if (ConsoleString = ConsoleString.Replace(TEXT("_"), TEXT("")); StaticEnum()->GetIndexByNameString(ConsoleString) != INDEX_NONE) { InConsoleVariable->Set(*ConsoleString, ECVF_SetByConsole); } else { FString ConsoleObjectName = IConsoleManager::Get().FindConsoleObjectName(InConsoleVariable); UE_LOGFMT(LogPixelStreaming2Settings, Warning, "Invalid value {0} received for enum {1} of type {2}", ConsoleString, ConsoleObjectName, StaticEnum()->GetName()); InConsoleVariable->Set(*InConsoleVariable->GetDefaultValue(), ECVF_SetByConsole); } } } static void VerifyCVarVideoSettings(IConsoleVariable* /* We ignore the passed in console variable as this method is called by many different CVars */) { IConsoleVariable* SimulcastCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("PixelStreaming2.Encoder.EnableSimulcast")); IConsoleVariable* CodecCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("PixelStreaming2.Encoder.Codec")); IConsoleVariable* ScalabilityModeCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("PixelStreaming2.Encoder.ScalabilityMode")); // Verify that the video codec and scalability mode strings correctly map to an enum CheckConsoleEnum(CodecCVar); CheckConsoleEnum(ScalabilityModeCVar); if (SimulcastCVar->GetBool()) { // Check that the selected codec supports simulcast FString Codec = CodecCVar->GetString(); if (Codec != TEXT("H264") && Codec != TEXT("VP8")) { UE_LOGFMT(LogPixelStreaming2Settings, Warning, "Selected codec doesn't support simulcast! Resetting default codec to {0}", CodecCVar->GetDefaultValue()); CodecCVar->Set(*CodecCVar->GetDefaultValue(), ECVF_SetByConsole); } } FString Codec = CodecCVar->GetString(); FString ScalabilityMode = ScalabilityModeCVar->GetString(); if ((Codec == TEXT("H264") || Codec == TEXT("VP8")) && (ScalabilityMode != TEXT("L1T1") && ScalabilityMode != TEXT("L1T2") && ScalabilityMode != TEXT("L1T3"))) { UE_LOGFMT(LogPixelStreaming2Settings, Warning, "Selected codec doesn't support the {0} scalability mode! Resetting scalability mode to {1}", ScalabilityMode, ScalabilityModeCVar->GetDefaultValue()); ScalabilityModeCVar->Set(*ScalabilityModeCVar->GetDefaultValue(), ECVF_SetByConsole); } } static void VerifyCVarDefaultStreamerType(IConsoleVariable* CVar) { TArray AvailableFactoryTypes = IPixelStreaming2StreamerFactory::GetAvailableFactoryTypes(); FString SpecifiedFactory = CVar->GetString(); if (AvailableFactoryTypes.Num() == 0) { // This code path executes when the cvar is initially set and no factories have been registered return; } bool bValid = false; for (const FString& AvailableFactory : AvailableFactoryTypes) { if (SpecifiedFactory == AvailableFactory) { bValid = true; break; } } if (!bValid) { UE_LOGFMT(LogPixelStreaming2Settings, Warning, "\"{0}\" isn't a registered streamer type. Valid types: [{1}]. Restoring to \"{2}\"", SpecifiedFactory, FString::Join(AvailableFactoryTypes, TEXT(",")), CVar->GetDefaultValue()); CVar->SetWithCurrentPriority(*CVar->GetDefaultValue()); } } FString ConsoleVariableToCommandArgValue(const FString InCVarName) { // CVars are . deliminated by section. To get their equivilent commandline arg for parsing // we need to remove the . and add a "=" return InCVarName.Replace(TEXT("."), TEXT("")).Replace(TEXT("PixelStreaming2"), TEXT("PixelStreaming")).Append(TEXT("=")); } FString ConsoleVariableToCommandArgParam(const FString InCVarName) { // CVars are . deliminated by section. To get their equivilent commandline arg parameter, we need to to remove the . return InCVarName.Replace(TEXT("."), TEXT("")).Replace(TEXT("PixelStreaming2"), TEXT("PixelStreaming")); } static void ParseLegacyCommandLineValue(const TCHAR* Match, TAutoConsoleVariable& CVar) { FString Value; if (FParse::Value(FCommandLine::Get(), Match, Value)) { CVar->Set(*Value, ECVF_SetByCommandline); } }; static void ParseLegacyCommandLineOption(const TCHAR* Match, TAutoConsoleVariable& CVar) { FString ValueMatch(Match); ValueMatch.Append(TEXT("=")); FString Value; if (FParse::Value(FCommandLine::Get(), *ValueMatch, Value)) { if (Value.Equals(FString(TEXT("true")), ESearchCase::IgnoreCase)) { CVar->Set(true, ECVF_SetByCommandline); } else if (Value.Equals(FString(TEXT("false")), ESearchCase::IgnoreCase)) { CVar->Set(false, ECVF_SetByCommandline); } } else if (FParse::Param(FCommandLine::Get(), Match)) { CVar->Set(true, ECVF_SetByCommandline); } } static FString FindPropertyFromCVar(const TSet> Set, const FString& Key) { for (const TPair& Pair : Set) { if (Pair.Key == Key) { return Pair.Value; } } return ""; } static FString FindCVarFromProperty(const TSet> Set, const FString& Value) { for (const TPair& Pair : Set) { if (Pair.Value == Value) { return Pair.Key; } } return ""; } } // namespace // Map of Property Names to their commandline args as GetMetaData() is not avaliable in packaged projects static const TSet> GetCmdArg = { { "PixelStreaming2.LogStats", "LogStats" }, { "PixelStreaming2.EpicRtcLogFilter", "EpicRtcLogFilter" }, { "PixelStreaming2.SendPlayerIdAsInteger", "SendPlayerIdAsInteger" }, { "PixelStreaming2.DisableLatencyTester", "DisableLatencyTester" }, { "PixelStreaming2.DecoupleFrameRate", "DecoupleFramerate" }, { "PixelStreaming2.DecoupleWaitFactor", "DecoupleWaitFactor" }, { "PixelStreaming2.SignalingReconnectInterval", "SignalingReconnectInterval" }, { "PixelStreaming2.SignalingMaxReconnectAttempts", "SignalingMaxReconnectAttempts" }, { "PixelStreaming2.SignalingKeepAliveInterval", "SignalingKeepAliveInterval" }, { "PixelStreaming2.UseMediaCapture", "UseMediaCapture" }, { "PixelStreaming2.ID", "DefaultStreamerID" }, { "PixelStreaming2.DefaultStreamerType", "DefaultStreamerType" }, { "PixelStreaming2.AutoStartStream", "AutoStartStream" }, { "PixelStreaming2.ConnectionURL", "ConnectionURL" }, { "PixelStreaming2.CaptureUseFence", "CaptureUseFence" }, { "PixelStreaming2.Encoder.Codec", "Codec" }, { "PixelStreaming2.Encoder.TargetBitrate", "EncoderTargetBitrate" }, { "PixelStreaming2.Encoder.MinQuality", "EncoderMinQuality" }, { "PixelStreaming2.Encoder.MaxQuality", "EncoderMaxQuality" }, { "PixelStreaming2.Encoder.ScalabilityMode", "ScalabilityMode" }, { "PixelStreaming2.Encoder.KeyframeInterval", "KeyframeInterval" }, { "PixelStreaming2.Encoder.MaxSessions", "MaxSessions" }, { "PixelStreaming2.Encoder.EnableSimulcast", "EnableSimulcast" }, { "PixelStreaming2.WebRTC.Fps", "WebRTCFps" }, { "PixelStreaming2.WebRTC.StartBitrate", "WebRTCStartBitrate" }, { "PixelStreaming2.WebRTC.MinBitrate", "WebRTCMinBitrate" }, { "PixelStreaming2.WebRTC.MaxBitrate", "WebRTCMaxBitrate" }, { "PixelStreaming2.WebRTC.DisableReceiveAudio", "WebRTCDisableReceiveAudio" }, { "PixelStreaming2.WebRTC.DisableReceiveVideo", "WebRTCDisableReceiveVideo" }, { "PixelStreaming2.WebRTC.DisableTransmitAudio", "WebRTCDisableTransmitAudio" }, { "PixelStreaming2.WebRTC.DisableTransmitVideo", "WebRTCDisableTransmitVideo" }, { "PixelStreaming2.WebRTC.DisableAudioSync", "WebRTCDisableAudioSync" }, { "PixelStreaming2.WebRTC.EnableFlexFec", "WebRTCEnableFlexFec" }, { "PixelStreaming2.WebRTC.DisableStats", "WebRTCDisableStats" }, { "PixelStreaming2.WebRTC.StatsInterval", "WebRTCStatsInterval" }, { "PixelStreaming2.WebRTC.NegotiateCodecs", "WebRTCNegotiateCodecs" }, { "PixelStreaming2.WebRTC.AudioGain", "WebRTCAudioGain" }, { "PixelStreaming2.WebRTC.PortAllocatorFlags", "WebRTCPortAllocatorFlags" }, { "PixelStreaming2.WebRTC.MinPort", "WebRTCMinPort" }, { "PixelStreaming2.WebRTC.MaxPort", "WebRTCMaxPort" }, { "PixelStreaming2.WebRTC.FieldTrials", "WebRTCFieldTrials" }, { "PixelStreaming2.WebRTC.DisableFrameDropper", "WebRTCDisableFrameDropper" }, { "PixelStreaming2.WebRTC.VideoPacing.MaxDelay", "WebRTCVideoPacingMaxDelay" }, { "PixelStreaming2.WebRTC.VideoPacing.Factor", "WebRTCVideoPacingFactor" }, { "PixelStreaming2.Editor.StartOnLaunch", "EditorStartOnLaunch" }, { "PixelStreaming2.Editor.UseRemoteSignallingServer", "EditorUseRemoteSignallingServer" }, { "PixelStreaming2.HMD.Enable", "HMDEnable" }, { "PixelStreaming2.HMD.MatchAspectRatio", "HMDMatchAspectRatio" }, { "PixelStreaming2.HMD.ApplyEyePosition", "HMDAppleEyePosition" }, { "PixelStreaming2.HMD.ApplyEyeRotation", "HMDApplyEyeRotation" }, { "PixelStreaming2.HMD.HFOV", "HMDHFOV" }, { "PixelStreaming2.HMD.VFOV", "HMDVFOV" }, { "PixelStreaming2.HMD.IPD", "HMDIPD" }, { "PixelStreaming2.HMD.ProjectionOffsetX", "HMDProjectionOffsetX" }, { "PixelStreaming2.HMD.ProjectionOffsetY", "HMDProjectionOffsetY" }, { "PixelStreaming2.AllowPixelStreamingCommands", "InputAllowConsoleCommands" }, { "PixelStreaming2.KeyFilter", "InputKeyFilter" }, { "PixelStreaming2.WebRTC.CodecPreferences", "WebRTCCodecPreferences" } }; static const TSet> GetMappedCmdArg = { { "PixelStreaming2.InputController", "InputController" }, { "PixelStreaming2.Encoder.QualityPreset", "QualityPreset" }, { "PixelStreaming2.Encoder.LatencyMode", "LatencyMode" }, { "PixelStreaming2.Encoder.H264Profile", "H264Profile" }, { "PixelStreaming2.Editor.Source", "EditorSource" } }; // Map a legacy cvar to its new property static const TSet> GetLegacyCmdArg = { { "PixelStreaming2.Encoder.MinQp", "EncoderMaxQuality" }, // Renamed to MaxQuality { "PixelStreaming2.Encoder.MaxQp", "EncoderMinQuality" }, // Renamed to MinQuality { "PixelStreaming2.IP", "ConnectionURL" }, // Moved to ConnectionURL { "PixelStreaming2.Port", "ConnectionURL" }, // Moved to ConnectionURL { "PixelStreaming2.URL", "ConnectionURL" }, // Renamed to ConnectionURL { "PixelStreaming2.SignallingURL", "ConnectionURL" }, // Renamed to ConnectionURL { "AllowPixelStreamingCommands", "InputAllowConsoleCommands" }, // Renamed to InputAllowConsoleCommands { "PixelStreaming2.NegotiateCodecs", "WebRTCNegotiateCodecs" }, // Renamed to PixelStreaming2.WebRTC.NegotiateCodecs { "PixelStreaming2.EnableHMD", "HMDEnable" }, // Renamed to PixelStreaming2.HMDEnable { "Editor.PixelStreaming2.StartOnLaunch", "EditorStartOnLaunch" }, // Renamed to PixelStreaming2.Editor.StartOnLaunch { "Editor.PixelStreaming2.UseRemoteSignallingServer", "EditorUseRemoteSignallingServer" }, // Renamed to PixelStreaming2.Editor.UseRemoteSignallingServer { "Editor.PixelStreaming2.Source", "EditorSource" } // Renamed to PixelStreaming2.Editor.Source }; // Begin Pixel Streaming Plugin CVars TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarLogStats( TEXT("PixelStreaming2.LogStats"), false, TEXT("Whether to show PixelStreaming stats in the log (default: false)."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnLogStatsChanged.Broadcast(Var); }), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEpicRtcLogFilter( TEXT("PixelStreaming2.EpicRtcLogFilter"), "", TEXT("Double forward slash (\"//\") separated list of regex patterns to filter from the EpicRtc logs (default: \"\")."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnEpicRtcLogFilterChanged.Broadcast(Var); }), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarDisableLatencyTester( TEXT("PixelStreaming2.DisableLatencyTester"), false, TEXT("If true disables latency tester being triggerable."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarInputController( TEXT("PixelStreaming2.InputController"), TEXT("Any"), TEXT("Various modes of input control supported by Pixel Streaming, currently: \"Any\" or \"Host\". Default: Any"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarDecoupleFramerate( TEXT("PixelStreaming2.DecoupleFramerate"), false, TEXT("Whether we should only stream as fast as we render or at some fixed interval. Coupled means only stream what we render."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnDecoupleFramerateChanged.Broadcast(Var); }), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarDecoupleWaitFactor( TEXT("PixelStreaming2.DecoupleWaitFactor"), 1.25f, TEXT("Frame rate factor to wait for a captured frame when streaming in decoupled mode. Higher factor waits longer but may also result in higher latency."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarSignalingReconnectInterval( TEXT("PixelStreaming2.SignalingReconnectInterval"), 2.0f, TEXT("Changes the number of seconds between attempted reconnects to the signaling server. This is useful for reducing the log spam produced from attempted reconnects. A value <= 0 results in no reconnect. Default: 2.0s"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarSignalingMaxReconnectAttempts( TEXT("PixelStreaming2.SignalingMaxReconnectAttempts"), -1, TEXT("Changes the number of attempts that will be made to reconnect to the signalling server. This is useful for triggering application shutdowns if this value is exceeded. A value of < 0 results in unlimited attempts. Default: -1"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarSignalingKeepAliveInterval( TEXT("PixelStreaming2.SignalingKeepAliveInterval"), 30.0f, TEXT("Changes the number of seconds between pings to the signaling server. This is useful for keeping the connection active. A value <= 0 results in no pings. Default: 30.0"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarUseMediaCapture( TEXT("PixelStreaming2.UseMediaCapture"), true, TEXT("Use Media Capture from MediaIOFramework to capture frames rather than Pixel Streamings internal backbuffer sources."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnUseMediaCaptureChanged.Broadcast(Var); }), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarDefaultStreamerID( TEXT("PixelStreaming2.ID"), TEXT("DefaultStreamer"), TEXT("Default Streamer ID to be used when not specified elsewhere."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarDefaultStreamerType( TEXT("PixelStreaming2.DefaultStreamerType"), TEXT("DefaultRtc"), TEXT("Default Streamer Type to be used when not specified elsewhere."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { VerifyCVarDefaultStreamerType(Var); }), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarAutoStartStream( TEXT("PixelStreaming2.AutoStartStream"), true, TEXT("Configure the PixelStreaming2 plugin to automatically start streaming once loaded (if not in editor). You may wish to set this value to false and manually call StartStreaming at a later point from your c++ code. Default: true"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarConnectionURL( TEXT("PixelStreaming2.ConnectionURL"), TEXT(""), TEXT("Default URL to connect to. This can be a URL to a signalling server or some other endpoint with the format (protocol)://(host):(port)"), ECVF_Default); FAutoConsoleVariableDeprecated UPixelStreaming2PluginSettings::CVarSignallingURL(TEXT("PixelStreaming2.SignallingURL"), TEXT("PixelStreaming2.ConnectionURL"), TEXT("5.6")); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarCaptureUseFence( TEXT("PixelStreaming2.CaptureUseFence"), true, TEXT("Whether the texture copy we do during image capture should use a fence or not (non-fenced is faster but less safe)."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnCaptureUseFenceChanged.Broadcast(Var); }), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarDebugDumpAudio( TEXT("PixelStreaming2.DumpDebugAudio"), false, TEXT("Dumps mixed audio from PS2 to a file on disk for debugging purposes."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnDebugDumpAudioChanged.Broadcast(Var); }), ECVF_Default); // Begin Encoder CVars TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderTargetBitrate( TEXT("PixelStreaming2.Encoder.TargetBitrate"), -1, TEXT("Target bitrate (bps). Ignore the bitrate WebRTC wants (not recommended). Set to -1 to disable. Default -1."), ECVF_RenderThreadSafe); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderMinQuality( TEXT("PixelStreaming2.Encoder.MinQuality"), 0, TEXT("0-100, Higher values result in a better minimum quality but higher average bitrates. Default 0 - i.e. no limit on a minimum Quality."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderMaxQuality( TEXT("PixelStreaming2.Encoder.MaxQuality"), 100, TEXT("0-100, Lower values result in lower average bitrates but reduces maximum achievable quality. Default 100 - i.e. no limit on a maximum Quality."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderQualityPreset( TEXT("PixelStreaming2.Encoder.QualityPreset"), TEXT("Default"), TEXT("PixelStreaming encoder presets that affecting Quality vs Bitrate. Supported modes are: `ULTRA_LOW_QUALITY`, `LOW_QUALITY`, `DEFAULT`, `HIGH_QUALITY` or `LOSSLESS`"), FConsoleVariableDelegate::CreateStatic(&CheckConsoleEnum), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderLatencyMode( TEXT("PixelStreaming2.Encoder.LatencyMode"), TEXT("UltraLowLatency"), TEXT("PixelStreaming encoder mode that affecting Quality vs Latency. Supported modes are: `ULTRA_LOW_LATENCY`, `LOW_LATENCY` or `DEFAULT`"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderKeyframeInterval( TEXT("PixelStreaming2.Encoder.KeyframeInterval"), -1, TEXT("How many frames before a key frame is sent. Default: -1 which disables the sending of periodic key frames. Note: NVENC reqires a reinitialization when this changes."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderMaxSessions( TEXT("PixelStreaming2.Encoder.MaxSessions"), -1, TEXT("-1 implies no limit. Maximum number of concurrent hardware encoder sessions for Pixel Streaming. Note GeForce gpus only support 8 concurrent sessions and will rollover to software encoding when that number is exceeded."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderEnableSimulcast( TEXT("PixelStreaming2.Encoder.EnableSimulcast"), false, TEXT("Enables simulcast. When enabled, the encoder will encode at full resolution, 1/2 resolution and 1/4 resolution simultaneously. Note: Simulcast is only supported with `H264` and `VP8` and you must use the SFU from the infrastructure to fully utilise this functionality."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { VerifyCVarVideoSettings(nullptr); Delegates()->OnSimulcastEnabledChanged.Broadcast(Var); }), ECVF_Default); FString UE::PixelStreaming2::GSelectedCodec = TEXT("H264"); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderCodec( TEXT("PixelStreaming2.Encoder.Codec"), UE::PixelStreaming2::GSelectedCodec, TEXT("PixelStreaming default encoder codec. Supported values are: `H264`, `VP8`, `VP9` or `AV1`"), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { UE::PixelStreaming2::GSelectedCodec = Var->GetString(); VerifyCVarVideoSettings(nullptr); }), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderScalabilityMode( TEXT("PixelStreaming2.Encoder.ScalabilityMode"), TEXT("L1T1"), TEXT("Indicates number of Spatial and temporal layers used, default: L1T1. For a full list of values refer to https://www.w3.org/TR/webrtc-svc/#scalabilitymodes*"), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { VerifyCVarVideoSettings(nullptr); Delegates()->OnScalabilityModeChanged.Broadcast(Var); }), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderH264Profile( TEXT("PixelStreaming2.Encoder.H264Profile"), TEXT("Baseline"), TEXT("PixelStreaming encoder profile. Supported modes are: `AUTO`, `BASELINE`, `MAIN`, `HIGH`, `PROGRESSIVE_HIGH`, `CONSTRAINED_HIGH` or `HIGH444`"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEncoderDebugDumpFrame( TEXT("PixelStreaming2.Encoder.DumpDebugFrames"), false, TEXT("Dumps frames from the encoder to a file on disk for debugging purposes."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnEncoderDebugDumpFrameChanged.Broadcast(Var); }), ECVF_Default); // Begin WebRTC CVars TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCFps( TEXT("PixelStreaming2.WebRTC.Fps"), 60, TEXT("Framerate for WebRTC encoding. Default: 60"), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnWebRTCFpsChanged.Broadcast(Var); }), ECVF_Default); // Note: 1 megabit is the maximum allowed in WebRTC for a start bitrate. TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCStartBitrate( TEXT("PixelStreaming2.WebRTC.StartBitrate"), 1000000, TEXT("Start bitrate (bps) that WebRTC will try begin the stream with. Must be between Min/Max bitrates. Default: 1000000"), ECVF_RenderThreadSafe); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCMinBitrate( TEXT("PixelStreaming2.WebRTC.MinBitrate"), 100000, TEXT("Min bitrate (bps) that WebRTC will not request below. Careful not to set too high otherwise WebRTC will just drop frames. Default: 100000"), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnWebRTCBitrateChanged.Broadcast(Var); }), ECVF_RenderThreadSafe); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCMaxBitrate( TEXT("PixelStreaming2.WebRTC.MaxBitrate"), 40000000, TEXT("Max bitrate (bps) that WebRTC will not request above. Default: 40000000 aka 40 megabits/per second."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnWebRTCBitrateChanged.Broadcast(Var); }), ECVF_RenderThreadSafe); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCDisableReceiveAudio( TEXT("PixelStreaming2.WebRTC.DisableReceiveAudio"), false, TEXT("Disables receiving audio from the browser into UE."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCDisableReceiveVideo( TEXT("PixelStreaming2.WebRTC.DisableReceiveVideo"), true, TEXT("Disables receiving video from the browser into UE."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCDisableTransmitAudio( TEXT("PixelStreaming2.WebRTC.DisableTransmitAudio"), false, TEXT("Disables transmission of UE audio to the browser."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCDisableTransmitVideo( TEXT("PixelStreaming2.WebRTC.DisableTransmitVideo"), false, TEXT("Disables transmission of UE video to the browser."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCDisableAudioSync( TEXT("PixelStreaming2.WebRTC.DisableAudioSync"), true, TEXT("Disables the synchronization of audio and video tracks in WebRTC. This can be useful in low latency usecases where synchronization is not required."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCEnableFlexFec( TEXT("PixelStreaming2.WebRTC.EnableFlexFec"), false, TEXT("Signals support for Flexible Forward Error Correction to WebRTC. This can cause a reduction in quality if total bitrate is low."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCDisableStats( TEXT("PixelStreaming2.WebRTC.DisableStats"), false, TEXT("Disables the collection of WebRTC stats."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnWebRTCDisableStatsChanged.Broadcast(Var); }), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCStatsInterval( TEXT("PixelStreaming2.WebRTC.StatsInterval"), 1.f, TEXT("Configures how often WebRTC stats are collected in seconds. Values less than 0.0f disable stats collection. Default: 1.0f"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCNegotiateCodecs( TEXT("PixelStreaming2.WebRTC.NegotiateCodecs"), false, TEXT("Whether PS should send all its codecs during sdp handshake so peers can negotiate or just send a single selected codec."), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCCodecPreferences( TEXT("PixelStreaming2.WebRTC.CodecPreferences"), TEXT("AV1,H264,VP9,VP8"), TEXT("A comma separated list of video codecs specifying the prefered order PS will signal during sdp handshake"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCAudioGain( TEXT("PixelStreaming2.WebRTC.AudioGain"), 1.0f, TEXT("Sets the amount of gain to apply to audio. Default: 1.0"), ECVF_Default); // End WebRTC CVars // Begin EditorStreaming CVars TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEditorStartOnLaunch( TEXT("PixelStreaming2.Editor.StartOnLaunch"), false, TEXT("Start Editor Streaming as soon as the Unreal Editor is launched. Default: false"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEditorUseRemoteSignallingServer( TEXT("PixelStreaming2.Editor.UseRemoteSignallingServer"), false, TEXT("Enables the use of a remote signalling server. Default: false"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarEditorSource( TEXT("PixelStreaming2.Editor.Source"), TEXT("Editor"), TEXT("Editor PixelStreaming source. Supported values are `Editor`, `LevelEditorViewport`. Default: `Editor`"), FConsoleVariableDelegate::CreateStatic(&CheckConsoleEnum), ECVF_Default); // End EditorStreaming CVars // Begin HMD CVars TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarHMDEnable( TEXT("PixelStreaming2.HMD.Enable"), false, TEXT("Enables HMD specific functionality for Pixel Streaming. Namely input handling and stereoscopic rendering. Default: false"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarHMDMatchAspectRatio( TEXT("PixelStreaming2.HMD.MatchAspectRatio"), true, TEXT("If true automatically resize the rendering resolution to match the aspect ratio determined by the HFoV and VFoV. Default: true"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarHMDApplyEyePosition( TEXT("PixelStreaming2.HMD.ApplyEyePosition"), true, TEXT("If true automatically position each eye's rendering by whatever amount WebXR reports for each left-right XRView. If false do no eye positioning. Default: true"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarHMDApplyEyeRotation( TEXT("PixelStreaming2.HMD.ApplyEyeRotation"), true, TEXT("If true automatically rotate each eye's rendering by whatever amount WebXR reports for each left-right XRView. If false do no eye rotation. Default: true"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarHMDHFOV( TEXT("PixelStreaming2.HMD.HFOV"), -1.0f, TEXT("Overrides the horizontal field of view for HMD rendering, values are in degrees and values less than 0.0f disable the override. Default: -1.0f"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarHMDVFOV( TEXT("PixelStreaming2.HMD.VFOV"), -1.0f, TEXT("Overrides the vertical field of view for HMD rendering, values are in degrees and values less than 0.0f disable the override. Default: -1.0f"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarHMDIPD( TEXT("PixelStreaming2.HMD.IPD"), -1.0f, TEXT("Overrides the HMD IPD (interpupillary distance), values are in centimeters and values less than 0.0f disable the override. Default: -1.0f"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarHMDProjectionOffsetX( TEXT("PixelStreaming2.HMD.ProjectionOffsetX"), -1.0f, TEXT("Overrides the left/right eye projection matrix x-offset, values are in clip space and values less than 0.0f disable the override. Default: -1.0f"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarHMDProjectionOffsetY( TEXT("PixelStreaming2.HMD.ProjectionOffsetY"), -1.0f, TEXT("Overrides the left-right eye projection matrix y-offset, values are in clip space and values less than 0.0f disable the override. Default: -1.0f"), ECVF_Default); // End HMD CVars // Begin Input CVars TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarInputAllowConsoleCommands( TEXT("PixelStreaming2.AllowPixelStreamingCommands"), false, TEXT("If true browser can send consoleCommand payloads that execute in UE's console. Default: false"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarInputKeyFilter( TEXT("PixelStreaming2.KeyFilter"), "", TEXT("Comma separated list of keys to ignore from streaming clients. Default: \"\""), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* Var) { Delegates()->OnInputKeyFilterChanged.Broadcast(Var); }), ECVF_Default); // End Input CVars TArray UPixelStreaming2PluginSettings::GetCodecPreferences() { TArray OutCodecPreferences; FString StringOptions = UPixelStreaming2PluginSettings::CVarWebRTCCodecPreferences.GetValueOnAnyThread(); if (StringOptions.IsEmpty()) { return OutCodecPreferences; } TArray CodecArray; StringOptions.ParseIntoArray(CodecArray, TEXT(","), true); for (const FString& CodecString : CodecArray) { uint64 EnumIndex = StaticEnum()->GetIndexByNameString(CodecString); checkf(EnumIndex != INDEX_NONE, TEXT("CVar was not containing valid enum string")); OutCodecPreferences.Add(static_cast(StaticEnum()->GetValueByIndex(EnumIndex))); } return OutCodecPreferences; } EPortAllocatorFlags UPixelStreaming2PluginSettings::GetPortAllocationFlags() { EPortAllocatorFlags OutPortAllocatorFlags = EPortAllocatorFlags::None; FString StringOptions = UPixelStreaming2PluginSettings::CVarWebRTCPortAllocatorFlags.GetValueOnAnyThread(); if (StringOptions.IsEmpty()) { return OutPortAllocatorFlags; } TArray FlagArray; StringOptions.ParseIntoArray(FlagArray, TEXT(","), true); int OptionCount = FlagArray.Num(); while (OptionCount > 0) { FString Flag = FlagArray[OptionCount - 1]; // Flags must match EpicRtc\Include\epic_rtc\core\connection_config.h if (Flag == "DISABLE_UDP") { OutPortAllocatorFlags |= EPortAllocatorFlags::DisableUdp; } else if (Flag == "DISABLE_STUN") { OutPortAllocatorFlags |= EPortAllocatorFlags::DisableStun; } else if (Flag == "DISABLE_RELAY") { OutPortAllocatorFlags |= EPortAllocatorFlags::DisableRelay; } else if (Flag == "DISABLE_TCP") { OutPortAllocatorFlags |= EPortAllocatorFlags::DisableTcp; } else if (Flag == "ENABLE_IPV6") { OutPortAllocatorFlags |= EPortAllocatorFlags::EnableIPV6; } else if (Flag == "ENABLE_SHARED_SOCKET") { OutPortAllocatorFlags |= EPortAllocatorFlags::EnableSharedSocket; } else if (Flag == "ENABLE_STUN_RETRANSMIT_ATTRIBUTE") { OutPortAllocatorFlags |= EPortAllocatorFlags::EnableStunRetransmitAttribute; } else if (Flag == "DISABLE_ADAPTER_ENUMERATION") { OutPortAllocatorFlags |= EPortAllocatorFlags::DisableAdapterEnumeration; } else if (Flag == "DISABLE_DEFAULT_LOCAL_CANDIDATE") { OutPortAllocatorFlags |= EPortAllocatorFlags::DisableDefaultLocalCandidate; } else if (Flag == "DISABLE_UDP_RELAY") { OutPortAllocatorFlags |= EPortAllocatorFlags::DisableUdpRelay; } else if (Flag == "DISABLE_COSTLY_NETWORKS") { OutPortAllocatorFlags |= EPortAllocatorFlags::DisableCostlyNetworks; } else if (Flag == "ENABLE_IPV6_ON_WIFI") { OutPortAllocatorFlags |= EPortAllocatorFlags::EnableIPV6OnWifi; } else if (Flag == "ENABLE_ANY_ADDRESS_PORTS") { OutPortAllocatorFlags |= EPortAllocatorFlags::EnableAnyAddressPort; } else if (Flag == "DISABLE_LINK_LOCAL_NETWORKS") { OutPortAllocatorFlags |= EPortAllocatorFlags::DisableLinkLocalNetworks; } else { UE_LOGFMT(LogPixelStreaming2Settings, Warning, "Unknown port allocator flag: {0}", Flag); } OptionCount--; } return OutPortAllocatorFlags; } void SetPortAllocationCVarFromProperty(UObject* This, FProperty* Property) { const FNumericProperty* EnumProperty = CastField(Property); void* PropertyAddress = EnumProperty->ContainerPtrToValuePtr(This); EPortAllocatorFlags CurrentValue = static_cast(EnumProperty->GetSignedIntPropertyValue(PropertyAddress)); FString CVarString = TEXT(""); if (static_cast(CurrentValue & EPortAllocatorFlags::DisableUdp)) { CVarString += "DISABLE_UDP,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::DisableStun)) { CVarString += "DISABLE_STUN,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::DisableRelay)) { CVarString += "DISABLE_RELAY,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::DisableTcp)) { CVarString += "DISABLE_TCP,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::EnableIPV6)) { CVarString += "ENABLE_IPV6,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::EnableSharedSocket)) { CVarString += "ENABLE_SHARED_SOCKET,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::EnableStunRetransmitAttribute)) { CVarString += "ENABLE_STUN_RETRANSMIT_ATTRIBUTE,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::DisableAdapterEnumeration)) { CVarString += "DISABLE_ADAPTER_ENUMERATION,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::DisableDefaultLocalCandidate)) { CVarString += "DISABLE_DEFAULT_LOCAL_CANDIDATE,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::DisableUdpRelay)) { CVarString += "DISABLE_UDP_RELAY,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::DisableCostlyNetworks)) { CVarString += "DISABLE_COSTLY_NETWORKS,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::EnableIPV6OnWifi)) { CVarString += "ENABLE_IPV6_ON_WIFI,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::EnableAnyAddressPort)) { CVarString += "ENABLE_ANY_ADDRESS_PORTS,"; } if (static_cast(CurrentValue & EPortAllocatorFlags::DisableLinkLocalNetworks)) { CVarString += "DISABLE_LINK_LOCAL_NETWORKS,"; } UPixelStreaming2PluginSettings::CVarWebRTCPortAllocatorFlags.AsVariable()->Set(*CVarString, ECVF_SetByProjectSetting); } void SetPortAllocationCVarAndPropertyFromValue(UObject* This, FProperty* Property, const FString& Value) { UPixelStreaming2PluginSettings::CVarWebRTCPortAllocatorFlags.AsVariable()->Set(*Value, ECVF_SetByCommandline); const FNumericProperty* EnumProperty = CastField(Property); int64* PropertyAddress = EnumProperty->ContainerPtrToValuePtr(This); *PropertyAddress = static_cast(UPixelStreaming2PluginSettings::GetPortAllocationFlags()); } TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCPortAllocatorFlags( TEXT("PixelStreaming2.WebRTC.PortAllocatorFlags"), TEXT(""), TEXT("Sets the WebRTC port allocator flags. Format:\"DISABLE_UDP,DISABLE_STUN,...\""), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCMinPort( TEXT("PixelStreaming2.WebRTC.MinPort"), 49152, // Default according to RFC5766 TEXT("Sets the minimum usable port for the WebRTC port allocator. Default: 49152"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCMaxPort( TEXT("PixelStreaming2.WebRTC.MaxPort"), 65535, // Default according to RFC5766 TEXT("Sets the maximum usable port for the WebRTC port allocator. Default: 65535"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCFieldTrials( TEXT("PixelStreaming2.WebRTC.FieldTrials"), TEXT(""), TEXT("Sets the WebRTC field trials string. Format:\"TRIAL1/VALUE1/TRIAL2/VALUE2/\""), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCDisableFrameDropper( TEXT("PixelStreaming2.WebRTC.DisableFrameDropper"), false, TEXT("Disables the WebRTC internal frame dropper using the field trial WebRTC-FrameDropper/Disabled/"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCVideoPacingMaxDelay( TEXT("PixelStreaming2.WebRTC.VideoPacing.MaxDelay"), -1.0f, TEXT("Enables the WebRTC-Video-Pacing field trial and sets the max delay (ms) parameter. Default: -1.0f (values below zero are discarded.)"), ECVF_Default); TAutoConsoleVariable UPixelStreaming2PluginSettings::CVarWebRTCVideoPacingFactor( TEXT("PixelStreaming2.WebRTC.VideoPacing.Factor"), -1.0f, TEXT("Enables the WebRTC-Video-Pacing field trial and sets the video pacing factor parameter. Larger values are more lenient on larger bitrates. Default: -1.0f (values below zero are discarded.)"), ECVF_Default); UPixelStreaming2PluginSettings::FDelegates* UPixelStreaming2PluginSettings::DelegateSingleton = nullptr; UPixelStreaming2PluginSettings::~UPixelStreaming2PluginSettings() { DelegateSingleton = nullptr; } FName UPixelStreaming2PluginSettings::GetCategoryName() const { return TEXT("Plugins"); } #if WITH_EDITOR FText UPixelStreaming2PluginSettings::GetSectionText() const { return NSLOCTEXT("PixelStreaming2Plugin", "PixelStreaming2SettingsSection", "PixelStreaming2"); } void UPixelStreaming2PluginSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); FString PropertyName = PropertyChangedEvent.Property->GetNameCPP(); FString CVarName; if (CVarName = FindCVarFromProperty(GetCmdArg, PropertyName); !CVarName.IsEmpty()) { if (PropertyName == "WebRTCPortAllocatorFlags") { SetPortAllocationCVarFromProperty(this, PropertyChangedEvent.Property); } else if (PropertyName == "Codec" || PropertyName == "ScalabilityMode" || PropertyName == "EnableSimulcast") { VerifyVideoSettings(); } else { SetCVarFromProperty(CVarName, PropertyChangedEvent.Property); } } else if (CVarName = FindCVarFromProperty(GetMappedCmdArg, PropertyName); !CVarName.IsEmpty()) { SetCVarFromProperty(CVarName, PropertyChangedEvent.Property); } } void UPixelStreaming2PluginSettings::VerifyVideoSettings() { FProperty* SimulcastProperty = GetClass()->FindPropertyByName(TEXT("EnableSimulcast")); FBoolProperty* SimulcastBoolProperty = CastField(SimulcastProperty); bool bSimulcastEnabled = SimulcastBoolProperty->GetPropertyValue_InContainer(this); FProperty* CodecProperty = GetClass()->FindPropertyByName(TEXT("Codec")); FStrProperty* CodecStrProperty = CastField(CodecProperty); FString CodecString = CodecStrProperty->GetPropertyValue_InContainer(this); FProperty* ScalabilityModeProperty = GetClass()->FindPropertyByName(TEXT("ScalabilityMode")); FStrProperty* ScalabilityModeStrProperty = CastField(ScalabilityModeProperty); FString ScalabilityModeString = ScalabilityModeStrProperty->GetPropertyValue_InContainer(this); if (bSimulcastEnabled) { if (CodecString != TEXT("H264") && CodecString != TEXT("VP8")) { UE_LOGFMT(LogPixelStreaming2Settings, Warning, "Default codec ({0}) doesn't support simulcast! Resetting default codec to H.264", CodecString); CodecStrProperty->SetPropertyValue_InContainer(this, TEXT("H264")); } } CodecString = CodecStrProperty->GetPropertyValue_InContainer(this); if ((CodecString == TEXT("H264") || CodecString == TEXT("VP8")) && (ScalabilityModeString != TEXT("L1T1") && ScalabilityModeString != TEXT("L1T2") && ScalabilityModeString != TEXT("L1T3"))) { UE_LOGFMT(LogPixelStreaming2Settings, Warning, "Default codec ({0}) doesn't support the {1} scalability mode! Resetting scalability mode to L1T1", CodecString, ScalabilityModeString); ScalabilityModeStrProperty->SetPropertyValue_InContainer(this, TEXT("L1T1")); } SetCVarFromProperty(FindCVarFromProperty(GetCmdArg, SimulcastProperty->GetNameCPP()), SimulcastProperty); SetCVarFromProperty(FindCVarFromProperty(GetCmdArg, CodecProperty->GetNameCPP()), CodecProperty); SetCVarFromProperty(FindCVarFromProperty(GetCmdArg, ScalabilityModeProperty->GetNameCPP()), ScalabilityModeProperty); } #endif void UPixelStreaming2PluginSettings::SetCVarAndPropertyFromValue(const FString& CVarName, FProperty* Property, const FString& Value) { IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(*CVarName); if (!CVar) { UE_LOGFMT(LogPixelStreaming2Settings, Warning, "Failed to find CVar: {0}", CVarName); return; } if (FByteProperty* ByteProperty = CastField(Property); ByteProperty != NULL && ByteProperty->Enum != NULL) { CVar->Set(FCString::Atoi(*Value), ECVF_SetByCommandline); ByteProperty->SetPropertyValue_InContainer(this, FCString::Atoi(*Value)); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] and Property [{1}] to [{2}] from command line", CVarName, Property->GetNameCPP(), FCString::Atoi(*Value)); } else if (FEnumProperty* EnumProperty = CastField(Property)) { int64 EnumIndex = EnumProperty->GetEnum()->GetIndexByNameString(Value.Replace(TEXT("_"), TEXT(""))); if (EnumIndex != INDEX_NONE) { CVar->Set(*EnumProperty->GetEnum()->GetNameStringByIndex(EnumIndex), ECVF_SetByCommandline); FNumericProperty* UnderlyingProp = EnumProperty->GetUnderlyingProperty(); int64* PropertyAddress = EnumProperty->ContainerPtrToValuePtr(this); *PropertyAddress = EnumProperty->GetEnum()->GetValueByIndex(EnumIndex); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] and Property [{1}] to [{2}] from command line", CVarName, Property->GetNameCPP(), EnumProperty->GetEnum()->GetNameStringByIndex(EnumIndex)); } else { UE_LOGFMT(LogPixelStreaming2Settings, Warning, "{0} is not a valid enum value for {1}", Value, EnumProperty->GetEnum()->CppType); } } else if (FBoolProperty* BoolProperty = CastField(Property)) { bool bValue = false; if (Value.Equals(FString(TEXT("true")), ESearchCase::IgnoreCase)) { bValue = true; } else if (Value.Equals(FString(TEXT("false")), ESearchCase::IgnoreCase)) { bValue = false; } CVar->Set(bValue, ECVF_SetByCommandline); BoolProperty->SetPropertyValue_InContainer(this, bValue); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] and Property [{1}] to [{2}] from command line", CVarName, Property->GetNameCPP(), bValue); } else if (FIntProperty* IntProperty = CastField(Property)) { CVar->Set(FCString::Atoi(*Value), ECVF_SetByCommandline); IntProperty->SetPropertyValue_InContainer(this, FCString::Atoi(*Value)); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] and Property [{1}] to [{2}] from command line", CVarName, Property->GetNameCPP(), FCString::Atoi(*Value)); } else if (FFloatProperty* FloatProperty = CastField(Property)) { CVar->Set(FCString::Atof(*Value), ECVF_SetByCommandline); FloatProperty->SetPropertyValue_InContainer(this, FCString::Atof(*Value)); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] and Property [{1}] to [{2}] from command line", CVarName, Property->GetNameCPP(), FCString::Atof(*Value)); } else if (FStrProperty* StringProperty = CastField(Property)) { CVar->Set(*Value, ECVF_SetByCommandline); StringProperty->SetPropertyValue_InContainer(this, *Value); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] and Property [{1}] to [\"{2}\"] from command line", CVarName, Property->GetNameCPP(), Value); } else if (FNameProperty* NameProperty = CastField(Property)) { CVar->Set(*Value, ECVF_SetByCommandline); NameProperty->SetPropertyValue_InContainer(this, FName(*Value)); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] and Property [{1}] to [\"{2}\"] from command line", CVarName, Property->GetNameCPP(), Value); } else if (FArrayProperty* ArrayProperty = CastField(Property)) { // TODO (william.belcher): Only FString array properties are currently supported CVar->Set(*Value, ECVF_SetByCommandline); TArray StringArray; Value.ParseIntoArray(StringArray, TEXT(","), true); TArray& Array = *ArrayProperty->ContainerPtrToValuePtr>(this); Array = StringArray; UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] and Property [{1}] to [\"{2}\"] from command line", CVarName, Property->GetNameCPP(), Value); } } void UPixelStreaming2PluginSettings::SetCVarFromProperty(const FString& CVarName, FProperty* Property) { IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(*CVarName); if (!CVar) { UE_LOGFMT(LogPixelStreaming2Settings, Warning, "Failed to find CVar: {0}", CVarName); return; } if (FByteProperty* ByteProperty = CastField(Property); ByteProperty != NULL && ByteProperty->Enum != NULL) { CVar->Set(ByteProperty->GetPropertyValue_InContainer(this), ECVF_SetByProjectSetting); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] to [{1}] from Property [{2}]", CVarName, ByteProperty->GetPropertyValue_InContainer(this), Property->GetNameCPP()); } else if (FEnumProperty* EnumProperty = CastField(Property)) { void* PropertyAddress = EnumProperty->ContainerPtrToValuePtr(this); int64 CurrentValue = EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(PropertyAddress); CVar->Set(*EnumProperty->GetEnum()->GetNameStringByValue(CurrentValue), ECVF_SetByProjectSetting); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] to [{1}] from Property [{2}]", CVarName, EnumProperty->GetEnum()->GetNameStringByValue(CurrentValue), Property->GetNameCPP()); } else if (FBoolProperty* BoolProperty = CastField(Property)) { CVar->Set(BoolProperty->GetPropertyValue_InContainer(this), ECVF_SetByProjectSetting); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] to [{1}] from Property [{2}]", CVarName, BoolProperty->GetPropertyValue_InContainer(this), Property->GetNameCPP()); } else if (FIntProperty* IntProperty = CastField(Property)) { CVar->Set(IntProperty->GetPropertyValue_InContainer(this), ECVF_SetByProjectSetting); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] to [{1}] from Property [{2}]", CVarName, IntProperty->GetPropertyValue_InContainer(this), Property->GetNameCPP()); } else if (FFloatProperty* FloatProperty = CastField(Property)) { CVar->Set(FloatProperty->GetPropertyValue_InContainer(this), ECVF_SetByProjectSetting); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] to [{1}] from Property [{2}]", CVarName, FloatProperty->GetPropertyValue_InContainer(this), Property->GetNameCPP()); } else if (FStrProperty* StringProperty = CastField(Property)) { CVar->Set(*StringProperty->GetPropertyValue_InContainer(this), ECVF_SetByProjectSetting); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] to [\"{1}\"] from Property [{2}]", CVarName, StringProperty->GetPropertyValue_InContainer(this), Property->GetNameCPP()); } else if (FNameProperty* NameProperty = CastField(Property)) { CVar->Set(*NameProperty->GetPropertyValue_InContainer(this).ToString(), ECVF_SetByProjectSetting); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] to [\"{1}\"] from Property [{2}]", CVarName, NameProperty->GetPropertyValue_InContainer(this), Property->GetNameCPP()); } else if (FArrayProperty* ArrayProperty = CastField(Property)) { // TODO (william.belcher): Only FString array properties are currently supported TArray Array = *ArrayProperty->ContainerPtrToValuePtr>(this); CVar->Set(*FString::Join(Array, TEXT(",")), ECVF_SetByProjectSetting); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Setting CVar [{0}] to [\"{1}\"] from Property [{2}]", CVarName, FString::Join(Array, TEXT(",")), Property->GetNameCPP()); } } void UPixelStreaming2PluginSettings::InitializeCVarsFromProperties() { UE_LOGFMT(LogPixelStreaming2Settings, Log, "Initializing CVars from ini"); for (FProperty* Property = GetClass()->PropertyLink; Property; Property = Property->PropertyLinkNext) { if (!Property->HasAnyPropertyFlags(CPF_Config)) { continue; } // Handle the majority of commandline argument if (Property->GetNameCPP() == "WebRTCPortAllocatorFlags") { SetPortAllocationCVarFromProperty(this, Property); continue; } FString CVarName; if (CVarName = FindCVarFromProperty(GetCmdArg, Property->GetNameCPP()); !CVarName.IsEmpty()) { SetCVarFromProperty(CVarName, Property); continue; } else if (CVarName = FindCVarFromProperty(GetMappedCmdArg, Property->GetNameCPP()); !CVarName.IsEmpty()) { SetCVarFromProperty(CVarName, Property); continue; } } } void UPixelStreaming2PluginSettings::ValidateCommandLineArgs() { FString CommandLine = FCommandLine::Get(); TArray CommandArray; CommandLine.ParseIntoArray(CommandArray, TEXT(" "), true); for (FString Command : CommandArray) { Command.RemoveFromStart(TEXT("-")); if (!Command.StartsWith("PixelStreaming")) { continue; } // Get the pure command line arg from an arg that contains an '=', eg PixelStreamingURL= FString CurrentCommandLineArg = Command; if (Command.Contains("=")) { Command.Split(TEXT("="), &CurrentCommandLineArg, nullptr); } bool bValidArg = false; for (const TPair& Pair : GetCmdArg) { FString ValidCommandLineArg = ConsoleVariableToCommandArgParam(Pair.Key); if (CurrentCommandLineArg == ValidCommandLineArg) { bValidArg = true; break; } } if (!bValidArg) { for (const TPair& Pair : GetMappedCmdArg) { FString ValidCommandLineArg = ConsoleVariableToCommandArgParam(Pair.Key); if (CurrentCommandLineArg == ValidCommandLineArg) { bValidArg = true; break; } } } if (!bValidArg) { for (const TPair& Pair : GetLegacyCmdArg) { FString ValidCommandLineArg = ConsoleVariableToCommandArgParam(Pair.Key); if (CurrentCommandLineArg == ValidCommandLineArg) { bValidArg = true; break; } } } if (!bValidArg) { UE_LOGFMT(LogPixelStreaming2Settings, Warning, "Unknown PixelStreaming command line arg: {0}", CurrentCommandLineArg); } } } void UPixelStreaming2PluginSettings::ParseCommandlineArgs() { UE_LOGFMT(LogPixelStreaming2Settings, Verbose, "Updating CVars and properties with command line args"); for (const TPair& Pair : GetCmdArg) { FString CVarString = Pair.Key; FString PropertyName = Pair.Value; FProperty* Property = GetClass()->FindPropertyByName(FName(*PropertyName)); if (!Property || !Property->HasAnyPropertyFlags(CPF_Config)) { continue; } if (PropertyName == "WebRTCPortAllocatorFlags") { FString ConsoleString; if (FParse::Value(FCommandLine::Get(), *ConsoleVariableToCommandArgValue(CVarString), ConsoleString)) { SetPortAllocationCVarAndPropertyFromValue(this, Property, ConsoleString); } continue; } // Handle a directly parsable commandline FString ConsoleString; if (FParse::Value(FCommandLine::Get(), *ConsoleVariableToCommandArgValue(CVarString), ConsoleString)) { SetCVarAndPropertyFromValue(CVarString, Property, ConsoleString); } else if (FParse::Param(FCommandLine::Get(), *ConsoleVariableToCommandArgParam(CVarString))) { SetCVarAndPropertyFromValue(CVarString, Property, TEXT("true")); } } for (const TPair& Pair : GetMappedCmdArg) { FString CVarString = Pair.Key; FString PropertyName = Pair.Value; FProperty* Property = GetClass()->FindPropertyByName(FName(*PropertyName)); if (!Property || !Property->HasAnyPropertyFlags(CPF_Config)) { continue; } // Handle a directly parsable commandline FString ConsoleString; if (FParse::Value(FCommandLine::Get(), *ConsoleVariableToCommandArgValue(CVarString), ConsoleString)) { SetCVarAndPropertyFromValue(CVarString, Property, ConsoleString); } } } void UPixelStreaming2PluginSettings::ParseLegacyCommandlineArgs() { FString SignallingServerIP; FString SignallingServerPort; for (const TPair& Pair : GetLegacyCmdArg) { FString LegacyCVarString = Pair.Key; FString PropertyName = Pair.Value; FProperty* Property = GetClass()->FindPropertyByName(FName(*PropertyName)); if (!Property || !Property->HasAnyPropertyFlags(CPF_Config)) { continue; } FString NewCVarString; if (FString CmdArgCVar = FindCVarFromProperty(GetCmdArg, PropertyName); !CmdArgCVar.IsEmpty()) { NewCVarString = CmdArgCVar; } else if (FString MappedCmdArgCVar = FindCVarFromProperty(GetMappedCmdArg, PropertyName); !MappedCmdArgCVar.IsEmpty()) { NewCVarString = MappedCmdArgCVar; } else { continue; } if (LegacyCVarString == "PixelStreaming2.Encoder.MinQp") { int32 MinQP; if (FParse::Value(FCommandLine::Get(), *ConsoleVariableToCommandArgValue(LegacyCVarString), MinQP)) { int32 ScaledQuality = 100.0f * (1.0f - (FMath::Clamp(MinQP, 0, 51) / 51.0f)); SetCVarAndPropertyFromValue(NewCVarString, Property, FString::Printf(TEXT("%d"), ScaledQuality)); UE_LOGFMT(LogPixelStreaming2Settings, Warning, "PixelStreamingEncoderMinQp is a legacy setting, converted to PixelStreamingEncoderMaxQuality={0}", CVarEncoderMaxQuality.GetValueOnAnyThread()); continue; } } else if (LegacyCVarString == "PixelStreaming2.Encoder.MaxQp") { int32 MaxQP; if (FParse::Value(FCommandLine::Get(), *ConsoleVariableToCommandArgValue(LegacyCVarString), MaxQP)) { int32 ScaledQuality = 100.0f * (1.0f - (FMath::Clamp(MaxQP, 0, 51) / 51.0f)); SetCVarAndPropertyFromValue(NewCVarString, Property, FString::Printf(TEXT("%d"), ScaledQuality)); UE_LOGFMT(LogPixelStreaming2Settings, Warning, "PixelStreamingEncoderMaxQp is a legacy setting, converted to PixelStreamingEncoderMinQuality={0}", CVarEncoderMinQuality.GetValueOnAnyThread()); continue; } } else if (LegacyCVarString == "PixelStreaming2.IP" || LegacyCVarString == "PixelStreaming2.Port") { if (LegacyCVarString == "PixelStreaming2.IP") { FParse::Value(FCommandLine::Get(), *ConsoleVariableToCommandArgValue(LegacyCVarString), SignallingServerIP); } else if (LegacyCVarString == "PixelStreaming2.Port") { FParse::Value(FCommandLine::Get(), *ConsoleVariableToCommandArgValue(LegacyCVarString), SignallingServerPort); } if (!SignallingServerIP.IsEmpty() && !SignallingServerPort.IsEmpty()) { FString LegacyUrl = TEXT("ws://") + SignallingServerIP + TEXT(":") + SignallingServerPort; SetCVarAndPropertyFromValue(NewCVarString, Property, LegacyUrl); UE_LOGFMT(LogPixelStreaming2Settings, Warning, "PixelStreamingIP and PixelStreamingPort are legacy settings converted to -PixelStreamingConnectionURL={0}", CVarConnectionURL.GetValueOnAnyThread()); } continue; } FString ConsoleString; if (FParse::Value(FCommandLine::Get(), *ConsoleVariableToCommandArgValue(LegacyCVarString), ConsoleString)) { SetCVarAndPropertyFromValue(NewCVarString, Property, ConsoleString); } else if (FParse::Param(FCommandLine::Get(), *ConsoleVariableToCommandArgParam(LegacyCVarString))) { SetCVarAndPropertyFromValue(NewCVarString, Property, TEXT("true")); } else { continue; } UE_LOGFMT(LogPixelStreaming2Settings, Warning, "{0} is a legacy setting and has been converted to {1}", ConsoleVariableToCommandArgParam(LegacyCVarString), ConsoleVariableToCommandArgParam(NewCVarString)); } ParseLegacyCommandLineOption(TEXT("PixelStreamingDebugDumpFrame"), CVarEncoderDebugDumpFrame); // End legacy PixelStreaming command line args } void UPixelStreaming2PluginSettings::PostInitProperties() { Super::PostInitProperties(); UE_LOGFMT(LogPixelStreaming2Settings, Log, "Initialising Pixel Streaming settings."); // Set all the CVars to reflect the state of the ini InitializeCVarsFromProperties(); // Validate command line args to log if they're invalid ValidateCommandLineArgs(); // Update CVars and properties based on command line args ParseCommandlineArgs(); // Handle parsing of legacy command line args (such as -PixelStreamingUrl) after .ini and new commandline args. ParseLegacyCommandlineArgs(); // These cvars don't have matching properties so need to be manually parsed ParseLegacyCommandLineOption(*ConsoleVariableToCommandArgParam(TEXT("PixelStreaming2.Encoder.DumpDebugFrames")), CVarEncoderDebugDumpFrame); ParseLegacyCommandLineOption(*ConsoleVariableToCommandArgParam(TEXT("PixelStreaming2.DumpDebugAudio")), CVarDebugDumpAudio); } UPixelStreaming2PluginSettings::FDelegates* UPixelStreaming2PluginSettings::Delegates() { if (DelegateSingleton == nullptr && !IsEngineExitRequested()) { DelegateSingleton = new UPixelStreaming2PluginSettings::FDelegates(); return DelegateSingleton; } return DelegateSingleton; } TArray UPixelStreaming2PluginSettings::GetVideoCodecOptions() const { FProperty* Property = GetClass()->FindPropertyByName(TEXT("EnableSimulcast")); FBoolProperty* BoolProperty = CastField(Property); bool bSimulcastEnabled = BoolProperty->GetPropertyValue_InContainer(this); if (bSimulcastEnabled) { return { UE::PixelStreaming2::GetCVarStringFromEnum(EVideoCodec::H264), UE::PixelStreaming2::GetCVarStringFromEnum(EVideoCodec::VP8) }; } return { UE::PixelStreaming2::GetCVarStringFromEnum(EVideoCodec::AV1), UE::PixelStreaming2::GetCVarStringFromEnum(EVideoCodec::H264), UE::PixelStreaming2::GetCVarStringFromEnum(EVideoCodec::VP8), UE::PixelStreaming2::GetCVarStringFromEnum(EVideoCodec::VP9) }; } TArray UPixelStreaming2PluginSettings::GetScalabilityModeOptions() const { FProperty* Property = GetClass()->FindPropertyByName(TEXT("Codec")); FStrProperty* StrProperty = CastField(Property); FString SelectedCodec = StrProperty->GetPropertyValue_InContainer(this); // H.264 and VP8 only support temporal scalability bool bRestrictModes = SelectedCodec == TEXT("H264") || SelectedCodec == TEXT("VP8"); if (bRestrictModes) { return { UE::PixelStreaming2::GetCVarStringFromEnum(EScalabilityMode::L1T1), UE::PixelStreaming2::GetCVarStringFromEnum(EScalabilityMode::L1T2), UE::PixelStreaming2::GetCVarStringFromEnum(EScalabilityMode::L1T3), }; } TArray ScalabilityModes; for (uint32 i = 0; i <= static_cast(EScalabilityMode::None); i++) { ScalabilityModes.Add(UE::PixelStreaming2::GetCVarStringFromEnum(static_cast(i))); } return ScalabilityModes; } TArray UPixelStreaming2PluginSettings::GetWebRTCCodecPreferencesOptions() const { TSet PossibleCodecs = { UE::PixelStreaming2::GetCVarStringFromEnum(EVideoCodec::AV1), UE::PixelStreaming2::GetCVarStringFromEnum(EVideoCodec::H264), UE::PixelStreaming2::GetCVarStringFromEnum(EVideoCodec::VP9), UE::PixelStreaming2::GetCVarStringFromEnum(EVideoCodec::VP8) }; FProperty* Property = GetClass()->FindPropertyByName(TEXT("WebRTCCodecPreferences")); FArrayProperty* ArrayProperty = CastField(Property); TArray CurrentCodecArray = *ArrayProperty->ContainerPtrToValuePtr>(this); for (FString VideoCodec : CurrentCodecArray) { PossibleCodecs.Remove(VideoCodec); } return PossibleCodecs.Array(); } TArray UPixelStreaming2PluginSettings::GetDefaultStreamerTypeOptions() const { return IPixelStreaming2StreamerFactory::GetAvailableFactoryTypes(); }