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:
parent
d77c707cd3
commit
3742f6b1f5
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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(
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user