126 lines
5.2 KiB
C
126 lines
5.2 KiB
C
|
|
// 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 <ProjectDir>/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<float> 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<ISubmixBufferListener, ESPMode::ThreadSafe> SubmixBridge;
|
||
|
|
};
|