Skip to main content
Module: LCKCore | Type: Interface (Pure Virtual)

Overview

ILCKEncoder defines the abstract interface for video encoding. Platform-specific implementations handle the actual encoding:
  • Windows: FLCKWindowsEncoder using Media Foundation
  • Android: FLCKAndroidEncoder using NDK MediaCodec
Most developers don’t need to interact with this interface directly. Use ULCKRecorderSubsystem or ULCKService for recording functionality.

Interface Definition

class ILCKEncoder : public FRunnable
{
public:
    virtual ~ILCKEncoder() = default;

    // Lifecycle
    virtual bool Open() = 0;
    virtual void Close() = 0;
    virtual bool IsEncoding() const = 0;

    // Encoding
    virtual void EncodeTexture(FTextureRHIRef& Texture, float TimeSeconds) = 0;
    virtual void EncodeAudio(TArrayView<float> PCMData) = 0;

    // Finalization
    virtual void Save(TFunction<void(float)> ProgressCallback) = 0;

    // Queries
    virtual float GetAudioTime() const = 0;
    virtual FString GetOutputPath() const = 0;
};

Encoder Factory

Encoders are created via the modular feature system:
class ILCKEncoderFactory : public IModularFeature
{
public:
    static FName GetModularFeatureName()
    {
        return TEXT("LCKEncoderFactory");
    }

    virtual FString GetEncoderName() const = 0;

    virtual TSharedPtr<ILCKEncoder> CreateEncoder(
        int32 Width,
        int32 Height,
        int32 VideoBitrate,
        int32 Framerate,
        int32 Samplerate,
        int32 AudioBitrate
    ) = 0;
};

Finding an Encoder

ILCKEncoderFactory* Factory = nullptr;
auto& ModularFeatures = IModularFeatures::Get();

if (ModularFeatures.IsModularFeatureAvailable(ILCKEncoderFactory::GetModularFeatureName()))
{
    Factory = &ModularFeatures.GetModularFeature<ILCKEncoderFactory>(
        ILCKEncoderFactory::GetModularFeatureName()
    );
}

if (Factory)
{
    TSharedPtr<ILCKEncoder> Encoder = Factory->CreateEncoder(
        1920, 1080,  // Resolution
        8000000,     // Video bitrate (8 Mbps)
        30,          // Framerate
        48000,       // Audio sample rate
        256000       // Audio bitrate
    );
}

Platform Implementations

FLCKWindowsEncoder

Uses Windows Media Foundation for encoding.Technologies:
  • IMFSinkWriter for muxing
  • IMFTransform for H.264 encoding
  • Direct3D 11 texture interop
Features:
  • Hardware-accelerated H.264 encoding
  • AAC audio encoding
  • MP4 container output
  • Triple-buffered texture pool
Error Handling:
HRESULT hr = SinkWriter->WriteSample(VideoStreamIndex, Sample);
if (FAILED(hr))
{
    UE_LOG(LogLCKEncoding, Error, TEXT("WriteSample failed: 0x%08X"), hr);
}

Texture Encoding Flow

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Scene Capture  │────>│  Render Target  │────>│  Texture Pool   │
└─────────────────┘     └─────────────────┘     └────────┬────────┘

                                                         v
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   MP4 File      │<────│  Video Encoder  │<────│  GPU Readback   │
└─────────────────┘     └─────────────────┘     └─────────────────┘

Triple-Buffered Texture Pool

The encoder uses triple-buffering to avoid GPU stalls:
class FTexturePool
{
    static constexpr int32 PoolSize = 3;
    TArray<FTextureRHIRef> Textures;
    int32 CurrentIndex = 0;

public:
    FTextureRHIRef GetNextTexture()
    {
        FTextureRHIRef Texture = Textures[CurrentIndex];
        CurrentIndex = (CurrentIndex + 1) % PoolSize;
        return Texture;
    }
};

Audio Encoding

Audio data flows through the encoder’s audio pipeline:
void ILCKEncoder::EncodeAudio(TArrayView<float> PCMData)
{
    // PCM format: 32-bit float, interleaved stereo
    // Sample rate: typically 48000 Hz

    // Convert float to int16 for encoder
    TArray<int16> IntSamples;
    IntSamples.SetNum(PCMData.Num());

    for (int32 i = 0; i < PCMData.Num(); ++i)
    {
        float Sample = FMath::Clamp(PCMData[i], -1.0f, 1.0f);
        IntSamples[i] = static_cast<int16>(Sample * 32767.0f);
    }

    // Encode to AAC...
}

Thread Safety

Encoding runs on a dedicated thread:
class ILCKEncoder : public FRunnable
{
protected:
    FRunnableThread* EncoderThread;
    FCriticalSection EncodingMutex;
    TQueue<FEncodingTask> TaskQueue;

public:
    virtual uint32 Run() override
    {
        while (bShouldRun)
        {
            FEncodingTask Task;
            if (TaskQueue.Dequeue(Task))
            {
                FScopeLock Lock(&EncodingMutex);
                ProcessTask(Task);
            }
        }
        return 0;
    }
};

Creating a Custom Encoder

Custom encoders are advanced usage. Most games should use the built-in platform encoders.
  1. Implement ILCKEncoder interface
  2. Create ILCKEncoderFactory implementation
  3. Register via modular features
class FMyCustomEncoder : public ILCKEncoder
{
public:
    virtual bool Open() override { /* Initialize encoder */ }
    virtual void EncodeTexture(FTextureRHIRef& Texture, float TimeSeconds) override { /* Encode frame */ }
    virtual void EncodeAudio(TArrayView<float> PCMData) override { /* Encode audio */ }
    virtual void Save(TFunction<void(float)> ProgressCallback) override { /* Finalize file */ }
    // ...
};

class FMyEncoderFactory : public ILCKEncoderFactory
{
public:
    virtual FString GetEncoderName() const override { return TEXT("MyEncoder"); }

    virtual TSharedPtr<ILCKEncoder> CreateEncoder(...) override
    {
        return MakeShared<FMyCustomEncoder>(...);
    }
};

// Register in module startup
void FMyModule::StartupModule()
{
    IModularFeatures::Get().RegisterModularFeature(
        ILCKEncoderFactory::GetModularFeatureName(),
        &MyEncoderFactory
    );
}

See Also