Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #define UNITY_4_6_1f1 // uncomment this for older unity versions
- using System;
- using System.IO;
- using System.Collections.Generic;
- using System.Threading;
- using System.Linq;
- using UnityEngine;
- namespace RuntimeAudioClipLoader
- {
- /// <summary>
- /// Facilitates loading of AudioClips at runtime from Stream or file path.
- /// Creates and hides one GameObject for it self on the first use.
- /// Supported formats are: waw, mp3, aiff, ogg.
- /// Supports streaming AudioClips and loading in background thread.
- /// Possible uses are:
- /// * support for modding as your players can easily replace sounds
- /// * less assets to be imported, thus faster build times.
- /// NAudio uses interop with Windows for some format conversions and thus properly works only on Windows.
- /// </summary>
- public class Manager : MonoBehaviour
- {
- static readonly string[] supportedFormats;
- static Manager()
- {
- supportedFormats = System.Enum.GetNames(typeof(AudioFormat));
- }
- class AudioInstance
- {
- public AudioClip audioClip;
- public CustomAudioFileReader reader;
- public float[] dataToSet;
- public int samplesCount;
- public Stream streamToDisposeOnceDone;
- public int channels
- {
- get
- {
- return reader.WaveFormat.Channels;
- }
- }
- public int sampleRate
- {
- get
- {
- return reader.WaveFormat.SampleRate;
- }
- }
- public static implicit operator AudioClip(AudioInstance ai)
- {
- return ai.audioClip;
- }
- }
- static Dictionary<string, AudioClip> cache = new Dictionary<string, AudioClip>();
- static Queue<AudioInstance> deferredLoadQueue = new Queue<AudioInstance>();
- static Queue<AudioInstance> deferredSetDataQueue = new Queue<AudioInstance>();
- static Queue<AudioInstance> deferredSetFail = new Queue<AudioInstance>();
- static Thread deferredLoaderThread;
- static GameObject managerInstance;
- static Dictionary<AudioClip, AudioClipLoadType> audioClipLoadType = new Dictionary<AudioClip, AudioClipLoadType>();
- static Dictionary<AudioClip, AudioDataLoadState> audioLoadState = new Dictionary<AudioClip, AudioDataLoadState>();
- /// <summary>
- /// Loads an AudioClip from file located at filePath.
- /// Supported formats: waw, mp3, aiff, ogg.
- /// </summary>
- /// <param name="filePath">filePath is used as Unity's name of the AudioClip and as a key used in the caching dictionary</param>
- /// <param name="useCache">audioClip will be cached, so it won't have to be loaded again next time</param>
- /// <param name="doStream">audioClip will be loaded on the fly on demand, use for long one time use clips</param>
- /// <param name="loadInBackground">if !doStream and loadInBackground then loading is done in own thread so it doesnt hang up caller's thread</param>
- /// <returns>null if error (i.e. unsupported format), may contain undefined data or only silence if clip is not yet loaded</returns>
- public static AudioClip Load(string filePath, bool useCache = true, bool doStream = false, bool loadInBackground = true
- #if UNITY_4_6_1f1
- , bool is3D = true
- #endif
- )
- {
- if (!IsSupportedFormat(filePath))
- {
- Debug.LogError(
- "Could not load AudioClip at path '" + filePath +
- "' it's extensions marks unsupported format, supported formats are: " +
- string.Join(", ", System.Enum.GetNames(typeof(AudioFormat))));
- return null;
- }
- AudioClip audioClip = null;
- if (useCache && cache.TryGetValue(filePath, out audioClip) && audioClip)
- {
- return audioClip;
- }
- var dataStream = new StreamReader(filePath);
- audioClip = Load(dataStream.BaseStream, GetAudioFormat(filePath), filePath, doStream, loadInBackground
- #if UNITY_4_6_1f1
- , is3D
- #endif
- );
- if (useCache)
- {
- cache[filePath] = audioClip;
- }
- return audioClip;
- }
- /// <summary>
- /// Loads an AudioClip from Stream, you need to manually supply AudioFormat.
- /// Use GetAudioFormat to get AudioFormat from filePath.
- /// Supported formats: waw, mp3, aiff, ogg.
- /// </summary>
- /// <param name="dataStream">Stream used to read the audio data</param>
- /// <param name="audioFormat">Use GetAudioFormat to get AudioFormat from filePath</param>
- /// <param name="unityAudioClipName">Used as Unity's name of the AudioClip</param>
- /// <param name="doStream">audioClip will be loaded on the fly on demand, use for long one time use clips</param>
- /// <param name="loadInBackground">if !doStream and loadInBackground then loading is done in own thread so it doesnt hang up caller's thread</param>
- /// <param name="diposeDataStreamIfNotNeeded">if !doStream will Close and Dispose the dataStream if/when it's no longer needed</param>
- /// <returns>null if error (i.e. unsupported format), may contain unknown data or only silence if clip is not yet loaded</returns>
- public static AudioClip Load(Stream dataStream, AudioFormat audioFormat, string unityAudioClipName, bool doStream = false, bool loadInBackground = true, bool diposeDataStreamIfNotNeeded = true
- #if UNITY_4_6_1f1
- , bool is3D = true
- #endif
- )
- {
- AudioClip audioClip = null;
- CustomAudioFileReader reader = null;
- try
- {
- reader = new CustomAudioFileReader(dataStream, audioFormat);
- AudioInstance audioInstance = new AudioInstance
- {
- reader = reader,
- samplesCount = (int)(reader.Length / (long)(reader.WaveFormat.BitsPerSample / 8))
- };
- if (doStream)
- {
- // tricky: lengthSamples is number of time units, one time units consists of samples for each channel
- #if UNITY_4_6_1f1
- audioClip = AudioClip.Create(unityAudioClipName, audioInstance.samplesCount / audioInstance.channels, audioInstance.channels, audioInstance.sampleRate, is3D, true, delegate (float[] target)
- #else
- audioClip = AudioClip.Create(unityAudioClipName, audioInstance.samplesCount / audioInstance.channels, audioInstance.channels, audioInstance.sampleRate, true, delegate (float[] target)
- #endif
- {
- reader.Read(target, 0, target.Length);
- }, delegate (int target)
- {
- reader.Seek((long)target, 0);
- });
- audioInstance.audioClip = audioClip;
- SetAudioClipLoadType(audioInstance, AudioClipLoadType.Streaming);
- SetAudioClipLoadState(audioInstance, AudioDataLoadState.Loaded);
- }
- else
- {
- #if UNITY_4_6_1f1
- audioClip = AudioClip.Create(unityAudioClipName, audioInstance.samplesCount / audioInstance.channels, audioInstance.channels, audioInstance.sampleRate, is3D, false);
- #else
- audioClip = AudioClip.Create(unityAudioClipName, audioInstance.samplesCount / audioInstance.channels, audioInstance.channels, audioInstance.sampleRate, false);
- #endif
- audioInstance.audioClip = audioClip;
- if (diposeDataStreamIfNotNeeded) audioInstance.streamToDisposeOnceDone = dataStream;
- SetAudioClipLoadType(audioInstance, AudioClipLoadType.DecompressOnLoad);
- SetAudioClipLoadState(audioInstance, AudioDataLoadState.Loading);
- if (loadInBackground)
- {
- lock (deferredLoadQueue)
- {
- deferredLoadQueue.Enqueue(audioInstance);
- }
- RunDeferredLoaderThread();
- EnsureInstanceExists();
- }
- else
- {
- audioInstance.dataToSet = new float[audioInstance.samplesCount];
- audioInstance.reader.Read(audioInstance.dataToSet, 0, audioInstance.dataToSet.Length);
- audioInstance.audioClip.SetData(audioInstance.dataToSet, 0);
- SetAudioClipLoadState(audioInstance, AudioDataLoadState.Loaded);
- }
- }
- }
- catch (Exception e)
- {
- SetAudioClipLoadState(audioClip, AudioDataLoadState.Failed);
- Debug.LogError("Could not load AudioClip named '" + unityAudioClipName + "', exception:" + e);
- }
- return audioClip;
- }
- static void RunDeferredLoaderThread()
- {
- if (deferredLoaderThread == null || !deferredLoaderThread.IsAlive)
- {
- deferredLoaderThread = new Thread(DeferredLoaderMain);
- deferredLoaderThread.IsBackground = true;
- deferredLoaderThread.Start();
- }
- }
- static void DeferredLoaderMain()
- {
- AudioInstance audioInstance = null;
- bool isSomethingInQueue = true;
- long delayedStopCounter = 100000;
- while (isSomethingInQueue || delayedStopCounter > 0)
- {
- delayedStopCounter -= 1;
- lock (deferredLoadQueue)
- {
- isSomethingInQueue = deferredLoadQueue.Count > 0;
- if (!isSomethingInQueue) continue;
- audioInstance = deferredLoadQueue.Dequeue();
- }
- delayedStopCounter = 100000;
- try
- {
- audioInstance.dataToSet = new float[audioInstance.samplesCount];
- audioInstance.reader.Read(audioInstance.dataToSet, 0, audioInstance.dataToSet.Length);
- audioInstance.reader.Close();
- audioInstance.reader.Dispose();
- if (audioInstance.streamToDisposeOnceDone != null)
- {
- audioInstance.streamToDisposeOnceDone.Close();
- audioInstance.streamToDisposeOnceDone.Dispose();
- audioInstance.streamToDisposeOnceDone = null;
- }
- lock (deferredSetDataQueue)
- {
- deferredSetDataQueue.Enqueue(audioInstance);
- }
- }
- catch (Exception e)
- {
- Debug.LogException(e);
- lock (deferredSetFail)
- {
- deferredSetFail.Enqueue(audioInstance); // because you can not access null comparison outside unity main thread, and dictionary asserts check if key is null
- }
- }
- }
- }
- // AudioClip.SetData only possible in main Unity thread, thus we need to create one instance of our MonoBehaviour whose Update() will get called
- void Update()
- {
- AudioInstance audioInstance = null;
- bool isSomethingInQueue = true;
- while (isSomethingInQueue)
- {
- lock (deferredSetDataQueue)
- {
- isSomethingInQueue = deferredSetDataQueue.Count > 0;
- if (!isSomethingInQueue) break;
- audioInstance = deferredSetDataQueue.Dequeue();
- }
- audioInstance.audioClip.SetData(audioInstance.dataToSet, 0);
- SetAudioClipLoadState(audioInstance, AudioDataLoadState.Loaded);
- audioInstance.audioClip = null;
- audioInstance.dataToSet = null;
- }
- lock (deferredSetFail)
- {
- while (deferredSetFail.Count > 0)
- {
- audioInstance = deferredSetFail.Dequeue();
- SetAudioClipLoadState(audioInstance, AudioDataLoadState.Failed);
- }
- }
- }
- static void EnsureInstanceExists()
- {
- if (!managerInstance)
- {
- managerInstance = new GameObject("Runtime AudioClip Loader Manger singleton instance");
- managerInstance.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideAndDontSave;
- managerInstance.AddComponent<Manager>();
- }
- }
- /// <summary>
- /// Facilitates a user settable/gettable AudioClip.loadState
- /// </summary>
- public static void SetAudioClipLoadState(AudioClip audioClip, AudioDataLoadState newLoadState)
- {
- audioLoadState[audioClip] = newLoadState;
- }
- /// <summary>
- /// Facilitates a user settable/gettable AudioClip.loadState
- /// </summary>
- /// <returns>AudioDataLoadState.Failed if audioClip is null or audioClip.loadState if this manager did not touch the audioClip</returns>
- public static AudioDataLoadState GetAudioClipLoadState(AudioClip audioClip)
- {
- AudioDataLoadState ret = AudioDataLoadState.Failed;
- #if UNITY_4_6_1f1
- audioLoadState.TryGetValue(audioClip, out ret);
- #else
- if (audioClip != null)
- {
- ret = audioClip.loadState;
- audioLoadState.TryGetValue(audioClip, out ret);
- }
- #endif
- return ret;
- }
- /// <summary>
- /// Facilitates a user settable/gettable AudioClip.loadType
- /// </summary>
- public static void SetAudioClipLoadType(AudioClip audioClip, AudioClipLoadType newLoadType)
- {
- audioClipLoadType[audioClip] = newLoadType;
- }
- /// <summary>
- /// Facilitates a user settable/gettable AudioClip.loadType
- /// </summary>
- /// <returns>-1 if audioClip is null or audioClip.loadType if this manager did not touch the audioClip</returns>
- public static AudioClipLoadType GetAudioClipLoadType(AudioClip audioClip)
- {
- AudioClipLoadType ret = (AudioClipLoadType)(-1);
- #if UNITY_4_6_1f1
- audioClipLoadType.TryGetValue(audioClip, out ret);
- #else
- if (audioClip != null)
- {
- ret = audioClip.loadType;
- audioClipLoadType.TryGetValue(audioClip, out ret);
- }
- #endif
- return ret;
- }
- static string GetExtension(string filePath)
- {
- return System.IO.Path.GetExtension(filePath).Substring(1).ToLower();
- }
- /// <summary>
- /// Checks if filePath constains file with supported extension
- /// </summary>
- /// <returns>true if extension is supported, false if not</returns>
- public static bool IsSupportedFormat(string filePath)
- {
- return supportedFormats.Contains(GetExtension(filePath));
- }
- /// <summary>
- /// Returns AudioFormat enum from filePath extension
- /// </summary>
- /// <returns>AudioFormat if filePath extension is supported format, -1 otherwise</returns>
- public static AudioFormat GetAudioFormat(string filePath)
- {
- AudioFormat ret = AudioFormat.unknown;
- try
- {
- ret = (AudioFormat)System.Enum.Parse(typeof(AudioFormat), GetExtension(filePath), true);
- }
- catch
- {
- }
- return ret;
- }
- /// <summary>
- /// Clears all cached AudioClips
- /// </summary>
- public static void ClearCache()
- {
- cache.Clear();
- }
- }
- #if UNITY_4_6_1f1
- //
- // Summary:
- // ///
- // Determines how the audio clip is loaded in.
- // ///
- public enum AudioClipLoadType
- {
- //
- // Summary:
- // ///
- // The audio data is decompressed when the audio clip is loaded.
- // ///
- DecompressOnLoad = 0,
- //
- // Summary:
- // ///
- // The audio data of the clip will be kept in memory in compressed form.
- // ///
- CompressedInMemory = 1,
- //
- // Summary:
- // ///
- // Streams audio data from disk.
- // ///
- Streaming = 2
- }
- //
- // Summary:
- // ///
- // Value describing the current load state of the audio data associated with an
- // AudioClip.
- // ///
- public enum AudioDataLoadState
- {
- //
- // Summary:
- // ///
- // Value returned by AudioClip.loadState for an AudioClip that has no audio data
- // loaded and where loading has not been initiated yet.
- // ///
- Unloaded = 0,
- //
- // Summary:
- // ///
- // Value returned by AudioClip.loadState for an AudioClip that is currently loading
- // audio data.
- // ///
- Loading = 1,
- //
- // Summary:
- // ///
- // Value returned by AudioClip.loadState for an AudioClip that has succeeded loading
- // its audio data.
- // ///
- Loaded = 2,
- //
- // Summary:
- // ///
- // Value returned by AudioClip.loadState for an AudioClip that has failed loading
- // its audio data.
- // ///
- Failed = 3
- }
- #endif
- }
Advertisement
Add Comment
Please, Sign In to add comment