mirror of
https://github.com/samsonjs/media.git
synced 2026-03-27 09:45:47 +00:00
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 d12afe0596)
This commit is contained in:
parent
e85e497911
commit
bb7e6324d8
6 changed files with 96 additions and 7 deletions
|
|
@ -13,6 +13,9 @@
|
|||
playback thread for a new ExoPlayer instance.
|
||||
* Allow download manager helpers to be cleared
|
||||
([#10776](https://github.com/google/ExoPlayer/issues/10776)).
|
||||
* Audio:
|
||||
* Use the compressed audio format bitrate to calculate the min buffer size
|
||||
for `AudioTrack` in direct playbacks (passthrough).
|
||||
* Session:
|
||||
* Add helper method to convert platform session token to Media3
|
||||
`SessionToken` ([#171](https://github.com/androidx/media/issues/171)).
|
||||
|
|
|
|||
|
|
@ -204,6 +204,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
|
||||
|
|
@ -218,6 +220,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
@OutputMode int outputMode,
|
||||
int pcmFrameSize,
|
||||
int sampleRate,
|
||||
int bitrate,
|
||||
double maxAudioTrackPlaybackSpeed);
|
||||
}
|
||||
|
||||
|
|
@ -791,6 +794,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;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ 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.math.IntMath.divide;
|
||||
import static com.google.common.primitives.Ints.checkedCast;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
|
|
@ -34,6 +35,7 @@ import androidx.media3.extractor.DtsUtil;
|
|||
import androidx.media3.extractor.MpegAudioUtil;
|
||||
import androidx.media3.extractor.OpusUtil;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/** Provide the buffer size to use when creating an {@link AudioTrack}. */
|
||||
@UnstableApi
|
||||
|
|
@ -174,10 +176,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.
|
||||
|
|
@ -188,12 +191,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:
|
||||
|
|
@ -210,13 +218,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. */
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import static androidx.media3.exoplayer.audio.DefaultAudioTrackBufferSizeProvide
|
|||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -15,10 +15,13 @@
|
|||
*/
|
||||
package androidx.media3.exoplayer.audio;
|
||||
|
||||
import static androidx.media3.common.C.MICROS_PER_SECOND;
|
||||
import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH;
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||
import static java.lang.Math.ceil;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue