// SimpleRecorder.h — In-game gameplay recorder (Video + Audio + Mux) // Captures the Unreal back buffer & audio submix, pipes to FFmpeg/NVENC. #pragma once #include "CoreMinimal.h" #include "UObject/Object.h" #include "Sound/SoundSubmix.h" #include "ISubmixBufferListener.h" #include "SimpleRecorder.generated.h" // ───────────────────────────────────────────────────────────────────── // USimpleRecorder // // A UObject-based recorder you can create from Blueprint or C++. // • StartRecording() — begins video + audio capture // • StopRecording() — stops capture, writes .wav, muxes final .mp4 // // Outputs (inside /Saved/Recordings/): // video_only.mp4 — NVENC-encoded H.264 // audio_only.wav — PCM 16-bit // final_video_with_audio.mp4 — muxed result // ───────────────────────────────────────────────────────────────────── UCLASS(BlueprintType) class AUDIOVIDEORECORD_API USimpleRecorder : public UObject { GENERATED_BODY() public: USimpleRecorder(); // ── Blueprint-callable API ─────────────────────────────────────── /** Starts capturing video frames and audio. */ UFUNCTION(BlueprintCallable, Category = "SimpleRecorder") void StartRecording(); /** Stops capturing. Saves audio_only.wav, then muxes the final file. */ UFUNCTION(BlueprintCallable, Category = "SimpleRecorder") void StopRecording(); /** Returns true while recording is active. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SimpleRecorder") bool IsRecording() const { return bIsRecording; } // ── Configurable settings (edit before calling StartRecording) ─── /** If true, capture resolution is auto-detected from the viewport at recording start. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SimpleRecorder|Settings") bool bAutoDetectResolution = true; /** Capture width in pixels (ignored if bAutoDetectResolution is true). */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SimpleRecorder|Settings") int32 CaptureWidth = 1920; /** Capture height in pixels (ignored if bAutoDetectResolution is true). */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SimpleRecorder|Settings") int32 CaptureHeight = 1080; /** Target frames per second. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SimpleRecorder|Settings") int32 CaptureFPS = 60; /** Video bitrate string for FFmpeg (e.g. "8M", "12M"). */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SimpleRecorder|Settings") FString VideoBitrate = TEXT("8M"); /** Directory where output files are saved (absolute or project-relative). */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SimpleRecorder|Settings") FString OutputDirectory; /** Full path to ffmpeg.exe. If empty we look on the system PATH. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SimpleRecorder|Settings") FString FFmpegPath; /** The audio submix to record. If null, the engine master submix is used. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SimpleRecorder|Settings") USoundSubmix* TargetSubmix = nullptr; // ── Audio submix callback (forwarded from bridge) ────────────── void OnNewSubmixBuffer( const USoundSubmix* OwningSubmix, float* AudioData, int32 NumSamples, int32 NumChannels, const int32 SampleRate, double AudioClock); protected: virtual void BeginDestroy() override; private: // ── Internal helpers ───────────────────────────────────────────── void InitOutputPaths(); FString GetFFmpegExecutable() const; /** Called every frame on the render thread when the back buffer is ready. */ void OnBackBufferReady(SWindow& SlateWindow, const FTextureRHIRef& BackBuffer); /** Writes the captured audio buffer to a .wav file. */ void SaveAudioToWav(); /** Runs FFmpeg to mux video_only.mp4 + audio_only.wav → final .mp4. */ void MuxAudioVideo(); // ── State ──────────────────────────────────────────────────────── bool bIsRecording = false; // Video FDelegateHandle BackBufferDelegateHandle; FILE* FFmpegVideoPipe = nullptr; // Audio — accumulated raw PCM data TArray AudioBuffer; // interleaved float samples int32 AudioSampleRate = 48000; int32 AudioNumChannels = 2; FCriticalSection AudioBufferCritSection; // Output file paths (computed once per recording session) FString VideoFilePath; FString AudioFilePath; FString FinalFilePath; // Submix bridge (UObjects can't be TSharedRef, so we use a bridge) TSharedPtr SubmixBridge; };