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`.
|
* 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:
|
||||||
|
@ -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:
|
||||||
|
@ -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(
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user