AkSoundbankGenerationManager.cpp 14 KB

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