Files
UnrealEngine/Engine/Source/Runtime/AudioAnalyzer/Private/AudioAnalyzer.cpp
2025-05-18 13:04:45 +08:00

287 lines
8.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AudioAnalyzer.h"
#include "Async/Async.h"
#include "AudioAnalyzerFacade.h"
#include "AudioAnalyzerModule.h"
#include "AudioAnalyzerSubsystem.h"
#include "AudioBusSubsystem.h"
#include "AudioDeviceHandle.h"
#include "AudioDeviceManager.h"
#include "AudioMixerDevice.h"
#include "Engine/Engine.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AudioAnalyzer)
FAudioAnalyzeTask::FAudioAnalyzeTask(TUniquePtr<Audio::FAnalyzerFacade>& InAnalyzerFacade, int32 InSampleRate, int32 InNumChannels)
: AnalyzerFacade(InAnalyzerFacade.Release())
, SampleRate(InSampleRate)
, NumChannels(InNumChannels)
{
}
void FAudioAnalyzeTask::SetAudioBuffer(TArray<float>&& InAudioData)
{
AudioData = MoveTemp(InAudioData);
}
void FAudioAnalyzeTask::SetAnalyzerControls(TSharedPtr<Audio::IAnalyzerControls> InControls)
{
AnalyzerControls = InControls;
}
void FAudioAnalyzeTask::DoWork()
{
check(AnalyzerFacade);
Results = AnalyzerFacade->AnalyzeAudioBuffer(AudioData, NumChannels, SampleRate, AnalyzerControls);
}
void UAudioAnalyzer::StartAnalyzing(UWorld* InWorld, UAudioBus* AudioBusToAnalyze)
{
if (!InWorld)
{
return;
}
const FAudioDeviceHandle AudioDevice = InWorld->GetAudioDevice();
if (!AudioDevice.IsValid())
{
return;
}
StartAnalyzing(AudioDevice.GetDeviceID(), AudioBusToAnalyze);
}
void UAudioAnalyzer::StartAnalyzing(const Audio::FDeviceId InAudioDeviceId, UAudioBus* AudioBusToAnalyze)
{
if (!AudioBusToAnalyze)
{
UE_LOG(LogAudioAnalyzer, Error, TEXT("Unable to analyze audio without an audio bus to analyze."));
return;
}
const FAudioDeviceManager* AudioDeviceManager = FAudioDeviceManager::Get();
if (!AudioDeviceManager)
{
UE_LOG(LogAudioAnalyzer, Error, TEXT("Unable to analyze audio with a null audio device manager."));
return;
}
const Audio::FMixerDevice* MixerDevice = static_cast<const Audio::FMixerDevice*>(AudioDeviceManager->GetAudioDeviceRaw(InAudioDeviceId));
if (!MixerDevice)
{
UE_LOG(LogAudioAnalyzer, Error, TEXT("Audio analyzer only works with the audio mixer."));
return;
}
// Retrieve the audio analyzer subsystem if it's not already retrieved
if (!AudioAnalyzerSubsystem)
{
AudioAnalyzerSubsystem = UAudioAnalyzerSubsystem::Get();
}
// Store the audio bus we're analyzing
AudioBus = AudioBusToAnalyze;
NumBusChannels = AudioBus->GetNumChannels();
check(NumBusChannels > 0);
AudioMixerSampleRate = (int32)MixerDevice->GetSampleRate();
// Get the analyzer factory to use for our analysis
if (!AnalyzerFactory)
{
AnalyzerFactory = Audio::GetAnalyzerFactory(GetAnalyzerFactoryName());
}
checkf(AnalyzerFactory != nullptr, TEXT("Need to register the factory as a modular feature for the analyzer '%s'."), *GetAnalyzerFactoryName().ToString());
// Start the audio bus. This won't do anything if the bus is already started elsewhere.
uint32 AudioBusId = AudioBus->GetUniqueID();
int32 NumChannels = (int32)AudioBus->AudioBusChannels + 1;
UAudioBusSubsystem* AudioBusSubsystem = MixerDevice->GetSubsystem<UAudioBusSubsystem>();
check(AudioBusSubsystem);
Audio::FAudioBusKey AudioBusKey = Audio::FAudioBusKey(AudioBusId);
const FString AudioAnalyzerBusName = FString::Format(TEXT("_Analyzer_{0}_AudioBusId_{1}"), { *GetName(), AudioBusId });
AudioBusSubsystem->StartAudioBus(AudioBusKey, AudioAnalyzerBusName, NumChannels, false);
// Get an output patch for the audio bus
NumFramesPerBufferToAnalyze = MixerDevice->GetNumOutputFrames();
PatchOutputStrongPtr = AudioBusSubsystem->AddPatchOutputForAudioBus(AudioBusKey, NumFramesPerBufferToAnalyze, NumChannels);
// Register this audio analyzer with the audio analyzer subsystem
// The subsystem will query this analyzer to see if it has enough audio to perform analysis.
// If it does, it'll report the results (of any previous analysis) and ask us to start analyzing the audio.
if (AudioAnalyzerSubsystem)
{
AudioAnalyzerSubsystem->RegisterAudioAnalyzer(this);
}
// Setup the analyzer facade here once
AnalyzerFacade = MakeUnique<Audio::FAnalyzerFacade>(GetSettings(AudioMixerSampleRate, NumBusChannels), AnalyzerFactory);
}
void UAudioAnalyzer::StartAnalyzing(const UObject* WorldContextObject, UAudioBus* AudioBusToAnalyze)
{
if (!AudioBusToAnalyze)
{
UE_LOG(LogAudioAnalyzer, Error, TEXT("Unable to analyze audio without an audio bus to analyze."));
return;
}
// Retrieve the world and if it's not a valid world, don't do anything
UWorld* ThisWorld = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
if (!ThisWorld || !ThisWorld->bAllowAudioPlayback || ThisWorld->GetNetMode() == NM_DedicatedServer)
{
return;
}
const FAudioDeviceHandle AudioDevice = ThisWorld->GetAudioDevice();
if (!AudioDevice.IsValid())
{
return;
}
StartAnalyzing(AudioDevice.GetDeviceID(), AudioBusToAnalyze);
}
void UAudioAnalyzer::StopAnalyzing(const UObject* WorldContextObject)
{
if (AudioAnalyzerSubsystem)
{
// Unregister the audio analyzer from the analyzer subsystem
AudioAnalyzerSubsystem->UnregisterAudioAnalyzer(this);
// Null out our patch output
PatchOutputStrongPtr = nullptr;
}
}
bool UAudioAnalyzer::IsReadyForAnalysis() const
{
check(AudioBus != nullptr);
// Only allow one worker task to happen at a time
if (AnalysisTask.IsValid() && !AnalysisTask->IsWorkDone())
{
return false;
}
// Only ready if we've got a patch output and there's enough audio queued up
if (PatchOutputStrongPtr.IsValid())
{
int32 NumSamplesAvailable = PatchOutputStrongPtr->GetNumSamplesAvailable();
int32 NumAudioBusChannels = AudioBus->GetNumChannels();
check(NumAudioBusChannels > 0);
int32 NumFramesAvailable = NumSamplesAvailable / NumAudioBusChannels;
return NumFramesAvailable >= NumFramesPerBufferToAnalyze;
}
return false;
}
bool UAudioAnalyzer::DoAnalysis()
{
bool bHasResults = false;
// Make sure the task is finished before we do any more analysis
if (AnalysisTask.IsValid())
{
check(AnalysisTask->IsWorkDone());
// Do final ensure completion before reusing the task
AnalysisTask->EnsureCompletion();
// Report results from previous analysis
FAudioAnalyzeTask& Task = AnalysisTask->GetTask();
ResultsInternal = Task.GetResults();
// Retrieve the analysis buffer so we can reuse the memory when we copy to it from the patch output
AnalysisBuffer = Task.GetAudioBuffer();
bHasResults = true;
}
else
{
// Create a task if one doesn't exist
AnalysisTask = TSharedPtr<FAsyncTask<FAudioAnalyzeTask>>(new FAsyncTask<FAudioAnalyzeTask>(AnalyzerFacade, AudioMixerSampleRate, NumBusChannels));
}
// Make sure our task is valid and done
check(AnalysisTask.IsValid() && AnalysisTask->IsDone());
// Copy the audio to be analyzed from the patch output
int32 NumSamplesAvailable = PatchOutputStrongPtr->GetNumSamplesAvailable();
AnalysisBuffer.Reset();
AnalysisBuffer.AddUninitialized(NumSamplesAvailable);
PatchOutputStrongPtr->PopAudio(AnalysisBuffer.GetData(), NumSamplesAvailable, false);
check(AudioMixerSampleRate != 0);
check(NumBusChannels != 0);
FAudioAnalyzeTask& Task = AnalysisTask->GetTask();
Task.SetAnalyzerControls(GetAnalyzerControls());
Task.SetAudioBuffer(MoveTemp(AnalysisBuffer));
AnalysisTask->StartBackgroundTask();
return bHasResults;
}
void UAudioAnalyzer::BeginDestroy()
{
Super::BeginDestroy();
if (AnalysisTask.IsValid())
{
AnalysisTask->EnsureCompletion();
}
// Unregister this audio analyzer from the subsystem since we won't be needing anymore ticks
if (AudioAnalyzerSubsystem)
{
AudioAnalyzerSubsystem->UnregisterAudioAnalyzer(this);
}
}
UAudioAnalyzerSettings* UAudioAnalyzer::GetSettingsFromProperty(FProperty* Property)
{
if (nullptr == Property)
{
return nullptr;
}
if (Property->IsA(FObjectPropertyBase::StaticClass()))
{
FObjectPropertyBase* ObjectPropertyBase = CastFieldChecked<FObjectPropertyBase>(Property);
if (nullptr == ObjectPropertyBase)
{
return nullptr;
}
if (ObjectPropertyBase->PropertyClass->IsChildOf(UAudioAnalyzerSettings::StaticClass()))
{
UObject* PropertyObject = ObjectPropertyBase->GetObjectPropertyValue_InContainer(this);
return Cast<UAudioAnalyzerSettings>(PropertyObject);
}
}
return nullptr;
}
TUniquePtr<Audio::IAnalyzerSettings> UAudioAnalyzer::GetSettings(const int32 InSampleRate, const int32 InNumChannels) const
{
return MakeUnique<Audio::IAnalyzerSettings>();
}
TSharedPtr<Audio::IAnalyzerControls> UAudioAnalyzer::GetAnalyzerControls() const
{
return AnalyzerControls;
}