Retry AudioTrack init and write before throwing

Retry AudioTrack init and write for 100ms before
giving up and aborting playback.

This was tested by throwing every 2 init/write and
making sure playback did not stopped.

#exo-offload

PiperOrigin-RevId: 333536841
This commit is contained in:
krocard 2020-09-24 18:00:47 +01:00 committed by kim-vde
parent 56cb327f1e
commit 7dfdde9246

View File

@ -246,6 +246,12 @@ public final class DefaultAudioSink implements AudioSink {
*/
private static final int ERROR_NATIVE_DEAD_OBJECT = -32;
/**
* The duration for which failed attempts to initialize or write to the audio track may be retried
* before throwing an exception, in milliseconds.
*/
private static final int AUDIO_TRACK_RETRY_DURATION_MS = 100;
private static final String TAG = "AudioTrack";
/**
@ -279,6 +285,9 @@ public final class DefaultAudioSink implements AudioSink {
private final boolean enableAudioTrackPlaybackParams;
private final boolean enableOffload;
@MonotonicNonNull private StreamEventCallbackV29 offloadStreamEventCallbackV29;
private final PendingExceptionHolder<InitializationException>
initializationExceptionPendingExceptionHolder;
private final PendingExceptionHolder<WriteException> writeExceptionPendingExceptionHolder;
@Nullable private Listener listener;
/**
@ -425,6 +434,10 @@ public final class DefaultAudioSink implements AudioSink {
activeAudioProcessors = new AudioProcessor[0];
outputBuffers = new ByteBuffer[0];
mediaPositionParametersCheckpoints = new ArrayDeque<>();
initializationExceptionPendingExceptionHolder =
new PendingExceptionHolder<>(AUDIO_TRACK_RETRY_DURATION_MS);
writeExceptionPendingExceptionHolder =
new PendingExceptionHolder<>(AUDIO_TRACK_RETRY_DURATION_MS);
}
// AudioSink implementation.
@ -710,8 +723,17 @@ public final class DefaultAudioSink implements AudioSink {
}
if (!isAudioTrackInitialized()) {
initializeAudioTrack();
try {
initializeAudioTrack();
} catch (InitializationException e) {
if (e.isRecoverable) {
throw e; // Do not delay the exception if it can be recovered at higher level.
}
initializationExceptionPendingExceptionHolder.throwExceptionIfDeadlineIsReached(e);
return false;
}
}
initializationExceptionPendingExceptionHolder.clear();
if (startMediaTimeUsNeedsInit) {
startMediaTimeUs = max(0, presentationTimeUs);
@ -929,8 +951,14 @@ public final class DefaultAudioSink implements AudioSink {
if (listener != null) {
listener.onAudioSinkError(e);
}
throw e;
if (e.isRecoverable) {
throw e; // Do not delay the exception if it can be recovered at higher level.
}
writeExceptionPendingExceptionHolder.throwExceptionIfDeadlineIsReached(e);
return;
}
writeExceptionPendingExceptionHolder.clear();
int bytesWritten = bytesWrittenOrError;
if (isOffloadedPlayback(audioTrack)) {
@ -1182,6 +1210,8 @@ public final class DefaultAudioSink implements AudioSink {
}
}.start();
}
writeExceptionPendingExceptionHolder.clear();
initializationExceptionPendingExceptionHolder.clear();
}
@Override
@ -1193,6 +1223,9 @@ public final class DefaultAudioSink implements AudioSink {
return;
}
writeExceptionPendingExceptionHolder.clear();
initializationExceptionPendingExceptionHolder.clear();
if (!isAudioTrackInitialized()) {
return;
}
@ -2065,4 +2098,37 @@ public final class DefaultAudioSink implements AudioSink {
return outputMode == OUTPUT_MODE_OFFLOAD;
}
}
private static final class PendingExceptionHolder<T extends Exception> {
private final long throwDelayMs;
@Nullable private T pendingException;
private long throwDeadlineMs;
public PendingExceptionHolder(long throwDelayMs) {
this.throwDelayMs = throwDelayMs;
}
public void throwExceptionIfDeadlineIsReached(T exception) throws T {
long nowMs = SystemClock.elapsedRealtime();
if (pendingException == null) {
pendingException = exception;
throwDeadlineMs = nowMs + throwDelayMs;
}
if (nowMs >= throwDeadlineMs) {
if (pendingException != exception) {
// All retry exception are probably the same, thus only save the last one to save memory.
pendingException.addSuppressed(exception);
}
T pendingException = this.pendingException;
clear();
throw pendingException;
}
}
public void clear() {
pendingException = null;
}
}
}