Files
UnrealEngine/Engine/Source/Runtime/MovieSceneCapture/Public/MovieSceneCaptureProtocolBase.h
2025-05-18 13:04:45 +08:00

378 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "MovieSceneCaptureSettings.h"
#include "MovieSceneCaptureProtocolBase.generated.h"
class FSceneViewport;
struct FFrameRate;
struct FFrameMetrics;
struct ICaptureProtocolHost;
struct FMovieSceneCaptureSettings;
UENUM()
enum class EMovieSceneCaptureProtocolState : uint8
{
/** The protocol is idle, and has not even been initialized */
Idle,
/** The protocol has been initialized (and bound to a viewport) but is not capturing frames yet */
Initialized,
/** The protocol has been initialized, bound to a viewport and is capturing data */
Capturing,
/** The protocol has finished capturing data, and is pending finalization */
Finalizing,
};
/** Structure used to initialize a capture protocol */
struct FCaptureProtocolInitSettings
{
/**~ @todo: add ability to capture a sub-rectangle */
/** Capture from a slate viewport, using the specified custom protocol settings */
MOVIESCENECAPTURE_API static FCaptureProtocolInitSettings FromSlateViewport(TSharedRef<FSceneViewport> InSceneViewport);
/** The slate viewport we should capture from */
TSharedPtr<FSceneViewport> SceneViewport;
/** The desired size of the captured frames */
FIntPoint DesiredSize;
private:
/** Private construction to ensure use of static init methods */
FCaptureProtocolInitSettings() {}
};
/**
* A capture protocol responsible for dealing with captured frames using some custom method (writing out to disk, streaming, etc)
*
* A typical process for capture consits of the following process:
* Setup -> [ Warm up -> [ Capture Frame ] ] -> Begin Finalize -> [ HasFinishedProcessing ] -> Finalize
*/
UCLASS(config=EditorPerProjectUserSettings, PerObjectConfig, Abstract, MinimalAPI)
class UMovieSceneCaptureProtocolBase : public UObject
{
public:
GENERATED_BODY()
MOVIESCENECAPTURE_API UMovieSceneCaptureProtocolBase(const FObjectInitializer& ObjInit);
public:
/**
* Get the current state of this capture protocol
*/
UFUNCTION(BlueprintCallable, Category=Capture)
EMovieSceneCaptureProtocolState GetState() const { return State; }
/**
* Check whether we can capture a frame from this protocol
*/
UFUNCTION(BlueprintCallable, Category=Capture)
bool IsCapturing() const { return State == EMovieSceneCaptureProtocolState::Capturing || bFrameRequested[GFrameCounter%2] == true; }
/**
* Setup this capture protocol
*
* @param InSettings The initial initialization settings to use for the capture
* @param Host The client that is initializing this protocol
*/
MOVIESCENECAPTURE_API bool Setup(const FCaptureProtocolInitSettings& InSettings, const ICaptureProtocolHost* Host);
/**
* Get the UWorld associated with this Capture Protocol. This is not valid until
* Setup has been called with a valid Slate viewport. Will return nullptr when
* the protocol has been created but the game world is not running (ie: in UI).
*/
MOVIESCENECAPTURE_API virtual class UWorld* GetWorld() const override;
public:
/**
* Called on the main thread before the movie capture itself is updated to reset per-frame state
*/
MOVIESCENECAPTURE_API void PreTick();
/**
* Called on the main thread to do any additional processing
*/
MOVIESCENECAPTURE_API void Tick();
/**
* Start warming up this capture protocol - called any time the process enters a warming-up state
*/
MOVIESCENECAPTURE_API void WarmUp();
/**
* Called when this protocol should start capturing
*
* @return true if the operation was successful, false otherwise
*/
MOVIESCENECAPTURE_API bool StartCapture();
/**
* Instruct this protocol to capture a frame relating to the specified metrics
*
* @param FrameMetrics Frame metrics relating to the current frame
* @param Host The client that is initializing this protocol
*/
MOVIESCENECAPTURE_API void CaptureFrame(const FFrameMetrics& FrameMetrics);
/**
* Called when we have finished capturing and we should start finalizing the capture
*/
MOVIESCENECAPTURE_API void BeginFinalize();
/**
* Check whether this protocol has any processing left to do, or whether it should be finalized.
* Only called when the capture has been asked to end.
*/
MOVIESCENECAPTURE_API bool HasFinishedProcessing() const;
/**
* Called when this protocol should tear down and finalize all its processing. Only called if HasFinishedProcessing is true.
*/
MOVIESCENECAPTURE_API void Finalize();
/**
* Called when generating formatting filename to add any additional format mappings
*
* @param FormatMappings Map to add additional format rules to
*/
MOVIESCENECAPTURE_API void AddFormatMappings(TMap<FString, FStringFormatArg>& FormatMappings) const;
/**
* Called when this protocol has been released
*/
MOVIESCENECAPTURE_API void OnReleaseConfig(FMovieSceneCaptureSettings& InSettings);
/**
* Called when this protocol has been loaded
*/
MOVIESCENECAPTURE_API void OnLoadConfig(FMovieSceneCaptureSettings& InSettings);
/**
* Test whether this capture protocol thinks the file should be written to. Only called when we're not overwriting existing files.
* By default, we simply test for the file's existence, however this can be overridden to afford complex behaviour like
* writing out multiple video files for different file names
* @param InFilename The filename to test
* @param bOverwriteExisting Whether we are allowed to overwrite existing files
* @return Whether we should deem this file writable or not
*/
MOVIESCENECAPTURE_API bool CanWriteToFile(const TCHAR* InFilename, bool bOverwriteExisting) const;
protected:
/**
* Called once at the start of the capture process (before any warmup) to set up anything required for the capture.
*/
virtual bool SetupImpl()
{
return true;
}
/**
* Called on the main thread before the movie capture itself is updated to reset per-frame state
*/
virtual void PreTickImpl() {}
/**
* Called on the main thread to do any additional processing
*/
virtual void TickImpl() {}
/**
* Start warming up this capture protocol
*/
virtual void WarmUpImpl() {}
/**
* Start capturing
*
* @return true if the operation was successful, false otherwise
*/
virtual bool StartCaptureImpl()
{
return true;
}
/**
* Instruct this protocol to capture a frame relating to the specified metrics
*
* @param FrameMetrics Frame metrics relating to the current frame
*/
virtual void CaptureFrameImpl(const FFrameMetrics& FrameMetrics) {}
/**
* Pause capturing
*/
virtual void PauseCaptureImpl() {}
/**
* Called when we have finished capturing and we should start finalizing the capture
*/
virtual void BeginFinalizeImpl() {}
/**
* Check whether this protocol has any processing left to do, or whether it should be finalized.
* Only called when the capture has been asked to end.
*/
virtual bool HasFinishedProcessingImpl() const { return true; }
/**
* Called when we have finished capturing
*/
virtual void FinalizeImpl() {}
/**
* Called when generating formatting filename to add any additional format mappings
*
* @param FormatMappings Map to add additional format rules to
*/
virtual void AddFormatMappingsImpl(TMap<FString, FStringFormatArg>& FormatMappings) const {}
/**
* Called when this protocol has been released
*/
virtual void OnReleaseConfigImpl(FMovieSceneCaptureSettings& InSettings) {}
/**
* Called when this protocol has been loaded
*/
virtual void OnLoadConfigImpl(FMovieSceneCaptureSettings& InSettings) {}
/**
* Test whether this capture protocol thinks the file should be written to. Only called when we're not overwriting existing files.
* By default, we simply test for the file's existence, however this can be overridden to afford complex behaviour like
* writing out multiple video files for different file names
* @param InFilename The filename to test
* @param bOverwriteExisting Whether we are allowed to overwrite existing files
* @return Whether we should deem this file writable or not
*/
MOVIESCENECAPTURE_API virtual bool CanWriteToFileImpl(const TCHAR* InFilename, bool bOverwriteExisting) const;
MOVIESCENECAPTURE_API virtual FString GenerateFilenameImpl(const FFrameMetrics& FrameMetrics, const TCHAR* Extension, const FString* NameFormatString = nullptr) const;
MOVIESCENECAPTURE_API void EnsureFileWritableImpl(const FString& File) const;
protected:
/** Initialization settings */
TOptional<FCaptureProtocolInitSettings> InitSettings;
/** The capture host that is owns this protocol */
const ICaptureProtocolHost* CaptureHost;
private:
/** The current state of the protocol */
UPROPERTY(transient)
EMovieSceneCaptureProtocolState State;
/** Double buffer of values tracking whether we are capturing changes based on GFrameCounter */
bool bFrameRequested[2];
};
/**
* A class to inherit from for image capture protocols. Used to filter the UI for protocols used on the image capture pass.
*/
UCLASS(config = EditorPerProjectUserSettings, PerObjectConfig, Abstract, MinimalAPI)
class UMovieSceneImageCaptureProtocolBase : public UMovieSceneCaptureProtocolBase
{
GENERATED_BODY()
public:
UMovieSceneImageCaptureProtocolBase(const FObjectInitializer& ObjInit)
: UMovieSceneCaptureProtocolBase(ObjInit)
{}
};
/**
* A class to inherit from for audio capture protocols. Used to filter the UI for protocols used on the audio capture pass.
*/
UCLASS(config = EditorPerProjectUserSettings, PerObjectConfig, Abstract, MinimalAPI)
class UMovieSceneAudioCaptureProtocolBase : public UMovieSceneCaptureProtocolBase
{
GENERATED_BODY()
public:
UMovieSceneAudioCaptureProtocolBase(const FObjectInitializer& ObjInit)
: UMovieSceneCaptureProtocolBase(ObjInit)
{}
};
/** Metrics that correspond to a particular frame */
USTRUCT(BlueprintType)
struct FFrameMetrics
{
GENERATED_BODY()
/** Default construction */
FFrameMetrics() : TotalElapsedTime(0), FrameDelta(0), FrameNumber(0), NumDroppedFrames(0) {}
FFrameMetrics(float InTotalElapsedTime, float InFrameDelta, int32 InFrameNumber, int32 InNumDroppedFrames)
: TotalElapsedTime(InTotalElapsedTime), FrameDelta(InFrameDelta), FrameNumber(InFrameNumber), NumDroppedFrames(InNumDroppedFrames)
{
}
/** The total amount of time, in seconds, since the capture started */
UPROPERTY(BlueprintReadOnly, Category=Capture)
float TotalElapsedTime;
/** The total amount of time, in seconds, that this specific frame took to render (not accounting for dropped frames) */
UPROPERTY(BlueprintReadOnly, Category=Capture)
float FrameDelta;
/** The index of this frame from the start of the capture, including dropped frames */
UPROPERTY(BlueprintReadOnly, Category=Capture)
int32 FrameNumber;
/** The number of frames we dropped in-between this frame, and the last one we captured */
UPROPERTY(BlueprintReadOnly, Category=Capture)
int32 NumDroppedFrames;
};
/** Interface that defines when to capture or drop frames */
struct ICaptureStrategy
{
virtual ~ICaptureStrategy(){}
virtual void OnInitialize() = 0;
virtual void OnStop() = 0;
virtual bool ShouldSynchronizeFrames() const { return true; }
virtual bool ShouldPresent(double CurrentTimeSeconds, uint32 FrameIndex) const = 0;
virtual int32 GetDroppedFrames(double CurrentTimeSeconds, uint32 FrameIndex) const = 0;
};
/** Interface to be implemented by any class using an UMovieSceneCaptureProtocolBase instance*/
struct ICaptureProtocolHost
{
/** Get shared settings for the capture */
virtual const FMovieSceneCaptureSettings& GetSettings() const = 0;
/** Access Frame number index offset when saving out frames. */
virtual const int32 GetFrameNumberOffset() const = 0;
/** Get the capture frequency */
virtual FFrameRate GetCaptureFrameRate() const = 0;
/** Access the host's capture strategy */
virtual const ICaptureStrategy& GetCaptureStrategy() const = 0;
/** Ask the host to resolve the format string for a file name. */
virtual FString ResolveFileFormat(const FString& Format, const FFrameMetrics& FrameMetrics) const = 0;
/** Ask the host to inform us of how long the capture duration is expected to be. Should only be used as an estimate
* due to the possible complexities in calculating the duration due to handle frames, warmups, etc.
*/
virtual double GetEstimatedCaptureDurationSeconds() const = 0;
};