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
);
}
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);
}
FLCKAndroidEncoder
Uses Android NDK MediaCodec for encoding.Technologies:
AMediaCodec for H.264/AAC encoding
AMediaMuxer for MP4 muxing
- Vulkan/EGL texture interop
Features:
- Hardware-accelerated encoding
- GPU texture capture via Vulkan
- Low-latency encoding pipeline
- Direct write to device storage
Vulkan Interop:// Export Vulkan texture to EGL
VkExternalMemoryHandleTypeFlagBits HandleType =
VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
// Import into MediaCodec surface
AMediaCodec_queueInputBuffer(Codec, BufferIndex, 0, Size, TimeUs, 0);
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.
- Implement
ILCKEncoder interface
- Create
ILCKEncoderFactory implementation
- 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