// Copyright Epic Games, Inc. All Rights Reserved. #include "Providers/AudioBusProvider.h" #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AudioInsightsEditorDashboardFactory.h" #include "AudioInsightsEditorModule.h" #include "AudioMixerTrace.h" #include "Sound/AudioBus.h" namespace UE::Audio::Insights { FAudioBusProvider::FAudioBusProvider() : TDeviceDataMapTraceProvider>(GetName_Static()) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); AssetRegistryModule.Get().OnAssetAdded().AddRaw(this, &FAudioBusProvider::OnAssetAdded); AssetRegistryModule.Get().OnAssetRemoved().AddRaw(this, &FAudioBusProvider::OnAssetRemoved); AssetRegistryModule.Get().OnFilesLoaded().AddRaw(this, &FAudioBusProvider::OnFilesLoaded); FEditorDashboardFactory::OnActiveAudioDeviceChanged.AddRaw(this, &FAudioBusProvider::OnActiveAudioDeviceChanged); #if UE_AUDIO_PROFILERTRACE_ENABLED FTraceAuxiliary::OnTraceStarted.AddRaw(this, &FAudioBusProvider::OnTraceStarted); #endif } FAudioBusProvider::~FAudioBusProvider() { if (FAssetRegistryModule* AssetRegistryModule = FModuleManager::GetModulePtr("AssetRegistry")) { AssetRegistryModule->Get().OnAssetAdded().RemoveAll(this); AssetRegistryModule->Get().OnAssetRemoved().RemoveAll(this); AssetRegistryModule->Get().OnFilesLoaded().RemoveAll(this); } FEditorDashboardFactory::OnActiveAudioDeviceChanged.RemoveAll(this); #if UE_AUDIO_PROFILERTRACE_ENABLED FTraceAuxiliary::OnTraceStarted.RemoveAll(this); #endif // UE_AUDIO_PROFILERTRACE_ENABLED } FName FAudioBusProvider::GetName_Static() { return "AudioBusesProvider"; } void FAudioBusProvider::OnAssetAdded(const FAssetData& InAssetData) { if (bAreFilesLoaded && InAssetData.AssetClassPath == FTopLevelAssetPath(UAudioBus::StaticClass())) { AddAudioBusAsset(InAssetData); } } void FAudioBusProvider::OnAssetRemoved(const FAssetData& InAssetData) { if (InAssetData.AssetClassPath == FTopLevelAssetPath(UAudioBus::StaticClass())) { RemoveAudioBusAsset(InAssetData); } } void FAudioBusProvider::OnFilesLoaded() { bAreFilesLoaded = true; UpdateAudioBusAssetNames(); } void FAudioBusProvider::OnActiveAudioDeviceChanged() { UpdateAudioBusAssetNames(); } void FAudioBusProvider::RequestEntriesUpdate() { UpdateAudioBusAssetNames(); } void FAudioBusProvider::OnTraceStarted(FTraceAuxiliary::EConnectionType TraceType, const FString& TraceDestination) { #if UE_AUDIO_PROFILERTRACE_ENABLED UpdateAudioBusAssetNames(); #endif // UE_AUDIO_PROFILERTRACE_ENABLED } void FAudioBusProvider::AddAudioBusAsset(const FAssetData& InAssetData) { const bool bIsAudioBusAssetAlreadAdded = AudioBusDataViewEntries.ContainsByPredicate( [AssetName = InAssetData.GetObjectPathString()](const TSharedPtr& AudioBusAssetDashboardEntry) { return AudioBusAssetDashboardEntry->Name == AssetName; }); if (!bIsAudioBusAssetAlreadAdded) { const FAudioInsightsEditorModule AudioInsightsEditorModule = FAudioInsightsEditorModule::GetChecked(); const ::Audio::FDeviceId AudioDeviceId = AudioInsightsEditorModule.GetDeviceId(); TSharedPtr AudioBusAssetDashboardEntry = MakeShared(); AudioBusAssetDashboardEntry->DeviceId = AudioDeviceId; AudioBusAssetDashboardEntry->EntryType = EAudioBusEntryType::AssetBased; AudioBusAssetDashboardEntry->Name = InAssetData.GetObjectPathString(); AudioBusAssetDashboardEntry->AudioBus = Cast(InAssetData.GetAsset()); AudioBusAssetDashboardEntry->AudioBusId = AudioBusAssetDashboardEntry->AudioBus->GetUniqueID(); AudioBusDataViewEntries.Add(MoveTemp(AudioBusAssetDashboardEntry)); OnAudioBusAssetAdded.Broadcast(InAssetData.GetAsset()); bAssetEntriesNeedRefreshing = true; ++LastUpdateId; } } void FAudioBusProvider::RemoveAudioBusAsset(const FAssetData& InAssetData) { const int32 FoundAudioBusAssetNameIndex = AudioBusDataViewEntries.IndexOfByPredicate( [AssetName = InAssetData.GetObjectPathString()](const TSharedPtr& AudioBusAssetDashboardEntry) { return AudioBusAssetDashboardEntry->Name == AssetName; }); if (FoundAudioBusAssetNameIndex != INDEX_NONE) { const FAudioInsightsEditorModule AudioInsightsEditorModule = FAudioInsightsEditorModule::GetChecked(); const ::Audio::FDeviceId AudioDeviceId = AudioInsightsEditorModule.GetDeviceId(); RemoveDeviceEntry(AudioDeviceId, AudioBusDataViewEntries[FoundAudioBusAssetNameIndex]->AudioBus->GetUniqueID()); AudioBusDataViewEntries.RemoveAt(FoundAudioBusAssetNameIndex); OnAudioBusAssetRemoved.Broadcast(InAssetData.GetAsset()); bAssetEntriesNeedRefreshing = true; ++LastUpdateId; } } void FAudioBusProvider::UpdateAudioBusAssetNames() { // Get all UAudioBus assets TArray AssetDataArray; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); AssetRegistryModule.Get().GetAssetsByClass(FTopLevelAssetPath(UAudioBus::StaticClass()), AssetDataArray); // Build AudioBusDataViewEntries Reset(); AudioBusDataViewEntries.Empty(); for (const FAssetData& AssetData : AssetDataArray) { AddAudioBusAsset(AssetData); } AudioBusDataViewEntries.Sort([](const TSharedPtr& A, const TSharedPtr& B) { return A->GetDisplayName().CompareToCaseIgnored(B->GetDisplayName()) < 0; }); OnAudioBusAssetListUpdated.Broadcast(); } bool FAudioBusProvider::ProcessMessages() { // Helper lambdas auto BumpEntryFunc = [this](const FAudioBusMessageBase& Msg) { TSharedPtr* ToReturn = nullptr; UpdateDeviceEntry(Msg.DeviceId, Msg.AudioBusId, [&ToReturn, &Msg](TSharedPtr& Entry) { if (!Entry.IsValid()) { Entry = MakeShared(); Entry->DeviceId = Msg.DeviceId; Entry->AudioBusId = Msg.AudioBusId; } Entry->Timestamp = Msg.Timestamp; ToReturn = &Entry; }); return ToReturn; }; auto GetEntry = [this](const FAudioBusMessageBase& Msg) { return FindDeviceEntry(Msg.DeviceId, Msg.AudioBusId); }; // Process messages if (bAssetEntriesNeedRefreshing) { const FAudioInsightsEditorModule AudioInsightsEditorModule = FAudioInsightsEditorModule::GetChecked(); const ::Audio::FDeviceId AudioDeviceId = AudioInsightsEditorModule.GetDeviceId(); for (const TSharedPtr& AudioBusDataViewEntry : AudioBusDataViewEntries) { if (AudioBusDataViewEntry.IsValid() && AudioBusDataViewEntry->AudioBus.IsValid()) { UpdateDeviceEntry(AudioDeviceId, AudioBusDataViewEntry->AudioBusId, [&AudioBusDataViewEntry](TSharedPtr& Entry) { if (!Entry.IsValid()) { Entry = AudioBusDataViewEntry; } }); } } bAssetEntriesNeedRefreshing = false; } ProcessMessageQueue(TraceMessages.ActivateMessages, BumpEntryFunc, [](const FAudioBusActivateMessage& Msg, TSharedPtr* OutEntry) { FAudioBusAssetDashboardEntry& EntryRef = *OutEntry->Get(); if (EntryRef.Name.IsEmpty()) { EntryRef.Name = *Msg.Name; } if (EntryRef.EntryType == EAudioBusEntryType::None) { EntryRef.EntryType = FSoftObjectPath(EntryRef.Name).IsAsset() ? EAudioBusEntryType::AssetBased : EAudioBusEntryType::CodeGenerated; } }); ProcessMessageQueue(TraceMessages.DeactivateMessages, GetEntry, [this](const FAudioBusDeactivateMessage& Msg, TSharedPtr* OutEntry) { if (OutEntry && (*OutEntry)->Timestamp < Msg.Timestamp) { FAudioBusAssetDashboardEntry& EntryRef = *OutEntry->Get(); if (EntryRef.EntryType == EAudioBusEntryType::CodeGenerated) { RemoveDeviceEntry(Msg.DeviceId, Msg.AudioBusId); } } }); ProcessMessageQueue(TraceMessages.HasActivityMessages, GetEntry, [](const FAudioBusHasActivityMessage& Msg, TSharedPtr* OutEntry) { if (OutEntry) { FAudioBusAssetDashboardEntry& EntryRef = *OutEntry->Get(); if (!EntryRef.Name.IsEmpty() && EntryRef.EntryType != EAudioBusEntryType::None) { EntryRef.bHasActivity = Msg.bHasActivity; EntryRef.Timestamp = Msg.Timestamp; } } }); return true; } UE::Trace::IAnalyzer* FAudioBusProvider::ConstructAnalyzer(TraceServices::IAnalysisSession& InSession) { class FAudioBusTraceAnalyzer : public FTraceAnalyzerBase { public: FAudioBusTraceAnalyzer(TSharedRef InProvider, TraceServices::IAnalysisSession& InSession) : FTraceAnalyzerBase(InProvider) , Session(InSession) { } virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override { FTraceAnalyzerBase::OnAnalysisBegin(Context); UE::Trace::IAnalyzer::FInterfaceBuilder& Builder = Context.InterfaceBuilder; Builder.RouteEvent(RouteId_Activate, "Audio", "AudioBusActivate"); Builder.RouteEvent(RouteId_Deactivate, "Audio", "AudioBusDeactivate"); Builder.RouteEvent(RouteId_HasActivity, "Audio", "AudioBusHasActivity"); } virtual bool OnEvent(uint16 RouteId, EStyle Style, const FOnEventContext& Context) override { LLM_SCOPE_BYNAME(TEXT("Insights/FAudioBusTraceAnalyzer")); FAudioBusMessages& Messages = GetProvider().TraceMessages; switch (RouteId) { case RouteId_Activate: { Messages.ActivateMessages.Enqueue(FAudioBusActivateMessage { Context }); break; } case RouteId_Deactivate: { Messages.DeactivateMessages.Enqueue(FAudioBusDeactivateMessage{ Context }); break; } case RouteId_HasActivity: { Messages.HasActivityMessages.Enqueue(FAudioBusHasActivityMessage{ Context }); break; } default: { return OnEventFailure(RouteId, Style, Context); } } const double Timestamp = Context.EventTime.AsSeconds(Context.EventData.GetValue("Timestamp")); { TraceServices::FAnalysisSessionEditScope SessionEditScope(Session); Session.UpdateDurationSeconds(Timestamp); } return OnEventSuccess(RouteId, Style, Context); } private: enum : uint16 { RouteId_Activate, RouteId_Deactivate, RouteId_HasActivity }; TraceServices::IAnalysisSession& Session; }; bAssetEntriesNeedRefreshing = true; return new FAudioBusTraceAnalyzer(AsShared(), InSession); } } // namespace UE::Audio::Insights