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 60e8fb04f9..588c3f74b9 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
@@ -16,6 +16,7 @@
package androidx.media3.exoplayer.audio;
import static androidx.media3.common.util.Assertions.checkNotNull;
+import static androidx.media3.common.util.Util.constrainValue;
import static androidx.media3.exoplayer.audio.AudioCapabilities.DEFAULT_AUDIO_CAPABILITIES;
import static com.google.common.base.MoreObjects.firstNonNull;
import static java.lang.Math.max;
@@ -218,6 +219,39 @@ public final class DefaultAudioSink implements AudioSink {
}
}
+ /** Provides the buffer size to use when creating an {@link AudioTrack}. */
+ interface AudioTrackBufferSizeProvider {
+ /** Default instance. */
+ AudioTrackBufferSizeProvider DEFAULT =
+ new DefaultAudioTrackBufferSizeProvider.Builder().build();
+ /**
+ * Returns the buffer size to use when creating an {@link AudioTrack} for a specific format and
+ * output mode.
+ *
+ * @param minBufferSizeInBytes The minimum buffer size in bytes required to play this format.
+ * See {@link AudioTrack#getMinBufferSize}.
+ * @param encoding The {@link C.Encoding} of the format.
+ * @param outputMode How the audio will be played. One of the {@link OutputMode output modes}.
+ * @param pcmFrameSize The size of the PCM frames if the {@code encoding} is PCM, 1 otherwise,
+ * in bytes.
+ * @param sampleRate The sample rate of the format, in Hz.
+ * @param maxAudioTrackPlaybackSpeed The maximum speed the content will be played using {@link
+ * AudioTrack#setPlaybackParams}. 0.5 is 2x slow motion, 1 is real time, 2 is 2x fast
+ * forward, etc. This will be {@code 1} unless {@link
+ * Builder#setEnableAudioTrackPlaybackParams} is enabled.
+ * @return The computed buffer size in bytes. It should always be {@code >=
+ * minBufferSizeInBytes}. The computed buffer size must contain an integer number of frames:
+ * {@code bufferSizeInBytes % pcmFrameSize == 0}.
+ */
+ int getBufferSizeInBytes(
+ int minBufferSizeInBytes,
+ @C.Encoding int encoding,
+ @OutputMode int outputMode,
+ int pcmFrameSize,
+ int sampleRate,
+ double maxAudioTrackPlaybackSpeed);
+ }
+
/** A builder to create {@link DefaultAudioSink} instances. */
public static final class Builder {
@@ -226,11 +260,13 @@ public final class DefaultAudioSink implements AudioSink {
private boolean enableFloatOutput;
private boolean enableAudioTrackPlaybackParams;
private int offloadMode;
+ AudioTrackBufferSizeProvider audioTrackBufferSizeProvider;
/** Creates a new builder. */
public Builder() {
audioCapabilities = DEFAULT_AUDIO_CAPABILITIES;
offloadMode = OFFLOAD_MODE_DISABLED;
+ audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT;
}
/**
@@ -311,6 +347,18 @@ public final class DefaultAudioSink implements AudioSink {
return this;
}
+ /**
+ * Sets an {@link AudioTrackBufferSizeProvider} to compute the buffer size when {@link
+ * #configure} is called with {@code specifiedBufferSize == 0}.
+ *
+ *
The default value is {@link AudioTrackBufferSizeProvider#DEFAULT}.
+ */
+ public Builder setAudioTrackBufferSizeProvider(
+ AudioTrackBufferSizeProvider audioTrackBufferSizeProvider) {
+ this.audioTrackBufferSizeProvider = audioTrackBufferSizeProvider;
+ return this;
+ }
+
/** Builds the {@link DefaultAudioSink}. Must only be called once per Builder instance. */
public DefaultAudioSink build() {
if (audioProcessorChain == null) {
@@ -371,31 +419,18 @@ public final class DefaultAudioSink implements AudioSink {
*/
public static final int OFFLOAD_MODE_ENABLED_GAPLESS_DISABLED = 3;
+ /** Output mode of the audio sink. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({OUTPUT_MODE_PCM, OUTPUT_MODE_OFFLOAD, OUTPUT_MODE_PASSTHROUGH})
- private @interface OutputMode {}
+ public @interface OutputMode {}
- private static final int OUTPUT_MODE_PCM = 0;
- private static final int OUTPUT_MODE_OFFLOAD = 1;
- private static final int OUTPUT_MODE_PASSTHROUGH = 2;
-
- /** A minimum length for the {@link AudioTrack} buffer, in microseconds. */
- private static final long MIN_BUFFER_DURATION_US = 250_000;
- /** A maximum length for the {@link AudioTrack} buffer, in microseconds. */
- private static final long MAX_BUFFER_DURATION_US = 750_000;
- /** The length for passthrough {@link AudioTrack} buffers, in microseconds. */
- private static final long PASSTHROUGH_BUFFER_DURATION_US = 250_000;
- /** The length for offload {@link AudioTrack} buffers, in microseconds. */
- private static final long OFFLOAD_BUFFER_DURATION_US = 50_000_000;
-
- /**
- * A multiplication factor to apply to the minimum buffer size requested by the underlying {@link
- * AudioTrack}.
- */
- private static final int BUFFER_MULTIPLICATION_FACTOR = 4;
- /** To avoid underruns on some devices (e.g., Broadcom 7271), scale up the AC3 buffer duration. */
- private static final int AC3_BUFFER_MULTIPLICATION_FACTOR = 2;
+ /** The audio sink plays PCM audio. */
+ public static final int OUTPUT_MODE_PCM = 0;
+ /** The audio sink plays encoded audio in offload. */
+ public static final int OUTPUT_MODE_OFFLOAD = 1;
+ /** The audio sink plays encoded audio in passthrough. */
+ public static final int OUTPUT_MODE_PASSTHROUGH = 2;
/**
* Native error code equivalent of {@link AudioTrack#ERROR_DEAD_OBJECT} to workaround missing
@@ -442,6 +477,7 @@ public final class DefaultAudioSink implements AudioSink {
private final PendingExceptionHolder
initializationExceptionPendingExceptionHolder;
private final PendingExceptionHolder writeExceptionPendingExceptionHolder;
+ private final AudioTrackBufferSizeProvider audioTrackBufferSizeProvider;
@Nullable private PlayerId playerId;
@Nullable private Listener listener;
@@ -562,6 +598,7 @@ public final class DefaultAudioSink implements AudioSink {
enableFloatOutput = Util.SDK_INT >= 21 && builder.enableFloatOutput;
enableAudioTrackPlaybackParams = Util.SDK_INT >= 23 && builder.enableAudioTrackPlaybackParams;
offloadMode = Util.SDK_INT >= 29 ? builder.offloadMode : OFFLOAD_MODE_DISABLED;
+ audioTrackBufferSizeProvider = builder.audioTrackBufferSizeProvider;
releasingConditionVariable = new ConditionVariable(true);
audioTrackPositionTracker = new AudioTrackPositionTracker(new PositionTrackerListener());
channelMappingAudioProcessor = new ChannelMappingAudioProcessor();
@@ -724,6 +761,16 @@ public final class DefaultAudioSink implements AudioSink {
outputChannelConfig = encodingAndChannelConfig.second;
}
}
+ int bufferSize =
+ specifiedBufferSize != 0
+ ? specifiedBufferSize
+ : audioTrackBufferSizeProvider.getBufferSizeInBytes(
+ getAudioTrackMinBufferSize(outputSampleRate, outputChannelConfig, outputEncoding),
+ outputEncoding,
+ outputMode,
+ outputPcmFrameSize,
+ outputSampleRate,
+ enableAudioTrackPlaybackParams ? MAX_PLAYBACK_SPEED : DEFAULT_PLAYBACK_SPEED);
if (outputEncoding == C.ENCODING_INVALID) {
throw new ConfigurationException(
@@ -745,8 +792,7 @@ public final class DefaultAudioSink implements AudioSink {
outputSampleRate,
outputChannelConfig,
outputEncoding,
- specifiedBufferSize,
- enableAudioTrackPlaybackParams,
+ bufferSize,
availableAudioProcessors);
if (isAudioTrackInitialized()) {
this.pendingConfiguration = pendingConfiguration;
@@ -1207,8 +1253,8 @@ public final class DefaultAudioSink implements AudioSink {
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
playbackParameters =
new PlaybackParameters(
- Util.constrainValue(playbackParameters.speed, MIN_PLAYBACK_SPEED, MAX_PLAYBACK_SPEED),
- Util.constrainValue(playbackParameters.pitch, MIN_PITCH, MAX_PITCH));
+ constrainValue(playbackParameters.speed, MIN_PLAYBACK_SPEED, MAX_PLAYBACK_SPEED),
+ constrainValue(playbackParameters.pitch, MIN_PITCH, MAX_PITCH));
if (enableAudioTrackPlaybackParams && Util.SDK_INT >= 23) {
setAudioTrackPlaybackParametersV23(playbackParameters);
} else {
@@ -1789,47 +1835,6 @@ public final class DefaultAudioSink implements AudioSink {
return Util.SDK_INT >= 29 && audioTrack.isOffloadedPlayback();
}
- private static int getMaximumEncodedRateBytesPerSecond(@C.Encoding int encoding) {
- switch (encoding) {
- case C.ENCODING_MP3:
- return MpegAudioUtil.MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_AAC_LC:
- return AacUtil.AAC_LC_MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_AAC_HE_V1:
- return AacUtil.AAC_HE_V1_MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_AAC_HE_V2:
- return AacUtil.AAC_HE_V2_MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_AAC_XHE:
- return AacUtil.AAC_XHE_MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_AAC_ELD:
- return AacUtil.AAC_ELD_MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_AC3:
- return Ac3Util.AC3_MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_E_AC3:
- case C.ENCODING_E_AC3_JOC:
- return Ac3Util.E_AC3_MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_AC4:
- return Ac4Util.MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_DTS:
- return DtsUtil.DTS_MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_DTS_HD:
- return DtsUtil.DTS_HD_MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_DOLBY_TRUEHD:
- return Ac3Util.TRUEHD_MAX_RATE_BYTES_PER_SECOND;
- case C.ENCODING_PCM_16BIT:
- case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
- case C.ENCODING_PCM_24BIT:
- case C.ENCODING_PCM_32BIT:
- case C.ENCODING_PCM_8BIT:
- case C.ENCODING_PCM_FLOAT:
- case C.ENCODING_AAC_ER_BSAC:
- case C.ENCODING_INVALID:
- case Format.NO_VALUE:
- default:
- throw new IllegalArgumentException();
- }
- }
-
private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) {
switch (encoding) {
case C.ENCODING_MP3:
@@ -2019,6 +2024,13 @@ public final class DefaultAudioSink implements AudioSink {
.build();
}
+ private static int getAudioTrackMinBufferSize(
+ int sampleRateInHz, int channelConfig, int encoding) {
+ int minBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, encoding);
+ Assertions.checkState(minBufferSize != AudioTrack.ERROR_BAD_VALUE);
+ return minBufferSize;
+ }
+
private final class PositionTrackerListener implements AudioTrackPositionTracker.Listener {
@Override
@@ -2113,8 +2125,7 @@ public final class DefaultAudioSink implements AudioSink {
int outputSampleRate,
int outputChannelConfig,
int outputEncoding,
- int specifiedBufferSize,
- boolean enableAudioTrackPlaybackParams,
+ int bufferSize,
AudioProcessor[] availableAudioProcessors) {
this.inputFormat = inputFormat;
this.inputPcmFrameSize = inputPcmFrameSize;
@@ -2123,10 +2134,8 @@ public final class DefaultAudioSink implements AudioSink {
this.outputSampleRate = outputSampleRate;
this.outputChannelConfig = outputChannelConfig;
this.outputEncoding = outputEncoding;
+ this.bufferSize = bufferSize;
this.availableAudioProcessors = availableAudioProcessors;
-
- // Call computeBufferSize() last as it depends on the other configuration values.
- this.bufferSize = computeBufferSize(specifiedBufferSize, enableAudioTrackPlaybackParams);
}
/** Returns if the configurations are sufficiently compatible to reuse the audio track. */
@@ -2146,10 +2155,6 @@ public final class DefaultAudioSink implements AudioSink {
return (frameCount * C.MICROS_PER_SECOND) / outputSampleRate;
}
- public long durationUsToFrames(long durationUs) {
- return (durationUs * outputSampleRate) / C.MICROS_PER_SECOND;
- }
-
public AudioTrack buildAudioTrack(
boolean tunneling, AudioAttributes audioAttributes, int audioSessionId)
throws InitializationException {
@@ -2250,49 +2255,6 @@ public final class DefaultAudioSink implements AudioSink {
}
}
- private int computeBufferSize(
- int specifiedBufferSize, boolean enableAudioTrackPlaybackParameters) {
- if (specifiedBufferSize != 0) {
- return specifiedBufferSize;
- }
- switch (outputMode) {
- case OUTPUT_MODE_PCM:
- return getPcmDefaultBufferSize(
- enableAudioTrackPlaybackParameters ? MAX_PLAYBACK_SPEED : DEFAULT_PLAYBACK_SPEED);
- case OUTPUT_MODE_OFFLOAD:
- return getEncodedDefaultBufferSize(OFFLOAD_BUFFER_DURATION_US);
- case OUTPUT_MODE_PASSTHROUGH:
- return getEncodedDefaultBufferSize(PASSTHROUGH_BUFFER_DURATION_US);
- default:
- throw new IllegalStateException();
- }
- }
-
- private int getEncodedDefaultBufferSize(long bufferDurationUs) {
- int rate = getMaximumEncodedRateBytesPerSecond(outputEncoding);
- if (outputEncoding == C.ENCODING_AC3) {
- rate *= AC3_BUFFER_MULTIPLICATION_FACTOR;
- }
- return (int) (bufferDurationUs * rate / C.MICROS_PER_SECOND);
- }
-
- private int getPcmDefaultBufferSize(float maxAudioTrackPlaybackSpeed) {
- int minBufferSize =
- AudioTrack.getMinBufferSize(outputSampleRate, outputChannelConfig, outputEncoding);
- Assertions.checkState(minBufferSize != AudioTrack.ERROR_BAD_VALUE);
- int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR;
- int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * outputPcmFrameSize;
- int maxAppBufferSize =
- max(minBufferSize, (int) durationUsToFrames(MAX_BUFFER_DURATION_US) * outputPcmFrameSize);
- int bufferSize =
- Util.constrainValue(multipliedBufferSize, minAppBufferSize, maxAppBufferSize);
- if (maxAudioTrackPlaybackSpeed != 1f) {
- // Maintain the buffer duration by scaling the size accordingly.
- bufferSize = Math.round(bufferSize * maxAudioTrackPlaybackSpeed);
- }
- return bufferSize;
- }
-
@RequiresApi(21)
private static android.media.AudioAttributes getAudioTrackAttributesV21(
AudioAttributes audioAttributes, boolean tunneling) {
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioTrackBufferSizeProvider.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioTrackBufferSizeProvider.java
new file mode 100644
index 0000000000..5083d92896
--- /dev/null
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioTrackBufferSizeProvider.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.media3.exoplayer.audio;
+
+import static androidx.media3.common.util.Util.constrainValue;
+import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_OFFLOAD;
+import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH;
+import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PCM;
+import static com.google.common.primitives.Ints.checkedCast;
+import static java.lang.Math.max;
+
+import android.media.AudioTrack;
+import androidx.media3.common.C;
+import androidx.media3.common.Format;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.exoplayer.audio.DefaultAudioSink.OutputMode;
+import androidx.media3.extractor.AacUtil;
+import androidx.media3.extractor.Ac3Util;
+import androidx.media3.extractor.Ac4Util;
+import androidx.media3.extractor.DtsUtil;
+import androidx.media3.extractor.MpegAudioUtil;
+
+/** Provide the buffer size to use when creating an {@link AudioTrack}. */
+@UnstableApi
+public class DefaultAudioTrackBufferSizeProvider
+ implements DefaultAudioSink.AudioTrackBufferSizeProvider {
+
+ /** Default minimum length for the {@link AudioTrack} buffer, in microseconds. */
+ private static final int MIN_PCM_BUFFER_DURATION_US = 250_000;
+ /** Default maximum length for the {@link AudioTrack} buffer, in microseconds. */
+ private static final int MAX_PCM_BUFFER_DURATION_US = 750_000;
+ /** Default multiplication factor to apply to the minimum buffer size requested. */
+ private static final int PCM_BUFFER_MULTIPLICATION_FACTOR = 4;
+ /** Default length for passthrough {@link AudioTrack} buffers, in microseconds. */
+ private static final int PASSTHROUGH_BUFFER_DURATION_US = 250_000;
+ /** Default length for offload {@link AudioTrack} buffers, in microseconds. */
+ private static final int OFFLOAD_BUFFER_DURATION_US = 50_000_000;
+ /**
+ * Default multiplication factor to apply to AC3 passthrough buffer to avoid underruns on some
+ * devices (e.g., Broadcom 7271).
+ */
+ private static final int AC3_BUFFER_MULTIPLICATION_FACTOR = 2;
+
+ /** A builder to create {@link DefaultAudioTrackBufferSizeProvider} instances. */
+ public static class Builder {
+
+ private int minPcmBufferDurationUs;
+ private int maxPcmBufferDurationUs;
+ private int pcmBufferMultiplicationFactor;
+ private int passthroughBufferDurationUs;
+ private int offloadBufferDurationUs;
+ private int ac3BufferMultiplicationFactor;
+
+ /** Creates a new builder. */
+ public Builder() {
+ minPcmBufferDurationUs = MIN_PCM_BUFFER_DURATION_US;
+ maxPcmBufferDurationUs = MAX_PCM_BUFFER_DURATION_US;
+ pcmBufferMultiplicationFactor = PCM_BUFFER_MULTIPLICATION_FACTOR;
+ passthroughBufferDurationUs = PASSTHROUGH_BUFFER_DURATION_US;
+ offloadBufferDurationUs = OFFLOAD_BUFFER_DURATION_US;
+ ac3BufferMultiplicationFactor = AC3_BUFFER_MULTIPLICATION_FACTOR;
+ }
+
+ /**
+ * Sets the minimum length for PCM {@link AudioTrack} buffers, in microseconds. Default is
+ * {@value #MIN_PCM_BUFFER_DURATION_US}.
+ */
+ public Builder setMinPcmBufferDurationUs(int minPcmBufferDurationUs) {
+ this.minPcmBufferDurationUs = minPcmBufferDurationUs;
+ return this;
+ }
+
+ /**
+ * Sets the maximum length for PCM {@link AudioTrack} buffers, in microseconds. Default is
+ * {@value #MAX_PCM_BUFFER_DURATION_US}.
+ */
+ public Builder setMaxPcmBufferDurationUs(int maxPcmBufferDurationUs) {
+ this.maxPcmBufferDurationUs = maxPcmBufferDurationUs;
+ return this;
+ }
+
+ /**
+ * Sets the multiplication factor to apply to the minimum buffer size requested. Default is
+ * {@value #PCM_BUFFER_MULTIPLICATION_FACTOR}.
+ */
+ public Builder setPcmBufferMultiplicationFactor(int pcmBufferMultiplicationFactor) {
+ this.pcmBufferMultiplicationFactor = pcmBufferMultiplicationFactor;
+ return this;
+ }
+
+ /**
+ * Sets the length for passthrough {@link AudioTrack} buffers, in microseconds. Default is
+ * {@value #PASSTHROUGH_BUFFER_DURATION_US}.
+ */
+ public Builder setPassthroughBufferDurationUs(int passthroughBufferDurationUs) {
+ this.passthroughBufferDurationUs = passthroughBufferDurationUs;
+ return this;
+ }
+
+ /**
+ * The length for offload {@link AudioTrack} buffers, in microseconds. Default is {@value
+ * #OFFLOAD_BUFFER_DURATION_US}.
+ */
+ public Builder setOffloadBufferDurationUs(int offloadBufferDurationUs) {
+ this.offloadBufferDurationUs = offloadBufferDurationUs;
+ return this;
+ }
+
+ /**
+ * Sets the multiplication factor to apply to the passthrough buffer for AC3 to avoid underruns
+ * on some devices (e.g., Broadcom 7271). Default is {@value #AC3_BUFFER_MULTIPLICATION_FACTOR}.
+ */
+ public Builder setAc3BufferMultiplicationFactor(int ac3BufferMultiplicationFactor) {
+ this.ac3BufferMultiplicationFactor = ac3BufferMultiplicationFactor;
+ return this;
+ }
+
+ /** Build the {@link DefaultAudioTrackBufferSizeProvider}. */
+ public DefaultAudioTrackBufferSizeProvider build() {
+ return new DefaultAudioTrackBufferSizeProvider(this);
+ }
+ }
+
+ /** The minimum length for PCM {@link AudioTrack} buffers, in microseconds. */
+ protected final int minPcmBufferDurationUs;
+ /** The maximum length for PCM {@link AudioTrack} buffers, in microseconds. */
+ protected final int maxPcmBufferDurationUs;
+ /** The multiplication factor to apply to the minimum buffer size requested. */
+ protected final int pcmBufferMultiplicationFactor;
+ /** The length for passthrough {@link AudioTrack} buffers, in microseconds. */
+ protected final int passthroughBufferDurationUs;
+ /** The length for offload {@link AudioTrack} buffers, in microseconds. */
+ protected final int offloadBufferDurationUs;
+ /**
+ * The multiplication factor to apply to AC3 passthrough buffer to avoid underruns on some devices
+ * (e.g., Broadcom 7271).
+ */
+ public final int ac3BufferMultiplicationFactor;
+
+ protected DefaultAudioTrackBufferSizeProvider(Builder builder) {
+ minPcmBufferDurationUs = builder.minPcmBufferDurationUs;
+ maxPcmBufferDurationUs = builder.maxPcmBufferDurationUs;
+ pcmBufferMultiplicationFactor = builder.pcmBufferMultiplicationFactor;
+ passthroughBufferDurationUs = builder.passthroughBufferDurationUs;
+ offloadBufferDurationUs = builder.offloadBufferDurationUs;
+ ac3BufferMultiplicationFactor = builder.ac3BufferMultiplicationFactor;
+ }
+
+ @Override
+ public int getBufferSizeInBytes(
+ int minBufferSizeInBytes,
+ @C.Encoding int encoding,
+ @OutputMode int outputMode,
+ int pcmFrameSize,
+ int sampleRate,
+ double maxAudioTrackPlaybackSpeed) {
+ int bufferSize =
+ get1xBufferSizeInBytes(
+ minBufferSizeInBytes, encoding, outputMode, pcmFrameSize, sampleRate);
+ // Maintain the buffer duration by scaling the size accordingly.
+ bufferSize = (int) (bufferSize * maxAudioTrackPlaybackSpeed);
+ // Buffer size must not be lower than the AudioTrack min buffer size for this format.
+ bufferSize = max(minBufferSizeInBytes, bufferSize);
+ // Increase if needed to make sure the buffers contains an integer number of frames.
+ return (bufferSize + pcmFrameSize - 1) / pcmFrameSize * pcmFrameSize;
+ }
+
+ /** Returns the buffer size for playback at 1x speed. */
+ protected int get1xBufferSizeInBytes(
+ int minBufferSizeInBytes, int encoding, int outputMode, int pcmFrameSize, int sampleRate) {
+ switch (outputMode) {
+ case OUTPUT_MODE_PCM:
+ return getPcmBufferSizeInBytes(minBufferSizeInBytes, sampleRate, pcmFrameSize);
+ case OUTPUT_MODE_PASSTHROUGH:
+ return getPassthroughBufferSizeInBytes(encoding);
+ case OUTPUT_MODE_OFFLOAD:
+ return getOffloadBufferSizeInBytes(encoding);
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /** Returns the buffer size for PCM playback. */
+ protected int getPcmBufferSizeInBytes(int minBufferSizeInBytes, int samplingRate, int frameSize) {
+ int targetBufferSize = minBufferSizeInBytes * pcmBufferMultiplicationFactor;
+ int minAppBufferSize = durationUsToBytes(minPcmBufferDurationUs, samplingRate, frameSize);
+ int maxAppBufferSize = durationUsToBytes(maxPcmBufferDurationUs, samplingRate, frameSize);
+ return constrainValue(targetBufferSize, minAppBufferSize, maxAppBufferSize);
+ }
+
+ /** Returns the buffer size for passthrough playback. */
+ protected int getPassthroughBufferSizeInBytes(@C.Encoding int encoding) {
+ int bufferSizeUs = passthroughBufferDurationUs;
+ if (encoding == C.ENCODING_AC3) {
+ bufferSizeUs *= ac3BufferMultiplicationFactor;
+ }
+ int maxByteRate = getMaximumEncodedRateBytesPerSecond(encoding);
+ return checkedCast((long) bufferSizeUs * maxByteRate / C.MICROS_PER_SECOND);
+ }
+
+ /** Returns the buffer size for offload playback. */
+ protected int getOffloadBufferSizeInBytes(@C.Encoding int encoding) {
+ int maxByteRate = getMaximumEncodedRateBytesPerSecond(encoding);
+ return checkedCast((long) offloadBufferDurationUs * maxByteRate / C.MICROS_PER_SECOND);
+ }
+
+ protected static int durationUsToBytes(int durationUs, int samplingRate, int frameSize) {
+ return checkedCast((long) durationUs * samplingRate * frameSize / C.MICROS_PER_SECOND);
+ }
+
+ protected static int getMaximumEncodedRateBytesPerSecond(@C.Encoding int encoding) {
+ switch (encoding) {
+ case C.ENCODING_MP3:
+ return MpegAudioUtil.MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AAC_LC:
+ return AacUtil.AAC_LC_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AAC_HE_V1:
+ return AacUtil.AAC_HE_V1_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AAC_HE_V2:
+ return AacUtil.AAC_HE_V2_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AAC_XHE:
+ return AacUtil.AAC_XHE_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AAC_ELD:
+ return AacUtil.AAC_ELD_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AC3:
+ return Ac3Util.AC3_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_E_AC3:
+ case C.ENCODING_E_AC3_JOC:
+ return Ac3Util.E_AC3_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AC4:
+ return Ac4Util.MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_DTS:
+ return DtsUtil.DTS_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_DTS_HD:
+ return DtsUtil.DTS_HD_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_DOLBY_TRUEHD:
+ return Ac3Util.TRUEHD_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_PCM_16BIT:
+ case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
+ case C.ENCODING_PCM_24BIT:
+ case C.ENCODING_PCM_32BIT:
+ case C.ENCODING_PCM_8BIT:
+ case C.ENCODING_PCM_FLOAT:
+ case C.ENCODING_AAC_ER_BSAC:
+ case C.ENCODING_INVALID:
+ case Format.NO_VALUE:
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/DefaultAudioTrackBufferSizeProviderTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/DefaultAudioTrackBufferSizeProviderTest.java
new file mode 100644
index 0000000000..8916391b8b
--- /dev/null
+++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/DefaultAudioTrackBufferSizeProviderTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.media3.exoplayer.audio;
+
+import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH;
+import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PCM;
+import static androidx.media3.exoplayer.audio.DefaultAudioTrackBufferSizeProvider.getMaximumEncodedRateBytesPerSecond;
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.media3.common.C;
+import androidx.media3.common.util.Util;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+
+/** Tests for {@link DefaultAudioTrackBufferSizeProvider}. */
+@RunWith(JUnit4.class)
+public class DefaultAudioTrackBufferSizeProviderTest {
+
+ private static final DefaultAudioTrackBufferSizeProvider DEFAULT =
+ new DefaultAudioTrackBufferSizeProvider.Builder().build();
+
+ /** Tests for {@link DefaultAudioTrackBufferSizeProvider} for PCM audio. */
+ @RunWith(Parameterized.class)
+ public static class PcmTest {
+
+ @Parameterized.Parameter(0)
+ @C.PcmEncoding
+ public int encoding;
+
+ @Parameterized.Parameter(1)
+ public int channelCount;
+
+ @Parameterized.Parameter(2)
+ public int sampleRate;
+
+ @Parameterized.Parameters(name = "{index}: encoding={0}, channelCount={1}, sampleRate={2}")
+ public static List data() {
+ return Sets.cartesianProduct(
+ ImmutableList.of(
+ /* encoding */ ImmutableSet.of(
+ C.ENCODING_PCM_8BIT,
+ C.ENCODING_PCM_16BIT,
+ C.ENCODING_PCM_16BIT_BIG_ENDIAN,
+ C.ENCODING_PCM_24BIT,
+ C.ENCODING_PCM_32BIT,
+ C.ENCODING_PCM_FLOAT),
+ /* channelCount */ ImmutableSet.of(1, 2, 3, 4, 6, 8),
+ /* sampleRate*/ ImmutableSet.of(
+ 8000, 11025, 16000, 22050, 44100, 48000, 88200, 96000)))
+ .stream()
+ .map(s -> s.toArray(new Integer[0]))
+ .collect(Collectors.toList());
+ }
+
+ private int getPcmFrameSize() {
+ return Util.getPcmFrameSize(encoding, channelCount);
+ }
+
+ private int durationUsToBytes(int durationUs) {
+ return (int) (((long) durationUs * getPcmFrameSize() * sampleRate) / C.MICROS_PER_SECOND);
+ }
+
+ @Test
+ public void getBufferSizeInBytes_veryBigMinBufferSize_isMinBufferSize() {
+ int bufferSize =
+ DEFAULT.getBufferSizeInBytes(
+ /* minBufferSizeInBytes= */ 123456789,
+ /* encoding= */ encoding,
+ /* outputMode= */ OUTPUT_MODE_PCM,
+ /* pcmFrameSize= */ getPcmFrameSize(),
+ /* sampleRate= */ sampleRate,
+ /* maxAudioTrackPlaybackSpeed= */ 1);
+
+ assertThat(bufferSize).isEqualTo(123456789);
+ }
+
+ @Test
+ public void getBufferSizeInBytes_noMinBufferSize_isMinBufferDuration() {
+ int bufferSize =
+ DEFAULT.getBufferSizeInBytes(
+ /* minBufferSizeInBytes= */ 0,
+ /* encoding= */ encoding,
+ /* outputMode= */ OUTPUT_MODE_PCM,
+ /* pcmFrameSize= */ getPcmFrameSize(),
+ /* sampleRate= */ sampleRate,
+ /* maxAudioTrackPlaybackSpeed= */ 1);
+
+ assertThat(bufferSize).isEqualTo(durationUsToBytes(DEFAULT.minPcmBufferDurationUs));
+ assertThat(bufferSize % getPcmFrameSize()).isEqualTo(0);
+ }
+
+ @Test
+ public void getBufferSizeInBytes_tooSmallMinBufferSize_isMinBufferDuration() {
+ int minBufferSizeInBytes =
+ durationUsToBytes(DEFAULT.minPcmBufferDurationUs / DEFAULT.pcmBufferMultiplicationFactor)
+ - 1;
+ int bufferSize =
+ DEFAULT.getBufferSizeInBytes(
+ /* minBufferSizeInBytes= */ minBufferSizeInBytes,
+ /* encoding= */ encoding,
+ /* outputMode= */ OUTPUT_MODE_PCM,
+ /* pcmFrameSize= */ getPcmFrameSize(),
+ /* sampleRate= */ sampleRate,
+ /* maxAudioTrackPlaybackSpeed= */ 1);
+
+ assertThat(bufferSize).isEqualTo(durationUsToBytes(DEFAULT.minPcmBufferDurationUs));
+ }
+
+ @Test
+ public void getBufferSizeInBytes_lowMinBufferSize_multipliesAudioTrackMinBuffer() {
+ int minBufferSizeInBytes =
+ durationUsToBytes(DEFAULT.minPcmBufferDurationUs / DEFAULT.pcmBufferMultiplicationFactor)
+ + 1;
+ int bufferSize =
+ DEFAULT.getBufferSizeInBytes(
+ /* minBufferSizeInBytes= */ minBufferSizeInBytes,
+ /* encoding= */ encoding,
+ /* outputMode= */ OUTPUT_MODE_PCM,
+ /* pcmFrameSize= */ getPcmFrameSize(),
+ /* sampleRate= */ sampleRate,
+ /* maxAudioTrackPlaybackSpeed= */ 1);
+
+ assertThat(bufferSize)
+ .isEqualTo(minBufferSizeInBytes * DEFAULT.pcmBufferMultiplicationFactor);
+ }
+
+ @Test
+ public void getBufferSizeInBytes_highMinBufferSize_multipliesAudioTrackMinBuffer() {
+ int minBufferSizeInBytes =
+ durationUsToBytes(DEFAULT.maxPcmBufferDurationUs / DEFAULT.pcmBufferMultiplicationFactor)
+ - 1;
+ int bufferSize =
+ DEFAULT.getBufferSizeInBytes(
+ /* minBufferSizeInBytes= */ minBufferSizeInBytes,
+ /* encoding= */ encoding,
+ /* outputMode= */ OUTPUT_MODE_PCM,
+ /* pcmFrameSize= */ getPcmFrameSize(),
+ /* sampleRate= */ sampleRate,
+ /* maxAudioTrackPlaybackSpeed= */ 1);
+
+ assertThat(bufferSize)
+ .isEqualTo(minBufferSizeInBytes * DEFAULT.pcmBufferMultiplicationFactor);
+ }
+
+ @Test
+ public void getBufferSizeInBytes_tooHighMinBufferSize_isMaxBufferDuration() {
+ int minBufferSizeInBytes =
+ durationUsToBytes(DEFAULT.maxPcmBufferDurationUs / DEFAULT.pcmBufferMultiplicationFactor)
+ + 1;
+ int bufferSize =
+ DEFAULT.getBufferSizeInBytes(
+ /* minBufferSizeInBytes= */ minBufferSizeInBytes,
+ /* encoding= */ encoding,
+ /* outputMode= */ OUTPUT_MODE_PCM,
+ /* pcmFrameSize= */ getPcmFrameSize(),
+ /* sampleRate= */ sampleRate,
+ /* maxAudioTrackPlaybackSpeed= */ 1);
+
+ assertThat(bufferSize).isEqualTo(durationUsToBytes(DEFAULT.maxPcmBufferDurationUs));
+ assertThat(bufferSize % getPcmFrameSize()).isEqualTo(0);
+ }
+
+ @Test
+ public void getBufferSizeInBytes_lowPlaybackSpeed_isScaledByPlaybackSpeed() {
+ int bufferSize =
+ DEFAULT.getBufferSizeInBytes(
+ /* minBufferSizeInBytes= */ 0,
+ /* encoding= */ encoding,
+ /* outputMode= */ OUTPUT_MODE_PCM,
+ /* pcmFrameSize= */ getPcmFrameSize(),
+ /* sampleRate= */ sampleRate,
+ /* maxAudioTrackPlaybackSpeed= */ 1 / 5F);
+
+ assertThat(bufferSize).isEqualTo(durationUsToBytes(DEFAULT.minPcmBufferDurationUs / 5));
+ }
+
+ @Test
+ public void getBufferSizeInBytes_highPlaybackSpeed_isScaledByPlaybackSpeed() {
+ int bufferSize =
+ DEFAULT.getBufferSizeInBytes(
+ /* minBufferSizeInBytes= */ 0,
+ /* encoding= */ encoding,
+ /* outputMode= */ OUTPUT_MODE_PCM,
+ /* pcmFrameSize= */ getPcmFrameSize(),
+ /* sampleRate= */ sampleRate,
+ /* maxAudioTrackPlaybackSpeed= */ 8F);
+
+ assertThat(bufferSize).isEqualTo(durationUsToBytes(DEFAULT.minPcmBufferDurationUs * 8));
+ }
+ }
+ /**
+ * Tests for {@link DefaultAudioTrackBufferSizeProvider} for encoded audio except {@link
+ * C#ENCODING_AC3}.
+ */
+ @RunWith(Parameterized.class)
+ public static class EncodedTest {
+
+ @Parameterized.Parameter(0)
+ @C.Encoding
+ public int encoding;
+
+ @Parameterized.Parameters(name = "{index}: encoding={0}")
+ public static ImmutableList data() {
+ return ImmutableList.of(
+ C.ENCODING_MP3,
+ C.ENCODING_AAC_LC,
+ C.ENCODING_AAC_HE_V1,
+ C.ENCODING_AC4,
+ C.ENCODING_DTS,
+ C.ENCODING_DOLBY_TRUEHD);
+ }
+
+ @Test
+ public void getBufferSizeInBytes_veryBigMinBufferSize_isMinBufferSize() {
+ int bufferSize =
+ DEFAULT.getBufferSizeInBytes(
+ /* minBufferSizeInBytes= */ 123456789,
+ /* encoding= */ encoding,
+ /* outputMode= */ OUTPUT_MODE_PASSTHROUGH,
+ /* pcmFrameSize= */ 1,
+ /* sampleRate= */ 0,
+ /* maxAudioTrackPlaybackSpeed= */ 0);
+
+ assertThat(bufferSize).isEqualTo(123456789);
+ }
+ }
+
+ @Test
+ public void
+ getBufferSizeInBytes_passthroughAC3_isPassthroughBufferSizeTimesMultiplicationFactor() {
+ int bufferSize =
+ DEFAULT.getBufferSizeInBytes(
+ /* minBufferSizeInBytes= */ 0,
+ /* encoding= */ C.ENCODING_AC3,
+ /* outputMode= */ OUTPUT_MODE_PASSTHROUGH,
+ /* pcmFrameSize= */ 1,
+ /* sampleRate= */ 0,
+ /* maxAudioTrackPlaybackSpeed= */ 1);
+
+ assertThat(bufferSize)
+ .isEqualTo(
+ durationUsToAc3MaxBytes(DEFAULT.passthroughBufferDurationUs)
+ * DEFAULT.ac3BufferMultiplicationFactor);
+ }
+
+ private static int durationUsToAc3MaxBytes(long durationUs) {
+ return (int)
+ (durationUs * getMaximumEncodedRateBytesPerSecond(C.ENCODING_AC3) / C.MICROS_PER_SECOND);
+ }
+}