From d7923785a7aa4c1a81756f469909dfa8f5bbfac9 Mon Sep 17 00:00:00 2001 From: christosts Date: Tue, 29 Nov 2022 15:18:11 +0000 Subject: [PATCH] Use audio bitrate to calculate AudioTrack min buffer in passthrough Use the bitrate of the audio format (when available) in DefaultAudioSink.AudioTrackBufferSizeProvider.getBufferSizeInBytes() to calculate accurate buffer sizes for direct (passthrough) playbacks. #minor-release PiperOrigin-RevId: 491628530 (cherry picked from commit e219ac21ae604f182769d69f6f590191a92100d0) --- .../exoplayer2/audio/DefaultAudioSink.java | 4 ++ .../DefaultAudioTrackBufferSizeProvider.java | 23 +++++++--- ...ltAudioTrackBufferSizeProviderAC3Test.java | 21 ++++++++- ...dioTrackBufferSizeProviderEncodedTest.java | 43 +++++++++++++++++++ ...ltAudioTrackBufferSizeProviderPcmTest.java | 9 ++++ 5 files changed, 93 insertions(+), 7 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 919880b34d..8016384f69 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -194,6 +194,8 @@ public final class DefaultAudioSink implements AudioSink { * @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 bitrate The bitrate of the audio stream if the stream is compressed, or {@link + * Format#NO_VALUE} if {@code encoding} is PCM or the bitrate is not known. * @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 @@ -208,6 +210,7 @@ public final class DefaultAudioSink implements AudioSink { @OutputMode int outputMode, int pcmFrameSize, int sampleRate, + int bitrate, double maxAudioTrackPlaybackSpeed); } @@ -781,6 +784,7 @@ public final class DefaultAudioSink implements AudioSink { outputMode, outputPcmFrameSize != C.LENGTH_UNSET ? outputPcmFrameSize : 1, outputSampleRate, + inputFormat.bitrate, enableAudioTrackPlaybackParams ? MAX_PLAYBACK_SPEED : DEFAULT_PLAYBACK_SPEED); offloadDisabledUntilNextConfiguration = false; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java index c9cc38fd53..2e774551fa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java @@ -19,6 +19,7 @@ import static com.google.android.exoplayer2.audio.DefaultAudioSink.OUTPUT_MODE_O import static com.google.android.exoplayer2.audio.DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH; import static com.google.android.exoplayer2.audio.DefaultAudioSink.OUTPUT_MODE_PCM; import static com.google.android.exoplayer2.util.Util.constrainValue; +import static com.google.common.math.IntMath.divide; import static com.google.common.primitives.Ints.checkedCast; import static java.lang.Math.max; @@ -27,6 +28,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.math.RoundingMode; /** Provide the buffer size to use when creating an {@link AudioTrack}. */ public class DefaultAudioTrackBufferSizeProvider @@ -166,10 +168,11 @@ public class DefaultAudioTrackBufferSizeProvider @OutputMode int outputMode, int pcmFrameSize, int sampleRate, + int bitrate, double maxAudioTrackPlaybackSpeed) { int bufferSize = get1xBufferSizeInBytes( - minBufferSizeInBytes, encoding, outputMode, pcmFrameSize, sampleRate); + minBufferSizeInBytes, encoding, outputMode, pcmFrameSize, sampleRate, bitrate); // 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. @@ -180,12 +183,17 @@ public class DefaultAudioTrackBufferSizeProvider /** Returns the buffer size for playback at 1x speed. */ protected int get1xBufferSizeInBytes( - int minBufferSizeInBytes, int encoding, int outputMode, int pcmFrameSize, int sampleRate) { + int minBufferSizeInBytes, + int encoding, + int outputMode, + int pcmFrameSize, + int sampleRate, + int bitrate) { switch (outputMode) { case OUTPUT_MODE_PCM: return getPcmBufferSizeInBytes(minBufferSizeInBytes, sampleRate, pcmFrameSize); case OUTPUT_MODE_PASSTHROUGH: - return getPassthroughBufferSizeInBytes(encoding); + return getPassthroughBufferSizeInBytes(encoding, bitrate); case OUTPUT_MODE_OFFLOAD: return getOffloadBufferSizeInBytes(encoding); default: @@ -202,13 +210,16 @@ public class DefaultAudioTrackBufferSizeProvider } /** Returns the buffer size for passthrough playback. */ - protected int getPassthroughBufferSizeInBytes(@C.Encoding int encoding) { + protected int getPassthroughBufferSizeInBytes(@C.Encoding int encoding, int bitrate) { int bufferSizeUs = passthroughBufferDurationUs; if (encoding == C.ENCODING_AC3) { bufferSizeUs *= ac3BufferMultiplicationFactor; } - int maxByteRate = getMaximumEncodedRateBytesPerSecond(encoding); - return checkedCast((long) bufferSizeUs * maxByteRate / C.MICROS_PER_SECOND); + int byteRate = + bitrate != Format.NO_VALUE + ? divide(bitrate, 8, RoundingMode.CEILING) + : getMaximumEncodedRateBytesPerSecond(encoding); + return checkedCast((long) bufferSizeUs * byteRate / C.MICROS_PER_SECOND); } /** Returns the buffer size for offload playback. */ diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderAC3Test.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderAC3Test.java index f282f1684e..d93bcaac26 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderAC3Test.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderAC3Test.java @@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,7 +35,7 @@ public class DefaultAudioTrackBufferSizeProviderAC3Test { @Test public void - getBufferSizeInBytes_passthroughAC3_isPassthroughBufferSizeTimesMultiplicationFactor() { + getBufferSizeInBytes_passthroughAc3AndNoBitrate_assumesMaxByteRateTimesMultiplicationFactor() { int bufferSize = DEFAULT.getBufferSizeInBytes( /* minBufferSizeInBytes= */ 0, @@ -42,6 +43,7 @@ public class DefaultAudioTrackBufferSizeProviderAC3Test { /* outputMode= */ OUTPUT_MODE_PASSTHROUGH, /* pcmFrameSize= */ 1, /* sampleRate= */ 0, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -50,6 +52,23 @@ public class DefaultAudioTrackBufferSizeProviderAC3Test { * DEFAULT.ac3BufferMultiplicationFactor); } + @Test + public void + getBufferSizeInBytes_passthroughAC3At256Kbits_isPassthroughBufferSizeTimesMultiplicationFactor() { + int bufferSize = + DEFAULT.getBufferSizeInBytes( + /* minBufferSizeInBytes= */ 0, + /* encoding= */ C.ENCODING_AC3, + /* outputMode= */ OUTPUT_MODE_PASSTHROUGH, + /* pcmFrameSize= */ 1, + /* sampleRate= */ 0, + /* bitrate= */ 256_000, + /* maxAudioTrackPlaybackSpeed= */ 1); + + // Default buffer duration 0.25s => 0.25 * 256000 / 8 = 8000 + assertThat(bufferSize).isEqualTo(8000 * DEFAULT.ac3BufferMultiplicationFactor); + } + private static int durationUsToAc3MaxBytes(long durationUs) { return (int) (durationUs * getMaximumEncodedRateBytesPerSecond(C.ENCODING_AC3) / MICROS_PER_SECOND); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderEncodedTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderEncodedTest.java index 30db87083d..9d3a7e40a8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderEncodedTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderEncodedTest.java @@ -15,10 +15,13 @@ */ package com.google.android.exoplayer2.audio; +import static com.google.android.exoplayer2.C.MICROS_PER_SECOND; import static com.google.android.exoplayer2.audio.DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH; +import static com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider.getMaximumEncodedRateBytesPerSecond; import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +46,8 @@ public class DefaultAudioTrackBufferSizeProviderEncodedTest { C.ENCODING_MP3, C.ENCODING_AAC_LC, C.ENCODING_AAC_HE_V1, + C.ENCODING_E_AC3, + C.ENCODING_E_AC3_JOC, C.ENCODING_AC4, C.ENCODING_DTS, C.ENCODING_DOLBY_TRUEHD); @@ -57,8 +62,46 @@ public class DefaultAudioTrackBufferSizeProviderEncodedTest { /* outputMode= */ OUTPUT_MODE_PASSTHROUGH, /* pcmFrameSize= */ 1, /* sampleRate= */ 0, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 0); assertThat(bufferSize).isEqualTo(123456789); } + + @Test + public void + getBufferSizeInBytes_passThroughAndBitrateNotSet_returnsBufferSizeWithAssumedBitrate() { + int bufferSize = + DEFAULT.getBufferSizeInBytes( + /* minBufferSizeInBytes= */ 0, + /* encoding= */ encoding, + /* outputMode= */ OUTPUT_MODE_PASSTHROUGH, + /* pcmFrameSize= */ 1, + /* sampleRate= */ 0, + /* bitrate= */ Format.NO_VALUE, + /* maxAudioTrackPlaybackSpeed= */ 1); + + assertThat(bufferSize) + .isEqualTo(durationUsToMaxBytes(encoding, DEFAULT.passthroughBufferDurationUs)); + } + + @Test + public void getBufferSizeInBytes_passthroughAndBitrateDefined() { + int bufferSize = + DEFAULT.getBufferSizeInBytes( + /* minBufferSizeInBytes= */ 0, + /* encoding= */ encoding, + /* outputMode= */ OUTPUT_MODE_PASSTHROUGH, + /* pcmFrameSize= */ 1, + /* sampleRate= */ 0, + /* bitrate= */ 256_000, + /* maxAudioTrackPlaybackSpeed= */ 1); + + // Default buffer duration is 250ms => 0.25 * 256000 / 8 = 8000 + assertThat(bufferSize).isEqualTo(8000); + } + + private static int durationUsToMaxBytes(@C.Encoding int encoding, long durationUs) { + return (int) (durationUs * getMaximumEncodedRateBytesPerSecond(encoding) / MICROS_PER_SECOND); + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderPcmTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderPcmTest.java index 06c0257548..293e14cf5f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderPcmTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderPcmTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static java.lang.Math.ceil; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -89,6 +90,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize).isEqualTo(roundUpToFrame(1234567890)); @@ -103,6 +105,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -121,6 +124,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -139,6 +143,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -157,6 +162,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -175,6 +181,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -190,6 +197,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1 / 5F); assertThat(bufferSize) @@ -205,6 +213,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 8F); int expected = roundUpToFrame(durationUsToBytes(DEFAULT.minPcmBufferDurationUs) * 8);