Allow renderer recovery with disabling offload if failed at first write

If offload fails at first write with [ERROR_DEAD_OBJECT](https://developer.android.com/reference/android/media/AudioTrack#ERROR_DEAD_OBJECT), then try disabling offload mode and try again. This allows recovery in a state where AudioTrack succeeds init in offload mode but writing is failing.

Issue: androidx/media#627
PiperOrigin-RevId: 564402181
This commit is contained in:
michaelkatz 2023-09-11 09:02:02 -07:00 committed by Copybara-Service
parent d77c707cd3
commit 3742f6b1f5
5 changed files with 44 additions and 7 deletions

View File

@ -33,6 +33,8 @@
* Add `media3.extractor.heif.HeifExtractor`.
* Audio:
* Add support for Opus gapless metadata during offload playback.
* Allow renderer recovery by disabling offload if failed at first write
([#627](https://github.com/androidx/media/issues/627)).
* Video:
* Text:
* Metadata:

View File

@ -220,6 +220,10 @@ public class PlaybackException extends Exception implements Bundleable {
/** Caused by an AudioTrack write operation failure. */
public static final int ERROR_CODE_AUDIO_TRACK_WRITE_FAILED = 5002;
// TODO(b/299907254): Stabilize error code, remove @UnstableApi annotation, and add to IntDef
/** Caused by an AudioTrack write operation failure in offload mode. */
@UnstableApi public static final int ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED = 5003;
// DRM errors (6xxx).
/** Caused by an unspecified error related to DRM protection. */
@ -324,6 +328,8 @@ public class PlaybackException extends Exception implements Bundleable {
return "ERROR_CODE_AUDIO_TRACK_INIT_FAILED";
case ERROR_CODE_AUDIO_TRACK_WRITE_FAILED:
return "ERROR_CODE_AUDIO_TRACK_WRITE_FAILED";
case ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED:
return "ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED";
case ERROR_CODE_DRM_UNSPECIFIED:
return "ERROR_CODE_DRM_UNSPECIFIED";
case ERROR_CODE_DRM_SCHEME_UNSUPPORTED:

View File

@ -618,9 +618,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
e = e.copyWithMediaPeriodId(readingPeriod.info.id);
}
}
if (e.isRecoverable && pendingRecoverableRendererError == null) {
if (e.isRecoverable
&& (pendingRecoverableRendererError == null
|| e.errorCode == PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED)) {
// If pendingRecoverableRendererError != null and error was
// ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED then upon retry, renderer will attempt with
// offload disabled.
Log.w(TAG, "Recoverable renderer error", e);
if (pendingRecoverableRendererError != null) {
pendingRecoverableRendererError.addSuppressed(e);
e = pendingRecoverableRendererError;
} else {
pendingRecoverableRendererError = e;
}
// Given that the player is now in an unhandled exception state, the error needs to be
// recovered or the player stopped before any other message is handled.
handler.sendMessageAtFrontOfQueue(

View File

@ -1163,9 +1163,17 @@ public final class DefaultAudioSink implements AudioSink {
int error = bytesWrittenOrError;
// Treat a write error on a previously successful offload channel as recoverable
// without disabling offload. Offload will be disabled when a new AudioTrack is created,
// if no longer supported.
boolean isRecoverable = isAudioTrackDeadObject(error) && getWrittenFrames() > 0;
// without disabling offload. Offload will be disabled if offload channel was not successfully
// written to or when a new AudioTrack is created, if no longer supported.
boolean isRecoverable = false;
if (isAudioTrackDeadObject(error)) {
if (getWrittenFrames() > 0) {
isRecoverable = true;
} else if (isOffloadedPlayback(audioTrack)) {
maybeDisableOffload();
isRecoverable = true;
}
}
WriteException e = new WriteException(error, configuration.inputFormat, isRecoverable);
if (listener != null) {

View File

@ -754,7 +754,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
e, inputFormat, e.isRecoverable, PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED);
} catch (WriteException e) {
throw createRendererException(
e, format, e.isRecoverable, PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED);
e,
format,
e.isRecoverable,
isBypassEnabled()
&& getConfiguration().offloadModePreferred != AudioSink.OFFLOAD_MODE_DISABLED
? PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED
: PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED);
}
if (fullyConsumed) {
@ -774,7 +780,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
audioSink.playToEndOfStream();
} catch (AudioSink.WriteException e) {
throw createRendererException(
e, e.format, e.isRecoverable, PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED);
e,
e.format,
e.isRecoverable,
isBypassEnabled()
? PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED
: PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED);
}
}