Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Leaking memory with WebRequests

Discussion in 'Audio & Video' started by vollstock, Jun 3, 2023.

  1. vollstock

    vollstock

    Joined:
    Aug 15, 2015
    Posts:
    8
    I have these two methods, which are leaking memory.
    I just don’t understand why. Can someone explain it to me and help me fix it?

    Code (CSharp):
    1. private IEnumerator LoadAudioWebRequest(string file)
    2. {
    3.     using var www = UnityWebRequestMultimedia.GetAudioClip(
    4.         $"file://{Application.streamingAssetsPath}/{file}", AudioType.MPEG);
    5.  
    6.     yield return www.SendWebRequest();
    7.  
    8.     if (www.result != UnityWebRequest.Result.Success)
    9.     {
    10.         Debug.Log(www.error);
    11.     }
    12.     else
    13.     {
    14.         audioSource.Stop();
    15.         audioSource.volume = 1;
    16.         audioSource.PlayOneShot(DownloadHandlerAudioClip.GetContent(www));
    17.     }
    18. }
    Code (CSharp):
    1. private IEnumerator LoadTextureWebRequest(string file)
    2. {
    3.    
    4.     using var www = UnityWebRequestTexture.GetTexture($"file://{Application.streamingAssetsPath}/{file}");
    5.    
    6.     yield return www.SendWebRequest();
    7.  
    8.     if (www.result != UnityWebRequest.Result.Success)
    9.     {
    10.         Debug.Log(www.error);
    11.     }
    12.     else
    13.     {
    14.         contentImage.texture = null;
    15.         contentImage.texture = DownloadHandlerTexture.GetContent(www);
    16.     }
    17. }
     
  2. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,476
    You have to call .Dispose() on the request when you're done with it.
     
  3. vollstock

    vollstock

    Joined:
    Aug 15, 2015
    Posts:
    8
    Thanks for the tip. I added

    Code (CSharp):
    1. www.Dispose();
    to the end of each method but that doesn't seem to change anything.
    My memory usage is still increasing slowly but surely :-(
     
  4. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,476
    You could try calling Destroy on your previous texture or audio where you're currently setting them to null (for example
    if (contentImage.texture != null) Destroy(contentImage.texture);
    , although I believe their memory should be freed once there's no longer any reference to them anyway. That's under the assumption that you're reusing these contentImage and audioSource objects. If that doesn't help, maybe someone else can offer additional suggestions.
     
  5. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,476
    Other things to consider is that the behavior in the editor isn't always the same as a build (i.e. confirm the issue persists in an actual build), and also that GC doesn't always kick in immediately, so wait a few seconds for memory consumption to drop back down - if it doesn't, then there's definitely still a problem.
     
  6. vollstock

    vollstock

    Joined:
    Aug 15, 2015
    Posts:
    8
    Yes, I am re-using them. With the texture I have another method of loading the image bytes from disk and then creating the texture from it. That works without leaking memory and only setting the reference to null:

    Code (CSharp):
    1. private IEnumerator LoadTextureAsync(string file)
    2. {
    3.     // destroy old texture
    4.     contentImage.texture = null;
    5.  
    6.     var uri = Path.Combine(Application.streamingAssetsPath, file);
    7.  
    8.     if (!File.Exists(uri))
    9.     {
    10.         Debug.LogWarning($"Image file not found {uri}");
    11.         yield break;
    12.     }
    13.  
    14.     bytes = File.ReadAllBytes(uri);
    15.     texture.LoadImage(bytes);
    16.     contentImage.texture = texture;
    17. }
    18.  
    That does however result in a short freeze when loading the texture to GPU. So I would love to use the WebRequest for that does not freeze for whatever reason.

    However, there is no
    AudioClip.LoadAudio(bytes)
    .
     
  7. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,476
    It might be useful to know (although maybe not in your particular use case), that you can create clips from data, but it's somewhat more complicated and requires providing a few things about the clip when creating it, which you could store separately. Here's a functional example going from clip to data and then back again:
    Code (CSharp):
    1. // audio clip to data
    2. AudioClip original = audioSource.clip;
    3. float[] data = new float[original.samples * original.channels];
    4. original.GetData(data, 0);
    5.  
    6. // data to audio clip (params read from original clip to keep the example simple)
    7. AudioClip audioClip = AudioClip.Create(original.name, original.samples, original.channels, original.frequency, false);
    8. audioClip.SetData(data, 0);
    As for what's wrong with memory not freeing when using UnityWebRequestMultimedia and UnityWebRequestTexture, I can't think of anything else to try :(
     
  8. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    521
    You definitely should not need to call .Dispose as you are using the using var syntax which automatically disposes when the variable goes out of scope.

    You might (though it would be odd if it made a difference) try the other syntax for using var that encapsulates the code in a block. I'll paste some code that I use that might be helpful.

    Code (CSharp):
    1.     private IEnumerator GetPictureTexture(String id)
    2.     {
    3.         using (var req = UnityWebRequestTexture.GetTexture(_pictureMgr.GetImageUrl(id)))
    4.         {
    5.             yield return req.SendWebRequest();
    6.  
    7.             if (req.result == UnityWebRequest.Result.ProtocolError || req.result == UnityWebRequest.Result.ConnectionError)
    8.                 Logger.Error($"{_nameofClass}.GetTexture error={req.error}");
    9.             else
    10.                 _pictureRenderer.material.SetTexture("_BaseMap", DownloadHandlerTexture.GetContent(req));
    11.         }
    12.  
    13.         SetTransparency(false);
    14.     }
    15.  
     
  9. vollstock

    vollstock

    Joined:
    Aug 15, 2015
    Posts:
    8
    @tleylan
    thanks for the effort, much appreciated. However, reading “using statement - ensure the correct use of disposable objects” it should indeed not make a difference and I tried: it doesn’t.
    I will try to find some time next week and report a bug.

    @adamgolden
    Thanks for the tip on creating an AudioClip. I read about it before but tried to avoid decoding the MP3 files.
    As I understand it, I must decode the MP3 into a raw
    float[]
    before I can call
    audioClip.SetData();
     
    adamgolden likes this.
  10. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,476
    Another thing you could try that shouldn't make a difference is calling .Dispose on the DownloadHandler itself. This is supposed to be happening automatically (unless the request's disposeDownloadHandlerOnDispose has been set to false).