Skip to content

Commit

Permalink
Don't block AudioTrack when waiting for previous release
Browse files Browse the repository at this point in the history
We wait until a previous AudioTrack has been released before
creating a new one. This is currently done with a thread
block operation, which may cause ANRs in the extreme case
when someone attempts to release the player while this is
still blocked.

The problem can be avoided by just returning false from
DefaultAudioSink.handleBuffer to try again until the previous
AudioTrack is released.

Reproduction steps to force the issue:
1. Add Thread.sleep(10000); to the AudioTrack release thread.
2. Add this to the demo app:
    private int positionMs = 0;

    Handler handler = new Handler();
    handler.post(new Runnable() {
      @OverRide
      public void run() {
        player.seekTo(positionMs++);
        if (positionMs == 10) {
          player.release();
        } else {
          handler.postDelayed(this, 1000);
        }
      }
3. Observe Player release timeout exception.

These steps can't be easily captured in a unit test as we can't
artifically delay the AudioTrack release from the test.

Issue: #10057
PiperOrigin-RevId: 459468912
  • Loading branch information
tonihei authored and rohitjoins committed Jul 7, 2022
1 parent 4230ea9 commit a80dd60
Showing 1 changed file with 14 additions and 6 deletions.
Expand Up @@ -29,7 +29,6 @@
import android.media.AudioTrack;
import android.media.PlaybackParams;
import android.media.metrics.LogSessionId;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Pair;
Expand All @@ -43,6 +42,8 @@
import com.google.android.exoplayer2.analytics.PlayerId;
import com.google.android.exoplayer2.audio.AudioProcessor.UnhandledAudioFormatException;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
Expand Down Expand Up @@ -606,7 +607,8 @@ private DefaultAudioSink(Builder builder) {
enableAudioTrackPlaybackParams = Util.SDK_INT >= 23 && builder.enableAudioTrackPlaybackParams;
offloadMode = Util.SDK_INT >= 29 ? builder.offloadMode : OFFLOAD_MODE_DISABLED;
audioTrackBufferSizeProvider = builder.audioTrackBufferSizeProvider;
releasingConditionVariable = new ConditionVariable(true);
releasingConditionVariable = new ConditionVariable(Clock.DEFAULT);
releasingConditionVariable.open();
audioTrackPositionTracker = new AudioTrackPositionTracker(new PositionTrackerListener());
channelMappingAudioProcessor = new ChannelMappingAudioProcessor();
trimmingAudioProcessor = new TrimmingAudioProcessor();
Expand Down Expand Up @@ -831,13 +833,15 @@ private void flushAudioProcessors() {
}
}

private void initializeAudioTrack() throws InitializationException {
// If we're asynchronously releasing a previous audio track then we block until it has been
private boolean initializeAudioTrack() throws InitializationException {
// If we're asynchronously releasing a previous audio track then we wait until it has been
// released. This guarantees that we cannot end up in a state where we have multiple audio
// track instances. Without this guarantee it would be possible, in extreme cases, to exhaust
// the shared memory that's available for audio track buffers. This would in turn cause the
// initialization of the audio track to fail.
releasingConditionVariable.block();
if (!releasingConditionVariable.isOpen()) {
return false;
}

audioTrack = buildAudioTrackWithRetry();
if (isOffloadedPlayback(audioTrack)) {
Expand Down Expand Up @@ -865,6 +869,7 @@ private void initializeAudioTrack() throws InitializationException {
}

startMediaTimeUsNeedsInit = true;
return true;
}

@Override
Expand Down Expand Up @@ -921,7 +926,10 @@ public boolean handleBuffer(

if (!isAudioTrackInitialized()) {
try {
initializeAudioTrack();
if (!initializeAudioTrack()) {
// Not yet ready for initialization of a new AudioTrack.
return false;
}
} catch (InitializationException e) {
if (e.isRecoverable) {
throw e; // Do not delay the exception if it can be recovered at higher level.
Expand Down

0 comments on commit a80dd60

Please sign in to comment.