From 7c3b461b649c7ad493282482e825713e6345d804 Mon Sep 17 00:00:00 2001 From: krocard Date: Thu, 23 Apr 2020 21:21:46 +0100 Subject: [PATCH] Make DefaultAudioSink.getFramesPerEncodedSample endianness independent While most ExoPlayer code parsing ByteBuffers is called with buffers in big endian, in certain situation, buffers in little endian are used too. MediaCodec produced ByteBuffers are in little endian, while buffers receive from the sources are in big endian (ByteBuffer's default). As a result, some code called from AudioSink in passthrough parsed bytebuffer in little endian. This is not correct because those format are specified in BigEndian. Changing the endianness of the ByteBuffer returned from MediaCodec would impact a lot more code that can currently be tested in the current COVID lockdown situation. As a result, this patch instead make the parsing code independent of the ByteBuffer.order() set. All the code that is called from DefaultAudioSink now parses the buffer explicitly in Big Endian. Additionally, the MPEG big endian header data of size 4 bytes was retrieved with ByteBuffer.get, which only returns one byte. PiperOrigin-RevId: 308116173 --- .../android/exoplayer2/audio/Ac3Util.java | 3 ++- .../google/android/exoplayer2/util/Util.java | 17 ++++++++++++ .../android/exoplayer2/util/UtilTest.java | 26 +++++++++++++++++++ .../exoplayer2/audio/DefaultAudioSink.java | 3 ++- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index d4042a99b1..f9a97d961f 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -516,7 +517,7 @@ public final class Ac3Util { int endIndex = buffer.limit() - TRUEHD_SYNCFRAME_PREFIX_LENGTH; for (int i = startIndex; i <= endIndex; i++) { // The syncword ends 0xBA for TrueHD or 0xBB for MLP. - if ((buffer.getInt(i + 4) & 0xFEFFFFFF) == 0xBA6F72F8) { + if ((Util.getBigEndianInt(buffer, i + 4) & 0xFFFFFFFE) == 0xF8726FBA) { return i - startIndex; } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index 60fe1a39d4..6d8ca9e4b5 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -60,6 +60,8 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; @@ -1859,6 +1861,21 @@ public final class Util { return initialValue; } + /** + * Absolute get method for reading an int value in {@link ByteOrder#BIG_ENDIAN} in a {@link + * ByteBuffer}. Same as {@link ByteBuffer#getInt(int)} except the buffer's order as returned by + * {@link ByteBuffer#order()} is ignored and {@link ByteOrder#BIG_ENDIAN} is used instead. + * + * @param buffer The buffer from which to read an int in big endian. + * @param index The index from which the bytes will be read. + * @return The int value at the given index with the buffer bytes ordered most significant to + * least significant. + */ + public static int getBigEndianInt(ByteBuffer buffer, int index) { + int value = buffer.getInt(index); + return buffer.order() == ByteOrder.BIG_ENDIAN ? value : Integer.reverseBytes(value); + } + /** * Returns the {@link C.NetworkType} of the current network connection. * diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java index 825988cf48..2e523a32c6 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java @@ -27,6 +27,8 @@ 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.testutil.TestUtil; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.Random; @@ -789,6 +791,30 @@ public class UtilTest { assertThat(result).isEqualTo(0x4); } + @Test + public void getBigEndianInt_fromBigEndian() { + byte[] bytes = {0x1F, 0x2E, 0x3D, 0x4C}; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); + + assertThat(Util.getBigEndianInt(byteBuffer, 0)).isEqualTo(0x1F2E3D4C); + } + + @Test + public void getBigEndianInt_fromLittleEndian() { + byte[] bytes = {(byte) 0xC2, (byte) 0xD3, (byte) 0xE4, (byte) 0xF5}; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); + + assertThat(Util.getBigEndianInt(byteBuffer, 0)).isEqualTo(0xC2D3E4F5); + } + + @Test + public void getBigEndianInt_unaligned() { + byte[] bytes = {9, 8, 7, 6, 5}; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); + + assertThat(Util.getBigEndianInt(byteBuffer, 1)).isEqualTo(0x08070605); + } + @Test public void inflate_withDeflatedData_success() { byte[] testData = TestUtil.buildTestData(/*arbitrary test data size*/ 256 * 1024); 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 a0aebdfe66..78699d41f4 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 @@ -1242,7 +1242,8 @@ public final class DefaultAudioSink implements AudioSink { private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) { switch (encoding) { case C.ENCODING_MP3: - return MpegAudioUtil.parseMpegAudioFrameSampleCount(buffer.get(buffer.position())); + int headerDataInBigEndian = Util.getBigEndianInt(buffer, buffer.position()); + return MpegAudioUtil.parseMpegAudioFrameSampleCount(headerDataInBigEndian); case C.ENCODING_AAC_LC: return AacUtil.AAC_LC_AUDIO_SAMPLE_COUNT; case C.ENCODING_AAC_HE_V1: