From 40517200fc5436c1e7cf91e6f2a24ce0a3c44e7b Mon Sep 17 00:00:00 2001 From: krocard Date: Mon, 31 Jan 2022 11:29:21 +0000 Subject: [PATCH] Retry AudioTrack with smaller buffer if > 1M Some phone with limited memory can't allocate bigger shared memory buffers. This might or might not be related to Binder's 1M transaction limit. Tested on Pixel 4 by setting the minimum buffer size to 1h. https://github.com/google/ExoPlayer/issues/9712 #minor-release PiperOrigin-RevId: 425324536 --- .../exoplayer/audio/DefaultAudioSink.java | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java index 588c3f74b9..f0c5db919d 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java @@ -78,6 +78,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; @UnstableApi public final class DefaultAudioSink implements AudioSink { + /** + * If an attempt to instantiate an AudioTrack with a buffer size larger than this value fails, a + * second attempt is made using this buffer size. + */ + private static final int AUDIO_TRACK_SMALLER_BUFFER_RETRY_SIZE = 1_000_000; + /** * Thrown when the audio track has provided a spurious timestamp, if {@link * #failOnSpuriousAudioTimestamp} is set. @@ -833,7 +839,7 @@ public final class DefaultAudioSink implements AudioSink { // initialization of the audio track to fail. releasingConditionVariable.block(); - audioTrack = buildAudioTrack(); + audioTrack = buildAudioTrackWithRetry(); if (isOffloadedPlayback(audioTrack)) { registerStreamEventCallbackV29(audioTrack); if (offloadMode != OFFLOAD_MODE_ENABLED_GAPLESS_DISABLED) { @@ -1027,12 +1033,31 @@ public final class DefaultAudioSink implements AudioSink { return false; } - private AudioTrack buildAudioTrack() throws InitializationException { + private AudioTrack buildAudioTrackWithRetry() throws InitializationException { try { - return checkNotNull(configuration) - .buildAudioTrack(tunneling, audioAttributes, audioSessionId); - } catch (InitializationException e) { + return buildAudioTrack(checkNotNull(configuration)); + } catch (InitializationException initialFailure) { + // Retry with a smaller buffer size. + if (configuration.bufferSize > AUDIO_TRACK_SMALLER_BUFFER_RETRY_SIZE) { + Configuration retryConfiguration = + configuration.copyWithBufferSize(AUDIO_TRACK_SMALLER_BUFFER_RETRY_SIZE); + try { + AudioTrack audioTrack = buildAudioTrack(retryConfiguration); + configuration = retryConfiguration; + return audioTrack; + } catch (InitializationException retryFailure) { + initialFailure.addSuppressed(retryFailure); + } + } maybeDisableOffload(); + throw initialFailure; + } + } + + private AudioTrack buildAudioTrack(Configuration configuration) throws InitializationException { + try { + return configuration.buildAudioTrack(tunneling, audioAttributes, audioSessionId); + } catch (InitializationException e) { if (listener != null) { listener.onAudioSinkError(e); } @@ -2138,6 +2163,19 @@ public final class DefaultAudioSink implements AudioSink { this.availableAudioProcessors = availableAudioProcessors; } + public Configuration copyWithBufferSize(int bufferSize) { + return new Configuration( + inputFormat, + inputPcmFrameSize, + outputMode, + outputPcmFrameSize, + outputSampleRate, + outputChannelConfig, + outputEncoding, + bufferSize, + availableAudioProcessors); + } + /** Returns if the configurations are sufficiently compatible to reuse the audio track. */ public boolean canReuseAudioTrack(Configuration audioTrackConfiguration) { return audioTrackConfiguration.outputMode == outputMode