Search Unity

The Ins and Outs of Audio Memory in Unity

Discussion in 'Scripting' started by pixelshaded, Jul 22, 2011.

  1. pixelshaded

    pixelshaded

    Joined:
    Oct 23, 2010
    Posts:
    40
    The project I am currently programming audio for targets low end machines. I spent many many hours google and forum searching trying to find definitive answers to how Audio works, especially when related to how it consumes memory and how I can release that memory when the audio is no longer needed. Hopefully the following information will save you loads of time.

    Hierarchy and Audio Memory
    Any AudioClips that are referenced in the hierarchy of a scene will be loaded in to memory when the scene gets loaded. Keep this in mind if you have a large audio library and are throwing a huge prefab of all your sounds into the scene hierarchy (nom nom memory nom nom). The only way to avoid this is to load audio dynamically with scripts.

    Objects and Garbage Collection
    Unity keeps track of anything you create that extends from their Object class. That means a couple things. If you do reference one of those objects and set that reference to null, the garbage collector WILL NOT pick it up. The reason for this is that the object still has references in Unity. If you want to release memory for Unity Objects you must call Destroy and pass the object as a parameter. Why is this important? AudioClips extend from Object. Hence setting them to null will not release them from memory.

    Resources.Load
    Resources.Load creates an object in memory and returns a reference to it. That object is actually the physical asset in your library which is loaded from a .asset file. This file is created when you build.

    If you call Destroy on the resource inside the editor you will get a warning that it will delete the actual asset itself. The editor protects you against doing this, but the build will actually delete the asset (but not permanently – more testing required to confirm full behavior). DestroyImmediate will destroy the asset without warning, so be careful when using it. If you are in the editor and that happens you will have to reimport your asset.

    The only way to release the memory held by the resource is to call Resources.UnloadUnusedAssets(). This process isn’t the fastest, so doing it during an Update can hurt your performance.

    One of the strongest advantages to using Resources.Load is that multiple copies of your AudioClip do not increase your memory size. You can play your clip with 8 different sources and all 8 will play individually and fully layered without adding more AudioClips to memory. The main disadvantage is that if you need to free up memory that an asset occupies, you really can’t be specific and must run the bulky Resources.UnloadUnusedAssets function.

    Note that you cannot instantiate an asset object in the build. The editor will allow you to do this however, which is unfortunately very misleading.

    Asset Bundles
    Asset bundles behave exactly like Resources.Load. Think of them as a way to fragment the .asset file into .unity3d files. This is especially powerful for the web player since you can prioritize which assets get downloaded by the user. Asset bundles need to be loaded with the WWW class. Just like Resources.Load, you will receive an actual asset so destroying them to release memory is not an option. You can however be more specific when unloading them compared to Resources.UnloadUnusedAssets.

    WWW
    WWW.audioClip does not return a reference to an AudioClip. In fact, it creates an entirely new clip object. The documentation does state that the returned AudioClip is “generated”, but let’s explore the implications of that statement. Note that WWW.GetAudioClip() does the exact same thing except is allows you to specify whether the sound is 3D or 2D.

    You can see that new clips are generated by running the following script:

    Code (csharp):
    1. WWW www = new WWW(urltosound);
    2. AudioClip clip;
    3.  
    4. for (int i = 0; i < 20; i++)
    5. {
    6.     clip = [url]www.audioClip;[/url]
    7. }
    8. Debug.Log("AudioClips " + FindObjectsOfTypeAll(typeof(AudioClip)).Length);
    You will see that 20 AudioClips are created. That creates a huge problem. These clips will reside in memory until they are destroyed. You can do that manually if you have a reference or if you don’t, by calling Resources.UnloadUnusedAssets(). Note that UnloadUnusedAssets will look through the entire game hierarchy and scripts to make sure there are no references to the resources. This IS slow so avoid putting in places where it will be called often (Update for example).

    Based on testing 3.3.0f4 and recent forum postings, you can not stream audio in the standalone player with the WWW class using WWW.GetAudioClip() and WWW.audioClip. Both return unplayable sounds until the file has been fully loaded. You can however stream Ogg files using WWW.oggVorbis (which sadly is not documented).

    WWW variables DO take up memory space. Since they do not extend from a Unity Object, you can release them from memory by setting them to null.

    Unlike Resources.Load, AudioClips you get from WWW do not automatically layer. In other words, if you play the clip with 8 sources, you will only hear one instance of your AudioClip (restarts to the beginning with each call to Play). So for each instance you want, you must create a new clip for it. You can do this with Instantiate(), but with one caveat: PlayOneShot() will return a channel error. That means that you must manually handle the creation and destruction of your AudioSources when using sounds created from WWW. If you do wish to use PlayOneShot(), you can instead create your copies by keeping the WWW in memory and calling WWW.audioClip or WWW.GetAudioClip().

    PlayOneShot
    PlayOneShot automatically creates an AudioSource for you and destroys it. However, what it does not do is destroy the AudioClip that you pass it. Hence you could get a huge memory leak if you pass it www.audioClip since it will create an un-referenced AudioClip object each time you PlayOneShot.

    AudioSource
    AudioSource.clip is a reference to an AudioClip. So you can destroy the AudioClip by running Destroy(AudioSource.clip). If you have another reference to the destroyed clip it will become null. Destroying an AudioSource DOES NOT destroy the AudioClip it references. This is significant if you are using AudioSources to store you AudioClips. You must destroy the AudioClip before you destroy the AudioSource if you want all related memory to get released.

    Original: http://www.alexander-fisher.com/?p=351#comments
     
    Last edited: Aug 1, 2011
    jealtman likes this.
  2. dansav

    dansav

    Joined:
    Sep 22, 2005
    Posts:
    453
    Awesome article. This just saved me a ton of headaches on an iphone app where audio clips were causing memory to accumulate.
    Thanks,

    Dan
     
  3. nah0y

    nah0y

    Joined:
    Apr 4, 2011
    Posts:
    74
    Thanks for the post !

    If I may add something, Unity 3.5.2 comes with something REALLY useful, that is Resources.Unload
    You can find it here (in the paragraph #2 : "Changes") : http://unity3d.com/unity/whats-new/unity-3.5.2 , it's not in the documentation for now.

    It will be better and faster to call Resources.Unload(yourResource) than Resources.UnloadUnusedAssets()
     
  4. zuizuimei

    zuizuimei

    Joined:
    Aug 15, 2013
    Posts:
    16
    thanks very much, I find very long time, I has more than three hundred audio clip for playing.also crash in ios.hope unity helping us handle this problem.
     
  5. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,342
    This thread is very old, but search engine still show it at very top when searching about this. So I would like to say that in Unity 5 there are dedicated methods for audio memory at last.

    AudioClip.LoadAudioData / AudioClip.UnloadAudioData

    and new inspector settings :

    AudioClip.preloadAudioData (In 4.x this defaults to true, which is bad for mobile project when every audio in the scene start loading together at scene init.) / AudioClip.loadInBackground (Useful for making non-blocking audio playing code where you don't care about the exact time that it will start.)

    I have used them, and they are much much more reliable than Resources.anything when used with audio.
     
    jealtman likes this.
  6. camuzak

    camuzak

    Joined:
    Dec 19, 2016
    Posts:
    1
    Thank you, this is incredibly useful!
     
  7. GameJob2017

    GameJob2017

    Joined:
    Mar 1, 2017
    Posts:
    16
    Hello all,

    Suppose we follow all your instructions correctly, is it true that we still don't know when exactly Garbage Collector removes the memory of these audio objects from RAM ?

    (In other words, we have no control over when Garbage Collector removes the objects from RAM. Is it correct ?)

    Thanks.



     
  8. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,342
    New promising solution is now available, the Addressable Asset System

    It is a direct upgrade of both `Resources.Load` and `AssetBundle` system at the same time. You could load-unload everything at will but they are not required to be in `Resources` anymore, including from the internet (hence an `AssetBundle` replacement, scheduled in 2019 as listened from Unite Berlin 2018)

    Plus anything you load via this system is reference counted, you get a nice memory profiler, etc. Haven't tried but I think this is the solution.

    The other way, if you go native with a plugin like my Native Audio or Christopher Create's Android Native Audio your loaded audio will obviously be out of garbage collector's reach.
     
    GameJob2017 likes this.