Commit 1e8d1631 by tonihei Committed by Rohit Singh

Use SingleThreadExecutor to release AudioTracks

We currently start a simple Thread to release AudioTracks
asynchronously. If many AudioTracks are released at the same
time, this may lead to OOM situations because we attempt to
create multiple new threads.

This can be improved by using a shared SingleThreadExecutor.
In the simple case of one simmultaneous release, it's exactly
the same behavior as before: create a thread and release it
as soon as it's done. For multiple simultanous releases we
get the advantage of sharing a single thread to avoid creating
more than one at the same time.

Issue: google/ExoPlayer#10057
PiperOrigin-RevId: 460698942
parent 6ec18c8a
...@@ -33,6 +33,7 @@ import android.os.Handler; ...@@ -33,6 +33,7 @@ import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.DoNotInline; import androidx.annotation.DoNotInline;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
...@@ -58,6 +59,7 @@ import java.nio.ByteOrder; ...@@ -58,6 +59,7 @@ import java.nio.ByteOrder;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.ExecutorService;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...@@ -463,6 +465,15 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -463,6 +465,15 @@ public final class DefaultAudioSink implements AudioSink {
*/ */
public static boolean failOnSpuriousAudioTimestamp = false; public static boolean failOnSpuriousAudioTimestamp = false;
private static final Object releaseExecutorLock = new Object();
@GuardedBy("releaseExecutorLock")
@Nullable
private static ExecutorService releaseExecutor;
@GuardedBy("releaseExecutorLock")
private static int pendingReleaseCount;
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
private final AudioProcessorChain audioProcessorChain; private final AudioProcessorChain audioProcessorChain;
private final boolean enableFloatOutput; private final boolean enableFloatOutput;
...@@ -1415,9 +1426,6 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1415,9 +1426,6 @@ public final class DefaultAudioSink implements AudioSink {
if (isOffloadedPlayback(audioTrack)) { if (isOffloadedPlayback(audioTrack)) {
checkNotNull(offloadStreamEventCallbackV29).unregister(audioTrack); checkNotNull(offloadStreamEventCallbackV29).unregister(audioTrack);
} }
// AudioTrack.release can take some time, so we call it on a background thread.
final AudioTrack toRelease = audioTrack;
audioTrack = null;
if (Util.SDK_INT < 21 && !externalAudioSessionIdProvided) { if (Util.SDK_INT < 21 && !externalAudioSessionIdProvided) {
// Prior to API level 21, audio sessions are not kept alive once there are no components // Prior to API level 21, audio sessions are not kept alive once there are no components
// associated with them. If we generated the session ID internally, the only component // associated with them. If we generated the session ID internally, the only component
...@@ -1431,18 +1439,8 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1431,18 +1439,8 @@ public final class DefaultAudioSink implements AudioSink {
pendingConfiguration = null; pendingConfiguration = null;
} }
audioTrackPositionTracker.reset(); audioTrackPositionTracker.reset();
releasingConditionVariable.close(); releaseAudioTrackAsync(audioTrack, releasingConditionVariable);
new Thread("ExoPlayer:AudioTrackReleaseThread") { audioTrack = null;
@Override
public void run() {
try {
toRelease.flush();
toRelease.release();
} finally {
releasingConditionVariable.open();
}
}
}.start();
} }
writeExceptionPendingExceptionHolder.clear(); writeExceptionPendingExceptionHolder.clear();
initializationExceptionPendingExceptionHolder.clear(); initializationExceptionPendingExceptionHolder.clear();
...@@ -1853,6 +1851,36 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1853,6 +1851,36 @@ public final class DefaultAudioSink implements AudioSink {
} }
} }
private static void releaseAudioTrackAsync(
AudioTrack audioTrack, ConditionVariable releasedConditionVariable) {
// AudioTrack.release can take some time, so we call it on a background thread. The background
// thread is shared statically to avoid creating many threads when multiple players are released
// at the same time.
releasedConditionVariable.close();
synchronized (releaseExecutorLock) {
if (releaseExecutor == null) {
releaseExecutor = Util.newSingleThreadExecutor("ExoPlayer:AudioTrackReleaseThread");
}
pendingReleaseCount++;
releaseExecutor.execute(
() -> {
try {
audioTrack.flush();
audioTrack.release();
} finally {
releasedConditionVariable.open();
synchronized (releaseExecutorLock) {
pendingReleaseCount--;
if (pendingReleaseCount == 0) {
releaseExecutor.shutdown();
releaseExecutor = null;
}
}
}
});
}
}
@RequiresApi(29) @RequiresApi(29)
private final class StreamEventCallbackV29 { private final class StreamEventCallbackV29 {
private final Handler handler; private final Handler handler;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment