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)
This commit is contained in:
christosts 2022-11-29 15:18:11 +00:00
parent b6477ddddd
commit d7923785a7
5 changed files with 93 additions and 7 deletions

View File

@ -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;

View File

@ -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. */

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);