Module: LCKAudio | Type: Interface
Overview
ILCKAudioSource defines the contract for audio capture sources in LCK. Each audio plugin (UnrealAudio, Oboe, Vivox, FMOD, Wwise) implements this interface.
Critical Usage Note
IMPORTANT: Use BindLambda (single delegate), NOT AddLambda.OnAudioDataDelegate is a single delegate, not a multicast delegate.
Using AddLambda will fail or produce unexpected behavior.
Correct Usage
// CORRECT: Use BindLambda for single delegate
Source->OnAudioDataDelegate.BindLambda([](
TArrayView<const float> PCM,
int32 Channels,
ELCKAudioChannel SourceChannel)
{
// Process audio data
});
Incorrect Usage
// WRONG: Do NOT use AddLambda - it's not a multicast delegate!
Source->OnAudioDataDelegate.AddLambda(...); // ERROR!
// WRONG: Do NOT use AddDynamic
Source->OnAudioDataDelegate.AddDynamic(...); // ERROR!
Interface Definition
class ILCKAudioSource : public IModularFeature,
public TSharedFromThis<ILCKAudioSource>
{
public:
static FName GetModularFeatureName()
{
return TEXT("LCKAudioSource");
}
// Audio data callback (single delegate - use BindLambda)
FOnRenderAudioDelegate OnAudioDataDelegate;
// Control methods
virtual bool StartCapture() noexcept = 0;
virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept = 0;
virtual void StopCapture() noexcept = 0;
// Query methods
virtual float GetVolume() const noexcept = 0;
virtual const FString& GetSourceName() const noexcept = 0;
TLCKAudioChannelsMask GetSupportedChannels() const noexcept;
protected:
TLCKAudioChannelsMask SupportedChannels;
};
Delegate Signature
DECLARE_DELEGATE_ThreeParams(
FOnRenderAudioDelegate,
TArrayView<const float>, // PCM - Interleaved audio samples (32-bit float)
int32, // Channels - Number of audio channels (1=mono, 2=stereo)
ELCKAudioChannel // SourceChannel - Which channel type (Game, Mic, VoiceChat)
);
| Property | Value |
|---|
| Sample Format | 32-bit float (-1.0 to 1.0) |
| Channel Layout | Interleaved |
| Typical Channels | 2 (stereo) |
| Sample Rate | Source-dependent (typically 48000 Hz) |
Supported Channels
Each audio source declares which channels it supports:
| Source | Game | Microphone | VoiceChat |
|---|
| LCKUnrealAudio | Yes | Yes | No |
| LCKOboe | No | Yes | No |
| LCKVivox | No | Yes | Yes |
| LCKFMOD | Yes | No | No |
| LCKWwise | Yes | No | No |
// Check if source supports a channel
TLCKAudioChannelsMask SupportedChannels = Source->GetSupportedChannels();
if (EnumHasAnyFlags(SupportedChannels, ELCKAudioChannel::Microphone))
{
// Source can capture microphone
}
Finding Audio Sources
Audio sources are discovered via the modular feature system:
TArray<ILCKAudioSource*> GetAllAudioSources()
{
return IModularFeatures::Get().GetModularFeatureImplementations<ILCKAudioSource>(
ILCKAudioSource::GetModularFeatureName()
);
}
// Find sources for specific channel
TArray<ILCKAudioSource*> GetSourcesForChannel(ELCKAudioChannel Channel)
{
TArray<ILCKAudioSource*> Result;
for (ILCKAudioSource* Source : GetAllAudioSources())
{
if (EnumHasAnyFlags(Source->GetSupportedChannels(), Channel))
{
Result.Add(Source);
}
}
return Result;
}
FLCKAudioMix
The FLCKAudioMix class combines multiple audio sources into a single stereo output:
class FLCKAudioMix
{
public:
void AddSource(TSharedPtr<ILCKAudioSource> Source);
void RemoveSource(TSharedPtr<ILCKAudioSource> Source);
// Get mixed stereo audio for specified channels
TArray<float> StereoMix(TLCKAudioChannelsMask Channels);
private:
TArray<TWeakPtr<ILCKAudioSource>> Sources;
FCriticalSection Mutex;
};
Usage
FLCKAudioMix AudioMix;
// Add sources
AudioMix.AddSource(UnrealAudioSource);
AudioMix.AddSource(VivoxSource);
// Get mixed audio (Game + Microphone channels)
TLCKAudioChannelsMask Channels = ELCKAudioChannel::Game | ELCKAudioChannel::Microphone;
TArray<float> MixedAudio = AudioMix.StereoMix(Channels);
Implementing a Custom Audio Source
Create Source Class
class FMyAudioSource : public ILCKAudioSource
{
public:
FMyAudioSource()
{
// Declare supported channels
SupportedChannels = ELCKAudioChannel::Game;
SourceName = TEXT("MyAudioSource");
}
virtual bool StartCapture() noexcept override
{
// Initialize audio capture
return true;
}
virtual void StopCapture() noexcept override
{
// Cleanup
}
virtual float GetVolume() const noexcept override
{
return CurrentVolume;
}
virtual const FString& GetSourceName() const noexcept override
{
return SourceName;
}
private:
FString SourceName;
float CurrentVolume = 0.0f;
};
Broadcast Audio Data
void FMyAudioSource::OnAudioCallback(float* Buffer, int32 NumSamples)
{
if (OnAudioDataDelegate.IsBound())
{
TArrayView<const float> PCMData(Buffer, NumSamples);
OnAudioDataDelegate.Execute(PCMData, 2, ELCKAudioChannel::Game);
}
}
Register as Modular Feature
void FMyAudioModule::StartupModule()
{
AudioSource = MakeShared<FMyAudioSource>();
IModularFeatures::Get().RegisterModularFeature(
ILCKAudioSource::GetModularFeatureName(),
AudioSource.Get()
);
}
void FMyAudioModule::ShutdownModule()
{
IModularFeatures::Get().UnregisterModularFeature(
ILCKAudioSource::GetModularFeatureName(),
AudioSource.Get()
);
}
Thread Safety
Audio callbacks often come from different threads. Use AsyncTask to marshal to the game thread:
void FMyAudioSource::OnAudioCallback(float* Buffer, int32 NumSamples)
{
// Copy data for thread safety
TArray<float> AudioData(Buffer, NumSamples);
// Marshal to game thread
AsyncTask(ENamedThreads::GameThread, [this, AudioData = MoveTemp(AudioData)]()
{
if (OnAudioDataDelegate.IsBound())
{
OnAudioDataDelegate.Execute(AudioData, 2, ELCKAudioChannel::Game);
}
});
}
See Also