Skip to main content
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)
);

Audio Data Format

PropertyValue
Sample Format32-bit float (-1.0 to 1.0)
Channel LayoutInterleaved
Typical Channels2 (stereo)
Sample RateSource-dependent (typically 48000 Hz)

Supported Channels

Each audio source declares which channels it supports:
SourceGameMicrophoneVoiceChat
LCKUnrealAudioYesYesNo
LCKOboeNoYesNo
LCKVivoxNoYesYes
LCKFMODYesNoNo
LCKWwiseYesNoNo
// 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

1

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;
};
2

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);
    }
}
3

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