AkSoundbankGenerationManager.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. /*******************************************************************************
  2. The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
  3. Technology released in source code form as part of the game integration package.
  4. The content of this file may not be used without valid licenses to the
  5. AUDIOKINETIC Wwise Technology.
  6. Note that the use of the game engine is subject to the Unreal(R) Engine End User
  7. License Agreement at https://www.unrealengine.com/en-US/eula/unreal
  8. License Usage
  9. Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
  10. this file in accordance with the end user license agreement provided with the
  11. software or, alternatively, in accordance with the terms contained
  12. in a written agreement between you and Audiokinetic Inc.
  13. Copyright (c) 2023 Audiokinetic Inc.
  14. *******************************************************************************/
  15. #include "AkSoundBankGenerationManager.h"
  16. #include "AkAudioBankGenerationHelpers.h"
  17. #include "AkAudioDevice.h"
  18. #include "AkAudioStyle.h"
  19. #include "AkWaapiClient.h"
  20. #include "AkWaapiUtils.h"
  21. #include "IAudiokineticTools.h"
  22. #include "Async/Async.h"
  23. #include "Framework/Docking/TabManager.h"
  24. #include "Framework/Notifications/NotificationManager.h"
  25. #if UE_5_0_OR_LATER
  26. #include "HAL/PlatformFileManager.h"
  27. #else
  28. #include "HAL/PlatformFilemanager.h"
  29. #endif
  30. #include "AkAudioModule.h"
  31. #include "Misc/ScopeExit.h"
  32. #include "Internationalization/Text.h"
  33. #include "Wwise/WwiseProjectDatabase.h"
  34. #define LOCTEXT_NAMESPACE "AkAudio"
  35. DECLARE_CYCLE_STAT(TEXT("AkSoundData - Waapi Call"), STAT_WaapiCall, STATGROUP_AkSoundBankGenerationSource);
  36. AkSoundBankGenerationManager::AkSoundBankGenerationManager(const FInitParameters& InitParameters)
  37. : InitParameters(InitParameters)
  38. {
  39. PlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
  40. }
  41. AkSoundBankGenerationManager::~AkSoundBankGenerationManager()
  42. {
  43. }
  44. void AkSoundBankGenerationManager::SetIsBuilding(bool bIsBuilding)
  45. {
  46. bIsBuildingData = bIsBuilding;
  47. }
  48. void AkSoundBankGenerationManager::Init()
  49. {
  50. }
  51. void AkSoundBankGenerationManager::DoGeneration()
  52. {
  53. if (bIsBuildingData)
  54. {
  55. Notify(TEXT("SoundBankGenerationAborted"),
  56. TEXT("Wwise SoundBank generation aborted, there is already a generation task in progress!"),
  57. TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"),
  58. true);
  59. return;
  60. }
  61. SetIsBuilding(true);
  62. StartTime = FPlatformTime::Cycles64();
  63. if (InitParameters.GenerationMode != ESoundBankGenerationMode::Commandlet)
  64. {
  65. CreateNotificationItem();
  66. }
  67. bool bGenerationSuccess = false;
  68. switch (InitParameters.GenerationMode)
  69. {
  70. #if AK_SUPPORT_WAAPI
  71. case ESoundBankGenerationMode::WAAPI:
  72. bGenerationSuccess = WAAPIGenerate();
  73. break;
  74. #endif
  75. case ESoundBankGenerationMode::WwiseConsole:
  76. case ESoundBankGenerationMode::Commandlet:
  77. default:
  78. bGenerationSuccess = WwiseConsoleGenerate();
  79. break;
  80. }
  81. WrapUpGeneration(bGenerationSuccess, TEXT("WwiseConsole"));
  82. }
  83. void AkSoundBankGenerationManager::WrapUpGeneration(const bool bSuccess, const FString& BuilderName)
  84. {
  85. SetIsBuilding(false);
  86. FString SuccessMessage;
  87. if(!bSuccess)
  88. {
  89. SuccessMessage = BuilderName + TEXT(" Sound Data Builder task failed");
  90. NotifyGenerationFailed();
  91. }
  92. else
  93. {
  94. SuccessMessage = BuilderName + TEXT(" Sound Data Builder task was successful");
  95. NotifyGenerationSucceeded();
  96. }
  97. auto EndTime = FPlatformTime::Cycles64();
  98. UE_LOG(LogAudiokineticTools, Display, TEXT("%s and took %f seconds."), *SuccessMessage,
  99. FPlatformTime::ToSeconds64(EndTime - StartTime));
  100. FWwiseProjectDatabase* ProjectDatabase = FWwiseProjectDatabase::Get();
  101. if(UNLIKELY(!ProjectDatabase))
  102. {
  103. return;
  104. }
  105. FAkAudioModule::AkAudioModuleInstance->UpdateWwiseResourceLoaderSettings();
  106. ProjectDatabase->UpdateDataStructure();
  107. }
  108. void AkSoundBankGenerationManager::CreateNotificationItem()
  109. {
  110. if (InitParameters.GenerationMode != ESoundBankGenerationMode::Commandlet)
  111. {
  112. AsyncTask(ENamedThreads::Type::GameThread, [sharedThis = SharedThis(this)]
  113. {
  114. FNotificationInfo Info(LOCTEXT("GeneratingSoundBanks", "Generating Wwise SoundBanks..."));
  115. Info.Image = FAkAudioStyle::GetBrush(TEXT("AudiokineticTools.AkBrowserTabIcon"));
  116. Info.bFireAndForget = false;
  117. Info.FadeOutDuration = 0.0f;
  118. Info.ExpireDuration = 0.0f;
  119. #if UE_4_26_OR_LATER
  120. Info.Hyperlink = FSimpleDelegate::CreateLambda([]() { FGlobalTabmanager::Get()->TryInvokeTab(FName("OutputLog")); });
  121. #else
  122. Info.Hyperlink = FSimpleDelegate::CreateLambda([]() { FGlobalTabmanager::Get()->InvokeTab(FName("OutputLog")); });
  123. #endif
  124. Info.HyperlinkText = LOCTEXT("ShowOutputLogHyperlink", "Show Output Log");
  125. sharedThis->NotificationItem = FSlateNotificationManager::Get().AddNotification(Info);
  126. });
  127. }
  128. }
  129. void AkSoundBankGenerationManager::Notify(const FString& key, const FString& message, const FString& AudioCuePath, bool bSuccess)
  130. {
  131. if (InitParameters.GenerationMode != ESoundBankGenerationMode::Commandlet)
  132. {
  133. AsyncTask(ENamedThreads::Type::GameThread, [sharedThis = SharedThis(this), key, message, AudioCuePath, bSuccess]
  134. {
  135. if (sharedThis->NotificationItem)
  136. {
  137. const FTextId TextId(TEXT(LOCTEXT_NAMESPACE), key);
  138. FText LocText = FText::ChangeKey(TextId.GetNamespace(), TextId.GetKey(), FText::FromString(message));
  139. sharedThis->NotificationItem->SetText(LocText);
  140. sharedThis->NotificationItem->SetCompletionState(
  141. bSuccess ? SNotificationItem::CS_Success : SNotificationItem::CS_Fail);
  142. sharedThis->NotificationItem->SetExpireDuration(3.5f);
  143. sharedThis->NotificationItem->SetFadeOutDuration(0.5f);
  144. sharedThis->NotificationItem->ExpireAndFadeout();
  145. }
  146. GEditor->PlayEditorSound(AudioCuePath);
  147. });
  148. }
  149. }
  150. void AkSoundBankGenerationManager::NotifyGenerationSucceeded()
  151. {
  152. Notify(TEXT("SoundBankGenerationCompleted"),
  153. TEXT("Wwise SoundBank generation completed!"),
  154. TEXT("/Engine/EditorSounds/Notifications/CompileSuccess_Cue.CompileSuccess_Cue"),
  155. true);
  156. }
  157. void AkSoundBankGenerationManager::NotifyGenerationFailed()
  158. {
  159. Notify(TEXT("SoundBankGenerationFailed"),
  160. TEXT("Generating Wwise SoundBanks failed!"),
  161. TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"),
  162. false);
  163. }
  164. void AkSoundBankGenerationManager::NotifyProfilingInProgress()
  165. {
  166. Notify(TEXT("SoundBankGenerationProfiling"),
  167. TEXT("Cannot generate SoundBanks while Authoring is profiling."),
  168. TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"),
  169. false);
  170. }
  171. void AkSoundBankGenerationManager::NotifyAuthoringUnavailable()
  172. {
  173. Notify(
  174. TEXT("SoundBankGenerationAuthoringLocked"),
  175. TEXT("Cannot generate SoundBanks while Authoring is in its current state."),
  176. TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"),
  177. false);
  178. }
  179. bool AkSoundBankGenerationManager::WwiseConsoleGenerate()
  180. {
  181. FString WwiseConsoleCommand = OverrideWwiseConsolePath.IsEmpty() ? AkAudioBankGenerationHelper::GetWwiseConsoleApplicationPath() : OverrideWwiseConsolePath;
  182. FString WwiseConsoleArguments;
  183. #if PLATFORM_MAC
  184. WwiseConsoleArguments = WwiseConsoleCommand + TEXT(" ");
  185. WwiseConsoleCommand = TEXT("/bin/sh");
  186. #endif
  187. WwiseConsoleArguments += FString::Printf(TEXT("generate-soundbank \"%s\" --use-stable-guid "),
  188. *PlatformFile->ConvertToAbsolutePathForExternalAppForWrite(*WwiseUnrealHelper::GetWwiseProjectPath()));
  189. auto GeneratedSoundBanksPath = WwiseUnrealHelper::GetSoundBankDirectory();
  190. if (InitParameters.Platforms.Num() > 0)
  191. {
  192. WwiseConsoleArguments += FString::Printf(TEXT(" --platform"));
  193. for (auto& Platform : InitParameters.Platforms)
  194. {
  195. WwiseConsoleArguments += FString::Printf(TEXT(" \"%s\""), *Platform);
  196. }
  197. }
  198. if (InitParameters.SkipLanguages)
  199. {
  200. WwiseConsoleArguments += TEXT(" --skip-languages");
  201. }
  202. else
  203. {
  204. if (InitParameters.Languages.Num() > 0)
  205. {
  206. WwiseConsoleArguments += FString::Printf(TEXT(" --language"));
  207. for (auto& Language : InitParameters.Languages)
  208. {
  209. WwiseConsoleArguments += FString::Printf(TEXT(" \"%s\""), *Language);
  210. }
  211. }
  212. }
  213. return RunWwiseConsole(WwiseConsoleCommand, WwiseConsoleArguments);
  214. }
  215. bool AkSoundBankGenerationManager::RunWwiseConsole(const FString& WwiseConsoleCommand,const FString& WwiseConsoleArguments)
  216. {
  217. UE_LOG(LogAudiokineticTools, Display, TEXT("Running WwiseConsole command : %s %s"), *WwiseConsoleCommand, *WwiseConsoleArguments);
  218. bool bGenerationSuccess = true;
  219. // Create a pipe for the child process's STDOUT.
  220. int32 ReturnCode = 0;
  221. void* WritePipe = nullptr;
  222. void* ReadPipe = nullptr;
  223. FPlatformProcess::CreatePipe(ReadPipe, WritePipe);
  224. ON_SCOPE_EXIT{
  225. FPlatformProcess::ClosePipe(ReadPipe, WritePipe);
  226. };
  227. FProcHandle ProcHandle = FPlatformProcess::CreateProc(*WwiseConsoleCommand, *WwiseConsoleArguments, true, true, true, nullptr, 0, nullptr, WritePipe);
  228. if (ProcHandle.IsValid())
  229. {
  230. FString NewLine;
  231. FPlatformProcess::Sleep(0.1f);
  232. // Wait for it to finish and get return code
  233. while (FPlatformProcess::IsProcRunning(ProcHandle))
  234. {
  235. NewLine = FPlatformProcess::ReadPipe(ReadPipe);
  236. if (NewLine.Len() > 0)
  237. {
  238. UE_LOG(LogAudiokineticTools, Display, TEXT("%s"), *NewLine);
  239. NewLine.Empty();
  240. }
  241. FPlatformProcess::Sleep(0.25f);
  242. }
  243. NewLine = FPlatformProcess::ReadPipe(ReadPipe);
  244. if (NewLine.Len() > 0)
  245. {
  246. UE_LOG(LogAudiokineticTools, Display, TEXT("%s"), *NewLine);
  247. }
  248. FPlatformProcess::GetProcReturnCode(ProcHandle, &ReturnCode);
  249. switch (ReturnCode)
  250. {
  251. case 0:
  252. UE_LOG(LogAudiokineticTools, Display, TEXT("WwiseConsole successfully completed."));
  253. break;
  254. case 2:
  255. UE_LOG(LogAudiokineticTools, Warning, TEXT("WwiseConsole completed with warnings :\n %s %s"), *WwiseConsoleCommand, *WwiseConsoleArguments);
  256. break;
  257. default:
  258. UE_LOG(LogAudiokineticTools, Error, TEXT("WwiseConsole failed with error %d :\n %s %s"), ReturnCode, *WwiseConsoleCommand, *WwiseConsoleArguments);
  259. bGenerationSuccess = false;
  260. break;
  261. }
  262. }
  263. else
  264. {
  265. bGenerationSuccess = false;
  266. ReturnCode = -1;
  267. // Most chances are the path to the .exe or the project were not set properly in GEditorIni file.
  268. UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to run WwiseConsole:\n %s %s"), *WwiseConsoleCommand, *WwiseConsoleArguments);
  269. }
  270. return bGenerationSuccess;
  271. }
  272. #if AK_SUPPORT_WAAPI
  273. bool AkSoundBankGenerationManager::WAAPIGenerate()
  274. {
  275. if (!SubscribeToGenerationDone())
  276. {
  277. return false;
  278. }
  279. ON_SCOPE_EXIT
  280. {
  281. CleanupWaapiSubscriptions();
  282. };
  283. TSharedRef<FJsonObject> args = MakeShared<FJsonObject>();
  284. TSharedRef<FJsonObject> options = MakeShared<FJsonObject>();
  285. TSharedPtr<FJsonObject> result;
  286. if (FAkWaapiClient::Get()->Call(ak::wwise::core::remote::getConnectionStatus, args, options, result, -1))
  287. {
  288. bool isConnected = false;
  289. if (result->TryGetBoolField(WwiseWaapiHelper::IS_CONNECTED, isConnected) && isConnected)
  290. {
  291. NotifyAuthoringUnavailable();
  292. return false;
  293. }
  294. }
  295. TArray<TSharedPtr<FJsonValue>> platformJsonArray;
  296. for (auto& platform : InitParameters.Platforms)
  297. {
  298. platformJsonArray.Add(MakeShared<FJsonValueString>(platform));
  299. }
  300. args->SetArrayField(WwiseWaapiHelper::PLATFORMS, platformJsonArray);
  301. if (InitParameters.Languages.Num() > 0)
  302. {
  303. TArray<TSharedPtr<FJsonValue>> LanguageJsonArray;
  304. for (auto& Language : InitParameters.Languages)
  305. {
  306. LanguageJsonArray.Add(MakeShared<FJsonValueString>(Language));
  307. }
  308. args->SetArrayField(WwiseWaapiHelper::LANGUAGES, LanguageJsonArray);
  309. }
  310. args->SetBoolField(WwiseWaapiHelper::SKIP_LANGUAGES, InitParameters.SkipLanguages);
  311. args->SetBoolField(WwiseWaapiHelper::WRITE_TO_DISK, true);
  312. // do we always want to rebuild init bank now?
  313. args->SetBoolField(WwiseWaapiHelper::REBUILD_INIT_BANK, true);
  314. bool WaapiCallSuccess = false;
  315. {
  316. SCOPE_CYCLE_COUNTER(STAT_WaapiCall);
  317. WaapiCallSuccess = FAkWaapiClient::Get()->Call(ak::wwise::core::soundbank::generate, args, options, result, -1);
  318. if (!WaapiCallSuccess)
  319. {
  320. auto Message = result->GetStringField(WwiseWaapiHelper::MESSSAGE);
  321. UE_LOG(LogAkAudio, Error, TEXT("WAAPI Sound Data generation failed: %s"), *Message);
  322. }
  323. }
  324. if (WaapiCallSuccess)
  325. {
  326. WaitForGenerationDoneEvent->Wait();
  327. }
  328. return WaapiGenerationSuccess;
  329. }
  330. bool AkSoundBankGenerationManager::SubscribeToGenerationDone()
  331. {
  332. TSharedPtr<FJsonObject> Result;
  333. TSharedRef<FJsonObject> DoneOptions = MakeShared<FJsonObject>();
  334. auto SoundBankGenerationDoneCallback = WampEventCallback::CreateRaw(this, &AkSoundBankGenerationManager::OnSoundBankGenerationDone);
  335. FAkWaapiClient::Get()->Subscribe(ak::wwise::core::soundbank::generationDone, DoneOptions, SoundBankGenerationDoneCallback, GenerationDoneSubscriptionId, Result);
  336. WaitForGenerationDoneEvent = FGenericPlatformProcess::GetSynchEventFromPool();
  337. return GenerationDoneSubscriptionId != 0;
  338. }
  339. void AkSoundBankGenerationManager::CleanupWaapiSubscriptions()
  340. {
  341. TSharedPtr<FJsonObject> result;
  342. FAkWaapiClient::Get()->Unsubscribe(GenerationDoneSubscriptionId, result);
  343. FGenericPlatformProcess::ReturnSynchEventToPool(WaitForGenerationDoneEvent);
  344. }
  345. void AkSoundBankGenerationManager::OnSoundBankGenerationDone(uint64_t id, TSharedPtr<FJsonObject> responseJson)
  346. {
  347. const TArray<TSharedPtr<FJsonValue>>* logs = nullptr;
  348. if (responseJson->TryGetArrayField(TEXT("logs"), logs))
  349. {
  350. for (auto& entry : *logs)
  351. {
  352. const TSharedPtr<FJsonObject>* jsonEntry = nullptr;
  353. if (entry->TryGetObject(jsonEntry))
  354. {
  355. const auto severity = jsonEntry->Get()->GetStringField(TEXT("severity"));
  356. const auto message = jsonEntry->Get()->GetStringField(WwiseWaapiHelper::MESSSAGE);
  357. FString platform = "";
  358. const TSharedPtr<FJsonObject>* jsonPlatform = nullptr;
  359. if (jsonEntry->Get()->TryGetObjectField(TEXT("platform"), jsonPlatform))
  360. {
  361. jsonPlatform->Get()->TryGetStringField(WwiseWaapiHelper::NAME, platform);
  362. }
  363. if (severity == TEXT("Message"))
  364. {
  365. UE_LOG(LogAudiokineticTools, Display, TEXT("%s: %s"), *platform, *message);
  366. }
  367. else if (severity == TEXT("Warning"))
  368. {
  369. UE_LOG(LogAudiokineticTools, Warning, TEXT("%s: %s"), *platform, *message);
  370. }
  371. else if (severity == TEXT("Error") || severity == TEXT("Fatal Error"))
  372. {
  373. WaapiGenerationSuccess = false;
  374. UE_LOG(LogAudiokineticTools, Error, TEXT("%s: %s"), *platform, *message);
  375. }
  376. }
  377. }
  378. }
  379. else
  380. {
  381. FString outError;
  382. if (responseJson->TryGetStringField(TEXT("error"), outError))
  383. {
  384. WaapiGenerationSuccess = false;
  385. UE_LOG(LogAudiokineticTools, Error, TEXT("%s"), *outError);
  386. }
  387. }
  388. WaitForGenerationDoneEvent->Trigger();
  389. }
  390. #endif
  391. #undef LOCTEXT_NAMESPACE