aeroson

updated Manager.cs

Sep 8th, 2015
213
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.05 KB | None | 0 0
  1. #define UNITY_4_6_1f1 // uncomment this for older unity versions
  2.  
  3. using System;
  4. using System.IO;
  5. using System.Collections.Generic;
  6. using System.Threading;
  7. using System.Linq;
  8. using UnityEngine;
  9.  
  10. namespace RuntimeAudioClipLoader
  11. {
  12.  
  13. /// <summary>
  14. /// Facilitates loading of AudioClips at runtime from Stream or file path.
  15. /// Creates and hides one GameObject for it self on the first use.
  16. /// Supported formats are: waw, mp3, aiff, ogg.
  17. /// Supports streaming AudioClips and loading in background thread.
  18. /// Possible uses are:
  19. /// * support for modding as your players can easily replace sounds
  20. /// * less assets to be imported, thus faster build times.
  21. /// NAudio uses interop with Windows for some format conversions and thus properly works only on Windows.
  22. /// </summary>
  23. public class Manager : MonoBehaviour
  24. {
  25. static readonly string[] supportedFormats;
  26. static Manager()
  27. {
  28. supportedFormats = System.Enum.GetNames(typeof(AudioFormat));
  29. }
  30. class AudioInstance
  31. {
  32. public AudioClip audioClip;
  33. public CustomAudioFileReader reader;
  34. public float[] dataToSet;
  35. public int samplesCount;
  36. public Stream streamToDisposeOnceDone;
  37. public int channels
  38. {
  39. get
  40. {
  41. return reader.WaveFormat.Channels;
  42. }
  43. }
  44. public int sampleRate
  45. {
  46. get
  47. {
  48. return reader.WaveFormat.SampleRate;
  49. }
  50. }
  51. public static implicit operator AudioClip(AudioInstance ai)
  52. {
  53. return ai.audioClip;
  54. }
  55. }
  56.  
  57. static Dictionary<string, AudioClip> cache = new Dictionary<string, AudioClip>();
  58. static Queue<AudioInstance> deferredLoadQueue = new Queue<AudioInstance>();
  59. static Queue<AudioInstance> deferredSetDataQueue = new Queue<AudioInstance>();
  60. static Queue<AudioInstance> deferredSetFail = new Queue<AudioInstance>();
  61. static Thread deferredLoaderThread;
  62. static GameObject managerInstance;
  63. static Dictionary<AudioClip, AudioClipLoadType> audioClipLoadType = new Dictionary<AudioClip, AudioClipLoadType>();
  64. static Dictionary<AudioClip, AudioDataLoadState> audioLoadState = new Dictionary<AudioClip, AudioDataLoadState>();
  65.  
  66.  
  67.  
  68. /// <summary>
  69. /// Loads an AudioClip from file located at filePath.
  70. /// Supported formats: waw, mp3, aiff, ogg.
  71. /// </summary>
  72. /// <param name="filePath">filePath is used as Unity's name of the AudioClip and as a key used in the caching dictionary</param>
  73. /// <param name="useCache">audioClip will be cached, so it won't have to be loaded again next time</param>
  74. /// <param name="doStream">audioClip will be loaded on the fly on demand, use for long one time use clips</param>
  75. /// <param name="loadInBackground">if !doStream and loadInBackground then loading is done in own thread so it doesnt hang up caller's thread</param>
  76. /// <returns>null if error (i.e. unsupported format), may contain undefined data or only silence if clip is not yet loaded</returns>
  77. public static AudioClip Load(string filePath, bool useCache = true, bool doStream = false, bool loadInBackground = true
  78. #if UNITY_4_6_1f1
  79. , bool is3D = true
  80. #endif
  81. )
  82. {
  83. if (!IsSupportedFormat(filePath))
  84. {
  85. Debug.LogError(
  86. "Could not load AudioClip at path '" + filePath +
  87. "' it's extensions marks unsupported format, supported formats are: " +
  88. string.Join(", ", System.Enum.GetNames(typeof(AudioFormat))));
  89. return null;
  90. }
  91. AudioClip audioClip = null;
  92. if (useCache && cache.TryGetValue(filePath, out audioClip) && audioClip)
  93. {
  94. return audioClip;
  95. }
  96.  
  97. var dataStream = new StreamReader(filePath);
  98. audioClip = Load(dataStream.BaseStream, GetAudioFormat(filePath), filePath, doStream, loadInBackground
  99. #if UNITY_4_6_1f1
  100. , is3D
  101. #endif
  102. );
  103.  
  104. if (useCache)
  105. {
  106. cache[filePath] = audioClip;
  107. }
  108. return audioClip;
  109. }
  110.  
  111.  
  112. /// <summary>
  113. /// Loads an AudioClip from Stream, you need to manually supply AudioFormat.
  114. /// Use GetAudioFormat to get AudioFormat from filePath.
  115. /// Supported formats: waw, mp3, aiff, ogg.
  116. /// </summary>
  117. /// <param name="dataStream">Stream used to read the audio data</param>
  118. /// <param name="audioFormat">Use GetAudioFormat to get AudioFormat from filePath</param>
  119. /// <param name="unityAudioClipName">Used as Unity's name of the AudioClip</param>
  120. /// <param name="doStream">audioClip will be loaded on the fly on demand, use for long one time use clips</param>
  121. /// <param name="loadInBackground">if !doStream and loadInBackground then loading is done in own thread so it doesnt hang up caller's thread</param>
  122. /// <param name="diposeDataStreamIfNotNeeded">if !doStream will Close and Dispose the dataStream if/when it's no longer needed</param>
  123. /// <returns>null if error (i.e. unsupported format), may contain unknown data or only silence if clip is not yet loaded</returns>
  124. public static AudioClip Load(Stream dataStream, AudioFormat audioFormat, string unityAudioClipName, bool doStream = false, bool loadInBackground = true, bool diposeDataStreamIfNotNeeded = true
  125. #if UNITY_4_6_1f1
  126. , bool is3D = true
  127. #endif
  128. )
  129. {
  130. AudioClip audioClip = null;
  131. CustomAudioFileReader reader = null;
  132. try
  133. {
  134. reader = new CustomAudioFileReader(dataStream, audioFormat);
  135. AudioInstance audioInstance = new AudioInstance
  136. {
  137. reader = reader,
  138. samplesCount = (int)(reader.Length / (long)(reader.WaveFormat.BitsPerSample / 8))
  139. };
  140. if (doStream)
  141. {
  142. // tricky: lengthSamples is number of time units, one time units consists of samples for each channel
  143. #if UNITY_4_6_1f1
  144. audioClip = AudioClip.Create(unityAudioClipName, audioInstance.samplesCount / audioInstance.channels, audioInstance.channels, audioInstance.sampleRate, is3D, true, delegate (float[] target)
  145. #else
  146. audioClip = AudioClip.Create(unityAudioClipName, audioInstance.samplesCount / audioInstance.channels, audioInstance.channels, audioInstance.sampleRate, true, delegate (float[] target)
  147. #endif
  148. {
  149. reader.Read(target, 0, target.Length);
  150. }, delegate (int target)
  151. {
  152. reader.Seek((long)target, 0);
  153. });
  154. audioInstance.audioClip = audioClip;
  155. SetAudioClipLoadType(audioInstance, AudioClipLoadType.Streaming);
  156. SetAudioClipLoadState(audioInstance, AudioDataLoadState.Loaded);
  157. }
  158. else
  159. {
  160. #if UNITY_4_6_1f1
  161. audioClip = AudioClip.Create(unityAudioClipName, audioInstance.samplesCount / audioInstance.channels, audioInstance.channels, audioInstance.sampleRate, is3D, false);
  162. #else
  163. audioClip = AudioClip.Create(unityAudioClipName, audioInstance.samplesCount / audioInstance.channels, audioInstance.channels, audioInstance.sampleRate, false);
  164. #endif
  165. audioInstance.audioClip = audioClip;
  166. if (diposeDataStreamIfNotNeeded) audioInstance.streamToDisposeOnceDone = dataStream;
  167. SetAudioClipLoadType(audioInstance, AudioClipLoadType.DecompressOnLoad);
  168. SetAudioClipLoadState(audioInstance, AudioDataLoadState.Loading);
  169. if (loadInBackground)
  170. {
  171. lock (deferredLoadQueue)
  172. {
  173. deferredLoadQueue.Enqueue(audioInstance);
  174. }
  175. RunDeferredLoaderThread();
  176. EnsureInstanceExists();
  177. }
  178. else
  179. {
  180. audioInstance.dataToSet = new float[audioInstance.samplesCount];
  181. audioInstance.reader.Read(audioInstance.dataToSet, 0, audioInstance.dataToSet.Length);
  182. audioInstance.audioClip.SetData(audioInstance.dataToSet, 0);
  183. SetAudioClipLoadState(audioInstance, AudioDataLoadState.Loaded);
  184. }
  185. }
  186. }
  187. catch (Exception e)
  188. {
  189. SetAudioClipLoadState(audioClip, AudioDataLoadState.Failed);
  190. Debug.LogError("Could not load AudioClip named '" + unityAudioClipName + "', exception:" + e);
  191. }
  192. return audioClip;
  193. }
  194. static void RunDeferredLoaderThread()
  195. {
  196. if (deferredLoaderThread == null || !deferredLoaderThread.IsAlive)
  197. {
  198. deferredLoaderThread = new Thread(DeferredLoaderMain);
  199. deferredLoaderThread.IsBackground = true;
  200. deferredLoaderThread.Start();
  201. }
  202. }
  203. static void DeferredLoaderMain()
  204. {
  205. AudioInstance audioInstance = null;
  206. bool isSomethingInQueue = true;
  207. long delayedStopCounter = 100000;
  208. while (isSomethingInQueue || delayedStopCounter > 0)
  209. {
  210. delayedStopCounter -= 1;
  211. lock (deferredLoadQueue)
  212. {
  213. isSomethingInQueue = deferredLoadQueue.Count > 0;
  214. if (!isSomethingInQueue) continue;
  215. audioInstance = deferredLoadQueue.Dequeue();
  216. }
  217. delayedStopCounter = 100000;
  218.  
  219. try
  220. {
  221. audioInstance.dataToSet = new float[audioInstance.samplesCount];
  222. audioInstance.reader.Read(audioInstance.dataToSet, 0, audioInstance.dataToSet.Length);
  223.  
  224. audioInstance.reader.Close();
  225. audioInstance.reader.Dispose();
  226.  
  227. if (audioInstance.streamToDisposeOnceDone != null)
  228. {
  229. audioInstance.streamToDisposeOnceDone.Close();
  230. audioInstance.streamToDisposeOnceDone.Dispose();
  231. audioInstance.streamToDisposeOnceDone = null;
  232. }
  233.  
  234. lock (deferredSetDataQueue)
  235. {
  236. deferredSetDataQueue.Enqueue(audioInstance);
  237. }
  238. }
  239. catch (Exception e)
  240. {
  241. Debug.LogException(e);
  242. lock (deferredSetFail)
  243. {
  244. deferredSetFail.Enqueue(audioInstance); // because you can not access null comparison outside unity main thread, and dictionary asserts check if key is null
  245. }
  246. }
  247. }
  248. }
  249.  
  250. // AudioClip.SetData only possible in main Unity thread, thus we need to create one instance of our MonoBehaviour whose Update() will get called
  251. void Update()
  252. {
  253. AudioInstance audioInstance = null;
  254. bool isSomethingInQueue = true;
  255. while (isSomethingInQueue)
  256. {
  257. lock (deferredSetDataQueue)
  258. {
  259. isSomethingInQueue = deferredSetDataQueue.Count > 0;
  260. if (!isSomethingInQueue) break;
  261. audioInstance = deferredSetDataQueue.Dequeue();
  262. }
  263. audioInstance.audioClip.SetData(audioInstance.dataToSet, 0);
  264. SetAudioClipLoadState(audioInstance, AudioDataLoadState.Loaded);
  265.  
  266. audioInstance.audioClip = null;
  267. audioInstance.dataToSet = null;
  268. }
  269.  
  270.  
  271. lock (deferredSetFail)
  272. {
  273. while (deferredSetFail.Count > 0)
  274. {
  275. audioInstance = deferredSetFail.Dequeue();
  276. SetAudioClipLoadState(audioInstance, AudioDataLoadState.Failed);
  277. }
  278. }
  279.  
  280. }
  281.  
  282. static void EnsureInstanceExists()
  283. {
  284. if (!managerInstance)
  285. {
  286. managerInstance = new GameObject("Runtime AudioClip Loader Manger singleton instance");
  287. managerInstance.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideAndDontSave;
  288. managerInstance.AddComponent<Manager>();
  289. }
  290. }
  291.  
  292.  
  293.  
  294.  
  295.  
  296. /// <summary>
  297. /// Facilitates a user settable/gettable AudioClip.loadState
  298. /// </summary>
  299. public static void SetAudioClipLoadState(AudioClip audioClip, AudioDataLoadState newLoadState)
  300. {
  301. audioLoadState[audioClip] = newLoadState;
  302. }
  303.  
  304. /// <summary>
  305. /// Facilitates a user settable/gettable AudioClip.loadState
  306. /// </summary>
  307. /// <returns>AudioDataLoadState.Failed if audioClip is null or audioClip.loadState if this manager did not touch the audioClip</returns>
  308. public static AudioDataLoadState GetAudioClipLoadState(AudioClip audioClip)
  309. {
  310. AudioDataLoadState ret = AudioDataLoadState.Failed;
  311. #if UNITY_4_6_1f1
  312. audioLoadState.TryGetValue(audioClip, out ret);
  313. #else
  314. if (audioClip != null)
  315. {
  316. ret = audioClip.loadState;
  317. audioLoadState.TryGetValue(audioClip, out ret);
  318. }
  319. #endif
  320. return ret;
  321. }
  322.  
  323. /// <summary>
  324. /// Facilitates a user settable/gettable AudioClip.loadType
  325. /// </summary>
  326. public static void SetAudioClipLoadType(AudioClip audioClip, AudioClipLoadType newLoadType)
  327. {
  328. audioClipLoadType[audioClip] = newLoadType;
  329. }
  330.  
  331. /// <summary>
  332. /// Facilitates a user settable/gettable AudioClip.loadType
  333. /// </summary>
  334. /// <returns>-1 if audioClip is null or audioClip.loadType if this manager did not touch the audioClip</returns>
  335. public static AudioClipLoadType GetAudioClipLoadType(AudioClip audioClip)
  336. {
  337. AudioClipLoadType ret = (AudioClipLoadType)(-1);
  338. #if UNITY_4_6_1f1
  339. audioClipLoadType.TryGetValue(audioClip, out ret);
  340. #else
  341. if (audioClip != null)
  342. {
  343. ret = audioClip.loadType;
  344. audioClipLoadType.TryGetValue(audioClip, out ret);
  345. }
  346. #endif
  347. return ret;
  348. }
  349.  
  350.  
  351. static string GetExtension(string filePath)
  352. {
  353. return System.IO.Path.GetExtension(filePath).Substring(1).ToLower();
  354. }
  355.  
  356. /// <summary>
  357. /// Checks if filePath constains file with supported extension
  358. /// </summary>
  359. /// <returns>true if extension is supported, false if not</returns>
  360. public static bool IsSupportedFormat(string filePath)
  361. {
  362. return supportedFormats.Contains(GetExtension(filePath));
  363. }
  364.  
  365. /// <summary>
  366. /// Returns AudioFormat enum from filePath extension
  367. /// </summary>
  368. /// <returns>AudioFormat if filePath extension is supported format, -1 otherwise</returns>
  369. public static AudioFormat GetAudioFormat(string filePath)
  370. {
  371. AudioFormat ret = AudioFormat.unknown;
  372. try
  373. {
  374. ret = (AudioFormat)System.Enum.Parse(typeof(AudioFormat), GetExtension(filePath), true);
  375. }
  376. catch
  377. {
  378.  
  379. }
  380. return ret;
  381. }
  382.  
  383. /// <summary>
  384. /// Clears all cached AudioClips
  385. /// </summary>
  386. public static void ClearCache()
  387. {
  388. cache.Clear();
  389. }
  390. }
  391.  
  392.  
  393. #if UNITY_4_6_1f1
  394. //
  395. // Summary:
  396. // ///
  397. // Determines how the audio clip is loaded in.
  398. // ///
  399. public enum AudioClipLoadType
  400. {
  401. //
  402. // Summary:
  403. // ///
  404. // The audio data is decompressed when the audio clip is loaded.
  405. // ///
  406. DecompressOnLoad = 0,
  407. //
  408. // Summary:
  409. // ///
  410. // The audio data of the clip will be kept in memory in compressed form.
  411. // ///
  412. CompressedInMemory = 1,
  413. //
  414. // Summary:
  415. // ///
  416. // Streams audio data from disk.
  417. // ///
  418. Streaming = 2
  419. }
  420.  
  421.  
  422. //
  423. // Summary:
  424. // ///
  425. // Value describing the current load state of the audio data associated with an
  426. // AudioClip.
  427. // ///
  428. public enum AudioDataLoadState
  429. {
  430. //
  431. // Summary:
  432. // ///
  433. // Value returned by AudioClip.loadState for an AudioClip that has no audio data
  434. // loaded and where loading has not been initiated yet.
  435. // ///
  436. Unloaded = 0,
  437. //
  438. // Summary:
  439. // ///
  440. // Value returned by AudioClip.loadState for an AudioClip that is currently loading
  441. // audio data.
  442. // ///
  443. Loading = 1,
  444. //
  445. // Summary:
  446. // ///
  447. // Value returned by AudioClip.loadState for an AudioClip that has succeeded loading
  448. // its audio data.
  449. // ///
  450. Loaded = 2,
  451. //
  452. // Summary:
  453. // ///
  454. // Value returned by AudioClip.loadState for an AudioClip that has failed loading
  455. // its audio data.
  456. // ///
  457. Failed = 3
  458. }
  459. #endif
  460.  
  461. }
Advertisement
Add Comment
Please, Sign In to add comment