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:
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?
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With