androidandroid-mediacodecaudiotrack

Memory leak of AudioTrack


I encountered a severe problem of memory leak when AudioTrack and MediaSync are used together. It seems to me, the problem is that AudioTrack doesn't release some native resources. As a result, the app can be run only several times, after that, AudioTrack can't be created since there are no available tracks any more.

Below is a short example which causes the memory leak. The full project may be downloaded here on GitHub. APK file may be downloaded here.

final MediaSync mediaSync = new MediaSync();
mediaSync.setSurface(mSurface);
final Surface inputSurface = mediaSync.createInputSurface(); // There is no the memory leak if I don't create this input surface.

final AudioTrack audioTrack = new AudioTrack.Builder()
        .setAudioAttributes(new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
                .build())
        .setAudioFormat(new AudioFormat.Builder()
                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                .setSampleRate(48000)
                .setChannelMask(12)
                .build())
        .build();
mediaSync.setAudioTrack(audioTrack); // There is no the memory leak if I don't set AudioTrack.

mediaSync.release();
inputSurface.release();
audioTrack.release();

I reproduce the issue in the following way:

  1. Run the app.
  2. Press the Home button, run again. Repeat it approximately 14 times, after that get the error.

Logcat:

2019-03-15 09:19:57.313 239-15387/? E/AudioFlinger: no more track names available
2019-03-15 09:19:57.313 239-15387/? E/AudioFlinger: createTrack_l() initCheck failed -12; no control block?
2019-03-15 09:19:57.313 3413-3413/com.audiotrackmemoryleak E/AudioTrack: AudioFlinger could not create track, status: -12
2019-03-15 09:19:57.313 3413-3413/com.audiotrackmemoryleak E/AudioTrack-JNI: Error -12 initializing AudioTrack
2019-03-15 09:19:57.313 3413-3413/com.audiotrackmemoryleak E/android.media.AudioTrack: Error code -20 when initializing AudioTrack.
2019-03-15 09:19:57.315 3413-3413/com.audiotrackmemoryleak D/AndroidRuntime: Shutting down VM
2019-03-15 09:19:57.316 3413-3413/com.audiotrackmemoryleak E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.audiotrackmemoryleak, PID: 3413
    java.lang.UnsupportedOperationException: Cannot create AudioTrack
        at android.media.AudioTrack$Builder.build(AudioTrack.java:776)
        at com.audiotrackmemoryleak.MainActivity.createMediaSync(MainActivity.java:68)
        at com.audiotrackmemoryleak.MainActivity.access$100(MainActivity.java:18)
        at com.audiotrackmemoryleak.MainActivity$1.surfaceCreated(MainActivity.java:32)
        at android.view.SurfaceView.updateWindow(SurfaceView.java:618)
        at ...

The command adb shell dumpsys media.audio_flinger demonstrates the problem:

...
Clients:             
  pid: 3413          
Notification Clients:
  pid: 239           
  pid: 841           
  pid: 3413          
  pid: 28651         
Global session refs: 
  session   pid count
     3193  3413     1
     3201  3413     1
     3209  3413     1
     3217  3413     1
     3225  3413     1
     3233  3413     1
     3241  3413     1
     3249  3413     1
     3257  3413     1
     3265  3413     1
     3273  3413     1
     3281  3413     1
     3289  3413     1
     3297  3413     1
...
          14 Tracks of which 0 are active
    Name Active Client Type      Fmt Chn mask Session fCount S F SRate  L dB  R dB    Server Main buf  Aux Buf Flags UndFrmCnt
       7     no   3413    3 00000001 00000003    3249   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
       5     no   3413    3 00000001 00000003    3233   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
      12     no   3413    3 00000001 00000003    3289   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
       3     no   3413    3 00000001 00000003    3217   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
       8     no   3413    3 00000001 00000003    3257   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
       9     no   3413    3 00000001 00000003    3265   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
       4     no   3413    3 00000001 00000003    3225   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
       1     no   3413    3 00000001 00000003    3201   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
      11     no   3413    3 00000001 00000003    3281   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
       0     no   3413    3 00000001 00000003    3193   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
       6     no   3413    3 00000001 00000003    3241   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
      13     no   3413    3 00000001 00000003    3297   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
       2     no   3413    3 00000001 00000003    3209   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
      10     no   3413    3 00000001 00000003    3273   1924 I 0 48000     0     0  00000000 0xa77fe000 0x0 0x000         0
  0 Effect Chains
...

I wonder if there's anyone here who can explain what's going on? How should I release AudioTrack properly?


Solution

  • I'm not sure whether it's an SDK bug or not but it's related to AudioAttributes.FLAG_DEEP_BUFFER. When you construct AudioTrack through the Builder this flag is set by default. See your case here: the check of shouldEnablePowerSaving() returns true and the switch case results in PERFORMANCE_MODE_POWER_SAVING with FLAG_DEEP_BUFFER enabled.

    To fix this you should disable this flag by for example adding .setFlags(AudioAttributes.FLAG_LOW_LATENCY) call to your AudioAttributes, but it requires min SDK 24. Otherwise you can abandon the use of AudioTrack.Builder altogether and construct the track like this:

        int audioSampleRate = 48000;
        int channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
        int bufSize = AudioTrack.getMinBufferSize(audioSampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
    
        AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, audioSampleRate,
                channelConfig, AudioFormat.ENCODING_PCM_16BIT, bufSize, AudioTrack.MODE_STREAM);
    

    Or you can check the code of shouldEnablePowerSaving() check and make it not pass in any other way.

    UPDATE: So the above solution just moved the leak to another audio thread. I investigated further and noticed that surfaceCreated() hands the same Surface object on my android 8 device. And indeed, this is the correct behaviour since android 7. I assume it somehow breaks the mediaSync surface logic: if you remove the call to mediaSync.createInputSurface() and add mediaSync.setSurface(null) call before releasing it, the leak will be gone.

    I don't know how to workaround this issue since the Surface being reused by the system and there's no way to know when it will be actually destroyed. I suggest switching to TextureView, it has similar but clearer API and doesn't destroy the surface on activity pause. You will need to remove the createMediaSync() call from onResume() and use it like this:

    setContentView(R.layout.activity_main);
    final TextureView textureView = findViewById(R.id.texture_view);
    
    textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            mSurface = new Surface(surface);
            createMediaSync();
        }
    
        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        }
    
        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            //release resources here
            return true;
        }
    
        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        }
    });
    

    Good luck!