Lost_Edge/Plugins/FMODStudio/Source/FMODAudioLink/Private/FMODAudioLinkFactory.cpp

240 lines
11 KiB (Stored with Git LFS)
C++

// Copyright (c), Firelight Technologies Pty, Ltd. 2024-2024.
#include "FMODAudioLinkFactory.h"
#include "FMODAudioLinkSynchronizer.h"
#include "FMODAudioLinkSourcePushed.h"
#include "FMODAudioLinkSettings.h"
#include "FMODAudioLinkLog.h"
#include "FMODAudioLinkComponent.h"
#include "FMODStudioModule.h"
#include "Async/Async.h"
#include "Components/AudioComponent.h"
#include "Engine/World.h"
#include "Sound/SoundSubmix.h"
#include "Templates/SharedPointer.h"
#include "AudioDevice.h"
bool FFMODAudioLinkFactory::bHasSubmix = false;
FName FFMODAudioLinkFactory::GetFactoryNameStatic()
{
static const FName FactoryName(TEXT("FMOD"));
return FactoryName;
}
FName FFMODAudioLinkFactory::GetFactoryName() const
{
return GetFactoryNameStatic();
}
TSubclassOf<UAudioLinkSettingsAbstract> FFMODAudioLinkFactory::GetSettingsClass() const
{
return UFMODAudioLinkSettings::StaticClass();
}
TUniquePtr<IAudioLink> FFMODAudioLinkFactory::CreateSubmixAudioLink(const FAudioLinkSubmixCreateArgs& InArgs)
{
if (!IFMODStudioModule::IsAvailable())
{
UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkFactory::CreateSubmixAudioLink: No FMODStudio module."));
return {};
}
if (!InArgs.Settings.IsValid())
{
UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkFactory::CreateSubmixAudioLink: Invalid FMODAudioLinkSettings."));
return {};
}
if (!InArgs.Submix.IsValid())
{
UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkFactory::CreateSubmixAudioLink: Invalid Submix."));
return {};
}
UE_LOG(LogFMODAudioLink, Verbose, TEXT("FFMODAudioLinkFactory::CreateSubmixAudioLink: Creating AudioLink %s for Submix %s."), *InArgs.Settings->GetName(), *InArgs.Submix->GetName());
bHasSubmix = true;
// Downcast to settings proxy
const FSharedFMODAudioLinkSettingsProxyPtr FMODSettingsSP = InArgs.Settings->GetCastProxy<FFMODAudioLinkSettingsProxy>();
// Make buffer listener first, which is our producer.
IAudioLinkFactory::FSubmixBufferListenerCreateParams SubmixListenerCreateArgs;
SubmixListenerCreateArgs.SizeOfBufferInFrames = FMODSettingsSP->GetReceivingBufferSizeInFrames();
SubmixListenerCreateArgs.bShouldZeroBuffer = FMODSettingsSP->ShouldClearBufferOnReceipt();
FSharedBufferedOutputPtr ProducerSP = CreateSubmixBufferListener(SubmixListenerCreateArgs);
TWeakPtr<IBufferedAudioOutput> ProducerWeak(ProducerSP);
// Create consumer.
FSharedFMODAudioLinkInputClientPtr ConsumerSP = MakeShared<FFMODAudioLinkInputClient, ESPMode::ThreadSafe>(
ProducerSP, InArgs.Settings->GetProxy(), InArgs.Submix->GetFName());
TWeakPtr<FFMODAudioLinkInputClient> ConsumerWeak(ConsumerSP);
// Setup a delegate to establish the link when we know the format.
ProducerSP->SetFormatKnownDelegate(
IBufferedAudioOutput::FOnFormatKnown::CreateLambda(
[ProducerWeak, ConsumerWeak, FMODSettingsSP](const IBufferedAudioOutput::FBufferFormat& InFormat)
{
// Unreal uses samples for 'Channels x samples' and frames for 'samples'
int32 BufferSizeInChannelSamples = FMODSettingsSP->GetReceivingBufferSizeInFrames() * InFormat.NumChannels;
int32 ReserveSizeInChannelSamples = (float)BufferSizeInChannelSamples * FMODSettingsSP->GetProducerConsumerBufferRatio();
int32 SilenceToAddToFirstBuffer = FMath::Min((float)BufferSizeInChannelSamples * FMODSettingsSP->GetInitialSilenceFillRatio(), ReserveSizeInChannelSamples);
// Set circular buffer ahead of first buffer.
if (auto ProducerSP = ProducerWeak.Pin())
{
ProducerSP->Reserve(ReserveSizeInChannelSamples, SilenceToAddToFirstBuffer);
}
AsyncTask(ENamedThreads::GameThread, [ConsumerWeak]()
{
if (FSharedFMODAudioLinkInputClientPtr ConsumerSP = ConsumerWeak.Pin())
{
// Stop ahead of starting to play. This might not be necessary for submixes, but in case we get a format change.
// As our link can remain open, stop anything playing on a format change.
// This won't do anything if we're already stopped.
ConsumerSP->Stop();
// Start the FMOD input object.
UE_LOG(LogFMODAudioLink, VeryVerbose, TEXT("FFMODAudioLinkFactory::CreateSubmixAudioLink: Start consumer."));
ConsumerSP->Start();
}
});
}));
// Start producer.
UE_LOG(LogFMODAudioLink, VeryVerbose, TEXT("FFMODAudioLinkFactory::CreateSubmixAudioLink: Start producer."));
ProducerSP->Start(InArgs.Device);
// Build a link, which owns both the consumer and producer.
return MakeUnique<FFMODAudioLink>(ProducerSP, ConsumerSP, InArgs.Device);
}
TUniquePtr<IAudioLink> FFMODAudioLinkFactory::CreateSourceAudioLink(const FAudioLinkSourceCreateArgs& InArgs)
{
if (!IFMODStudioModule::IsAvailable())
{
UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkFactory::CreateSourceAudioLink: No FMODStudio module."));
return {};
}
if (!InArgs.Settings.IsValid())
{
UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkFactory::CreateSourceAudioLink: Invalid FMODAudioLinkSettings."));
return {};
}
if (!InArgs.OwningComponent.IsValid())
{
UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkFactory::CreateSourceAudioLink: Invalid Owning Component."));
return {};
}
if (!InArgs.AudioComponent.IsValid())
{
UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkFactory::CreateSourceAudioLink: Invalid Audio Component."));
return {};
}
const UWorld* World = InArgs.OwningComponent->GetWorld();
if (UNLIKELY(!IsValid(World)))
{
UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkFactory::CreateSourceAudioLink: Invalid World in Owning Component."));
return {};
}
const FAudioDeviceHandle Handle = World->GetAudioDevice();
// Downcast to settings proxy.
const FSharedFMODAudioLinkSettingsProxyPtr FMODSettingsSP = InArgs.Settings->GetCastProxy<FFMODAudioLinkSettingsProxy>();
// Make buffer listener first, which is our producer.
FSourceBufferListenerCreateParams SourceBufferCreateArgs;
SourceBufferCreateArgs.SizeOfBufferInFrames = FMODSettingsSP->GetReceivingBufferSizeInFrames();
SourceBufferCreateArgs.bShouldZeroBuffer = true;
SourceBufferCreateArgs.OwningComponent = InArgs.OwningComponent;
SourceBufferCreateArgs.AudioComponent = InArgs.AudioComponent;
FSharedBufferedOutputPtr ProducerSP = CreateSourceBufferListener(SourceBufferCreateArgs);
static const FName UnknownOwner(TEXT("Unknown"));
FName OwnerName = InArgs.OwningComponent.IsValid() ? InArgs.OwningComponent->GetFName() : UnknownOwner;
TWeakPtr<IBufferedAudioOutput> ProducerWeak(ProducerSP);
// Create consumer.
FSharedFMODAudioLinkInputClientPtr ConsumerSP = MakeShared<FFMODAudioLinkInputClient, ESPMode::ThreadSafe>(ProducerSP, FMODSettingsSP, OwnerName);
TWeakPtr<FFMODAudioLinkInputClient> ConsumerWeak(ConsumerSP);
ProducerSP->SetFormatKnownDelegate(
IBufferedAudioOutput::FOnFormatKnown::CreateLambda(
[ProducerWeak, ConsumerWeak, FMODSettingsSP, WeakThis = InArgs.OwningComponent](const IBufferedAudioOutput::FBufferFormat& InFormat)
{
// Unreal uses samples for 'Channels x samples' and frames for 'samples'
int32 BufferSizeInChannelSamples = FMODSettingsSP->GetReceivingBufferSizeInFrames() * InFormat.NumChannels;
int32 ReserveSizeInChannelSamples = (float)BufferSizeInChannelSamples * FMODSettingsSP->GetProducerConsumerBufferRatio();
int32 SilenceToAddToFirstBuffer = FMath::Min((float)BufferSizeInChannelSamples * FMODSettingsSP->GetInitialSilenceFillRatio(), ReserveSizeInChannelSamples);
// Set circular buffer ahead of first buffer.
if (auto ProducerSP = ProducerWeak.Pin())
{
ProducerSP->Reserve(ReserveSizeInChannelSamples, SilenceToAddToFirstBuffer);
}
AsyncTask(ENamedThreads::GameThread, [ConsumerWeak, WeakThis]()
{
if (FSharedFMODAudioLinkInputClientPtr ConsumerSP = ConsumerWeak.Pin())
{
if (WeakThis.IsValid())
{
// Stop ahead of starting to play. This might not be necessary for submixes, but in case we get a format change.
// As our link can remain open, stop anything playing on a format change.
// This won't do anything if we're already stopped.
ConsumerSP->Stop();
// Start the FMOD input object.
UE_LOG(LogFMODAudioLink, VeryVerbose, TEXT("FFMODAudioLinkFactory::CreateSourceAudioLink: Start consumer."));
ConsumerSP->Start(Cast<UFMODAudioLinkComponent>(WeakThis.Get()));
}
}
});
}));
ProducerSP->SetBufferStreamEndDelegate(
IBufferedAudioOutput::FOnBufferStreamEnd::CreateLambda(
[ConsumerWeak](const IBufferedAudioOutput::FBufferStreamEnd&)
{
if (FSharedFMODAudioLinkInputClientPtr ConsumerSP = ConsumerWeak.Pin())
{
UE_LOG(LogFMODAudioLink, VeryVerbose, TEXT("FFMODAudioLinkFactory::CreateSourceAudioLink: Stop consumer."));
ConsumerSP->Stop();
}
}));
// Tell the Producer to Start receiving buffers from Sources.
// Pass a Lambda to do the some work when we know the Format, which starts FMOD up.
UE_LOG(LogFMODAudioLink, VeryVerbose, TEXT("FFMODAudioLinkFactory::CreateSourceAudioLink: Start producer."));
ProducerSP->Start(Handle ? Handle.GetAudioDevice() : nullptr);
// Make the link.
return MakeUnique<FFMODAudioLink>(ProducerSP, ConsumerSP);
}
IAudioLinkFactory::FAudioLinkSourcePushedSharedPtr FFMODAudioLinkFactory::CreateSourcePushedAudioLink(const FAudioLinkSourcePushedCreateArgs& InArgs)
{
if (IFMODStudioModule::IsAvailable())
{
UE_LOG(LogFMODAudioLink, VeryVerbose, TEXT("FFMODAudioLinkFactory::CreateSourcePushedAudioLink: Create AudioLink SourcePushed."));
return MakeShared<FFMODAudioLinkSourcePushed, ESPMode::ThreadSafe>(InArgs,this);
}
UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkFactory::CreateSourcePushedAudioLink: IFMODStudioModule not available."));
return nullptr;
}
IAudioLinkFactory::FAudioLinkSynchronizerSharedPtr FFMODAudioLinkFactory::CreateSynchronizerAudioLink()
{
UE_LOG(LogFMODAudioLink, VeryVerbose, TEXT("FFMODAudioLinkFactory::CreateSynchronizerAudioLink: Create AudioLink Synchronizer."));
auto SynchronizerSP = MakeShared<FFMODAudioLinkSynchronizer, ESPMode::ThreadSafe>();
return SynchronizerSP;
}