newly added updated FMODStudio plugin for migration
This commit is contained in:
@ -0,0 +1,239 @@
|
||||
// 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;
|
||||
}
|
Reference in New Issue
Block a user