diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Util.java b/libraries/common/src/main/java/androidx/media3/common/util/Util.java index f5d223ebab..69c78eeb6d 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/Util.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/Util.java @@ -809,23 +809,6 @@ public final class Util { return result; } - /** - * Returns the value of the {@code long} argument; throwing an exception if the value overflows an - * {@code int}. - * - *

Equivalent to API 26 {@link Math#toIntExact}. - * - * @param value The long value. - * @return The argument as an int. - * @throws ArithmeticException If the {@code argument} overflows an int. - */ - public static int toIntExact(long value) { - if ((int) value != value) { - throw new ArithmeticException(); // integer overflow - } - return (int) value; - } - /** * Returns the index of the first occurrence of {@code value} in {@code array}, or {@link * C#INDEX_UNSET} if {@code value} is not contained in {@code array}. 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 c63dbab47b..8c45a6347b 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,7 +16,6 @@ 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; @@ -219,38 +218,6 @@ public final class DefaultAudioSink implements AudioSink { } } - /** Provide 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, ignored - * 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... This will be {@code 1} unless {@link - * Builder#setEnableAudioTrackPlaybackParams} is enabled. - * @return The computed buffer size in bytes. It should always be {@code >= - * minBufferSizeInBytes}. - */ - 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 { @@ -259,13 +226,11 @@ 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; } /** @@ -346,18 +311,6 @@ 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) { @@ -418,18 +371,31 @@ 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}) - public @interface OutputMode {} + private @interface OutputMode {} - /** 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; + 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; /** * Native error code equivalent of {@link AudioTrack#ERROR_DEAD_OBJECT} to workaround missing @@ -476,7 +442,6 @@ 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; @@ -597,7 +562,6 @@ 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(); @@ -760,16 +724,6 @@ 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( @@ -791,7 +745,8 @@ public final class DefaultAudioSink implements AudioSink { outputSampleRate, outputChannelConfig, outputEncoding, - bufferSize, + specifiedBufferSize, + enableAudioTrackPlaybackParams, availableAudioProcessors); if (isAudioTrackInitialized()) { this.pendingConfiguration = pendingConfiguration; @@ -1252,8 +1207,8 @@ public final class DefaultAudioSink implements AudioSink { public void setPlaybackParameters(PlaybackParameters playbackParameters) { playbackParameters = new PlaybackParameters( - constrainValue(playbackParameters.speed, MIN_PLAYBACK_SPEED, MAX_PLAYBACK_SPEED), - constrainValue(playbackParameters.pitch, MIN_PITCH, MAX_PITCH)); + Util.constrainValue(playbackParameters.speed, MIN_PLAYBACK_SPEED, MAX_PLAYBACK_SPEED), + Util.constrainValue(playbackParameters.pitch, MIN_PITCH, MAX_PITCH)); if (enableAudioTrackPlaybackParams && Util.SDK_INT >= 23) { setAudioTrackPlaybackParametersV23(playbackParameters); } else { @@ -1829,6 +1784,47 @@ 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: @@ -2018,13 +2014,6 @@ 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 @@ -2119,7 +2108,8 @@ public final class DefaultAudioSink implements AudioSink { int outputSampleRate, int outputChannelConfig, int outputEncoding, - int bufferSize, + int specifiedBufferSize, + boolean enableAudioTrackPlaybackParams, AudioProcessor[] availableAudioProcessors) { this.inputFormat = inputFormat; this.inputPcmFrameSize = inputPcmFrameSize; @@ -2128,8 +2118,10 @@ 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. */ @@ -2149,6 +2141,10 @@ 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 { @@ -2249,6 +2245,49 @@ 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 deleted file mode 100644 index f6e0d843ff..0000000000 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioTrackBufferSizeProvider.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * 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.common.util.Util.toIntExact; -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 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. - return max(minBufferSizeInBytes, bufferSize); - } - - /** 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 toIntExact((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 toIntExact((long) offloadBufferDurationUs * maxByteRate / C.MICROS_PER_SECOND); - } - - protected static int durationUsToBytes(int durationUs, int samplingRate, int frameSize) { - return toIntExact((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 deleted file mode 100644 index a76a49374a..0000000000 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/DefaultAudioTrackBufferSizeProviderTest.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * 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.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, 16000, 44100, 48000, 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)); - } - - @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)); - } - - @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= */ 5F); - - assertThat(bufferSize).isEqualTo(durationUsToBytes(DEFAULT.minPcmBufferDurationUs * 5)); - } - } - /** - * 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= */ C.LENGTH_UNSET, - /* 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= */ C.LENGTH_UNSET, - /* 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); - } -}