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`. * Add `media3.extractor.heif.HeifExtractor`.
* Audio: * Audio:
* Add support for Opus gapless metadata during offload playback. * 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: * Video:
* Text: * Text:
* Metadata: * Metadata:

View File

@ -220,6 +220,10 @@ public class PlaybackException extends Exception implements Bundleable {
/** Caused by an AudioTrack write operation failure. */ /** Caused by an AudioTrack write operation failure. */
public static final int ERROR_CODE_AUDIO_TRACK_WRITE_FAILED = 5002; 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). // DRM errors (6xxx).
/** Caused by an unspecified error related to DRM protection. */ /** 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"; return "ERROR_CODE_AUDIO_TRACK_INIT_FAILED";
case ERROR_CODE_AUDIO_TRACK_WRITE_FAILED: case ERROR_CODE_AUDIO_TRACK_WRITE_FAILED:
return "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: case ERROR_CODE_DRM_UNSPECIFIED:
return "ERROR_CODE_DRM_UNSPECIFIED"; return "ERROR_CODE_DRM_UNSPECIFIED";
case ERROR_CODE_DRM_SCHEME_UNSUPPORTED: case ERROR_CODE_DRM_SCHEME_UNSUPPORTED:

View File

@ -618,9 +618,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
e = e.copyWithMediaPeriodId(readingPeriod.info.id); 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); Log.w(TAG, "Recoverable renderer error", e);
pendingRecoverableRendererError = 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 // 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. // recovered or the player stopped before any other message is handled.
handler.sendMessageAtFrontOfQueue( handler.sendMessageAtFrontOfQueue(

View File

@ -1163,9 +1163,17 @@ public final class DefaultAudioSink implements AudioSink {
int error = bytesWrittenOrError; int error = bytesWrittenOrError;
// Treat a write error on a previously successful offload channel as recoverable // 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, // without disabling offload. Offload will be disabled if offload channel was not successfully
// if no longer supported. // written to or when a new AudioTrack is created, if no longer supported.
boolean isRecoverable = isAudioTrackDeadObject(error) && getWrittenFrames() > 0; 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); WriteException e = new WriteException(error, configuration.inputFormat, isRecoverable);
if (listener != null) { 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); e, inputFormat, e.isRecoverable, PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED);
} catch (WriteException e) { } catch (WriteException e) {
throw createRendererException( 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) { if (fullyConsumed) {
@ -774,7 +780,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
audioSink.playToEndOfStream(); audioSink.playToEndOfStream();
} catch (AudioSink.WriteException e) { } catch (AudioSink.WriteException e) {
throw createRendererException( 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);
} }
} }