From b36db1a87e16e3f6d8ccc4b9ce09e59a0cab3ccc Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 16 Feb 2018 03:05:55 -0800 Subject: [PATCH] Add little endian and 14-bit mode support for DtsReader Issue:#3340 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185973510 --- RELEASENOTES.md | 2 + .../android/exoplayer2/audio/DtsUtil.java | 118 ++++++++++++++++-- .../exoplayer2/extractor/ts/DtsReader.java | 16 ++- .../exoplayer2/util/ParsableBitArray.java | 34 +++++ .../exoplayer2/util/ParsableBitArrayTest.java | 67 ++++++++++ 5 files changed, 219 insertions(+), 18 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7d15a0c70d..ec46844982 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -116,6 +116,8 @@ completed. * ID3: Better handle malformed ID3 data ([#3792](https://github.com/google/ExoPlayer/issues/3792). +* Support 14-bit mode and little endianness in DTS PES packets + ([#3340](https://github.com/google/ExoPlayer/issues/3340)). ### 2.6.1 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java index 9e9b927fab..dc07b1a646 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java @@ -20,12 +20,22 @@ import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableBitArray; import java.nio.ByteBuffer; +import java.util.Arrays; /** * Utility methods for parsing DTS frames. */ public final class DtsUtil { + private static final int SYNC_VALUE_BE = 0x7FFE8001; + private static final int SYNC_VALUE_14B_BE = 0x1FFFE800; + private static final int SYNC_VALUE_LE = 0xFE7F0180; + private static final int SYNC_VALUE_14B_LE = 0xFF1F00E8; + private static final byte FIRST_BYTE_BE = (byte) (SYNC_VALUE_BE >>> 24); + private static final byte FIRST_BYTE_14B_BE = (byte) (SYNC_VALUE_14B_BE >>> 24); + private static final byte FIRST_BYTE_LE = (byte) (SYNC_VALUE_LE >>> 24); + private static final byte FIRST_BYTE_14B_LE = (byte) (SYNC_VALUE_14B_LE >>> 24); + /** * Maps AMODE to the number of channels. See ETSI TS 102 114 table 5.4. */ @@ -45,6 +55,20 @@ public final class DtsUtil { 384, 448, 512, 640, 768, 896, 1024, 1152, 1280, 1536, 1920, 2048, 2304, 2560, 2688, 2816, 2823, 2944, 3072, 3840, 4096, 6144, 7680}; + /** + * Returns whether a given integer matches a DTS sync word. Synchronization and storage modes are + * defined in ETSI TS 102 114 V1.1.1 (2002-08), Section 5.3. + * + * @param word An integer. + * @return Whether a given integer matches a DTS sync word. + */ + public static boolean isSyncWord(int word) { + return word == SYNC_VALUE_BE + || word == SYNC_VALUE_LE + || word == SYNC_VALUE_14B_BE + || word == SYNC_VALUE_14B_LE; + } + /** * Returns the DTS format given {@code data} containing the DTS frame according to ETSI TS 102 114 * subsections 5.3/5.4. @@ -57,8 +81,8 @@ public final class DtsUtil { */ public static Format parseDtsFormat(byte[] frame, String trackId, String language, DrmInitData drmInitData) { - ParsableBitArray frameBits = new ParsableBitArray(frame); - frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE + ParsableBitArray frameBits = getNormalizedFrameHeader(frame); + frameBits.skipBits(32 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE int amode = frameBits.readBits(6); int channelCount = CHANNELS_BY_AMODE[amode]; int sfreq = frameBits.readBits(4); @@ -79,8 +103,21 @@ public final class DtsUtil { * @return The number of audio samples represented by the frame. */ public static int parseDtsAudioSampleCount(byte[] data) { - // See ETSI TS 102 114 subsection 5.4.1. - int nblks = ((data[4] & 0x01) << 6) | ((data[5] & 0xFC) >> 2); + int nblks; + switch (data[0]) { + case FIRST_BYTE_LE: + nblks = ((data[5] & 0x01) << 6) | ((data[4] & 0xFC) >> 2); + break; + case FIRST_BYTE_14B_LE: + nblks = ((data[4] & 0x07) << 4) | ((data[7] & 0x3C) >> 2); + break; + case FIRST_BYTE_14B_BE: + nblks = ((data[5] & 0x07) << 4) | ((data[6] & 0x3C) >> 2); + break; + default: + // We blindly assume FIRST_BYTE_BE if none of the others match. + nblks = ((data[4] & 0x01) << 6) | ((data[5] & 0xFC) >> 2); + } return (nblks + 1) * 32; } @@ -94,8 +131,21 @@ public final class DtsUtil { public static int parseDtsAudioSampleCount(ByteBuffer buffer) { // See ETSI TS 102 114 subsection 5.4.1. int position = buffer.position(); - int nblks = ((buffer.get(position + 4) & 0x01) << 6) - | ((buffer.get(position + 5) & 0xFC) >> 2); + int nblks; + switch (buffer.get(position)) { + case FIRST_BYTE_LE: + nblks = ((buffer.get(position + 5) & 0x01) << 6) | ((buffer.get(position + 4) & 0xFC) >> 2); + break; + case FIRST_BYTE_14B_LE: + nblks = ((buffer.get(position + 4) & 0x07) << 4) | ((buffer.get(position + 7) & 0x3C) >> 2); + break; + case FIRST_BYTE_14B_BE: + nblks = ((buffer.get(position + 5) & 0x07) << 4) | ((buffer.get(position + 6) & 0x3C) >> 2); + break; + default: + // We blindly assume FIRST_BYTE_BE if none of the others match. + nblks = ((buffer.get(position + 4) & 0x01) << 6) | ((buffer.get(position + 5) & 0xFC) >> 2); + } return (nblks + 1) * 32; } @@ -106,9 +156,59 @@ public final class DtsUtil { * @return The frame's size in bytes. */ public static int getDtsFrameSize(byte[] data) { - return (((data[5] & 0x02) << 12) - | ((data[6] & 0xFF) << 4) - | ((data[7] & 0xF0) >> 4)) + 1; + int fsize; + boolean uses14BitPerWord = false; + switch (data[0]) { + case FIRST_BYTE_14B_BE: + fsize = (((data[6] & 0x03) << 12) | ((data[7] & 0xFF) << 4) | ((data[8] & 0x3C) >> 2)) + 1; + uses14BitPerWord = true; + break; + case FIRST_BYTE_LE: + fsize = (((data[4] & 0x03) << 12) | ((data[7] & 0xFF) << 4) | ((data[6] & 0xF0) >> 4)) + 1; + break; + case FIRST_BYTE_14B_LE: + fsize = (((data[7] & 0x03) << 12) | ((data[6] & 0xFF) << 4) | ((data[9] & 0x3C) >> 2)) + 1; + uses14BitPerWord = true; + break; + default: + // We blindly assume FIRST_BYTE_BE if none of the others match. + fsize = (((data[5] & 0x03) << 12) | ((data[6] & 0xFF) << 4) | ((data[7] & 0xF0) >> 4)) + 1; + } + + // If the frame is stored in 14-bit mode, adjust the frame size to reflect the actual byte size. + return uses14BitPerWord ? fsize * 16 / 14 : fsize; + } + + private static ParsableBitArray getNormalizedFrameHeader(byte[] frameHeader) { + if (frameHeader[0] == FIRST_BYTE_BE) { + // The frame is already 16-bit mode, big endian. + return new ParsableBitArray(frameHeader); + } + // Data is not normalized, but we don't want to modify frameHeader. + frameHeader = Arrays.copyOf(frameHeader, frameHeader.length); + if (isLittleEndianFrameHeader(frameHeader)) { + // Change endianness. + for (int i = 0; i < frameHeader.length - 1; i += 2) { + byte temp = frameHeader[i]; + frameHeader[i] = frameHeader[i + 1]; + frameHeader[i + 1] = temp; + } + } + ParsableBitArray frameBits = new ParsableBitArray(frameHeader); + if (frameHeader[0] == (byte) (SYNC_VALUE_14B_BE >> 24)) { + // Discard the 2 most significant bits of each 16 bit word. + ParsableBitArray scratchBits = new ParsableBitArray(frameHeader); + while (scratchBits.bitsLeft() >= 16) { + scratchBits.skipBits(2); + frameBits.putInt(scratchBits.readBits(14), 14); + } + } + frameBits.reset(frameHeader); + return frameBits; + } + + private static boolean isLittleEndianFrameHeader(byte[] frameHeader) { + return frameHeader[0] == FIRST_BYTE_LE || frameHeader[0] == FIRST_BYTE_14B_LE; } private DtsUtil() {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java index df1e8816f0..0fc3383015 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java @@ -32,9 +32,7 @@ public final class DtsReader implements ElementaryStreamReader { private static final int STATE_READING_HEADER = 1; private static final int STATE_READING_SAMPLE = 2; - private static final int HEADER_SIZE = 15; - private static final int SYNC_VALUE = 0x7FFE8001; - private static final int SYNC_VALUE_SIZE = 4; + private static final int HEADER_SIZE = 18; private final ParsableByteArray headerScratchBytes; private final String language; @@ -63,10 +61,6 @@ public final class DtsReader implements ElementaryStreamReader { */ public DtsReader(String language) { headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]); - headerScratchBytes.data[0] = (byte) ((SYNC_VALUE >> 24) & 0xFF); - headerScratchBytes.data[1] = (byte) ((SYNC_VALUE >> 16) & 0xFF); - headerScratchBytes.data[2] = (byte) ((SYNC_VALUE >> 8) & 0xFF); - headerScratchBytes.data[3] = (byte) (SYNC_VALUE & 0xFF); state = STATE_FINDING_SYNC; this.language = language; } @@ -96,7 +90,6 @@ public final class DtsReader implements ElementaryStreamReader { switch (state) { case STATE_FINDING_SYNC: if (skipToNextSync(data)) { - bytesRead = SYNC_VALUE_SIZE; state = STATE_READING_HEADER; } break; @@ -154,7 +147,12 @@ public final class DtsReader implements ElementaryStreamReader { while (pesBuffer.bytesLeft() > 0) { syncBytes <<= 8; syncBytes |= pesBuffer.readUnsignedByte(); - if (syncBytes == SYNC_VALUE) { + if (DtsUtil.isSyncWord(syncBytes)) { + headerScratchBytes.data[0] = (byte) ((syncBytes >> 24) & 0xFF); + headerScratchBytes.data[1] = (byte) ((syncBytes >> 16) & 0xFF); + headerScratchBytes.data[2] = (byte) ((syncBytes >> 8) & 0xFF); + headerScratchBytes.data[3] = (byte) (syncBytes & 0xFF); + bytesRead = 4; syncBytes = 0; return true; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java index 19b303484f..fb5f9525e9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java @@ -263,6 +263,40 @@ public final class ParsableBitArray { assertValidOffset(); } + /** + * Overwrites {@code numBits} from this array using the {@code numBits} least significant bits + * from {@code value}. Bits are written in order from most significant to least significant. The + * read position is advanced by {@code numBits}. + * + * @param value The integer whose {@code numBits} least significant bits are written into {@link + * #data}. + * @param numBits The number of bits to write. + */ + public void putInt(int value, int numBits) { + int remainingBitsToRead = numBits; + if (numBits < 32) { + value &= (1 << numBits) - 1; + } + int firstByteReadSize = Math.min(8 - bitOffset, numBits); + int firstByteRightPaddingSize = 8 - bitOffset - firstByteReadSize; + int firstByteBitmask = (0xFF00 >> bitOffset) | ((1 << firstByteRightPaddingSize) - 1); + data[byteOffset] &= firstByteBitmask; + int firstByteInputBits = value >>> (numBits - firstByteReadSize); + data[byteOffset] |= firstByteInputBits << firstByteRightPaddingSize; + remainingBitsToRead -= firstByteReadSize; + int currentByteIndex = byteOffset + 1; + while (remainingBitsToRead > 8) { + data[currentByteIndex++] = (byte) (value >>> (remainingBitsToRead - 8)); + remainingBitsToRead -= 8; + } + int lastByteRightPaddingSize = 8 - remainingBitsToRead; + data[currentByteIndex] &= (1 << lastByteRightPaddingSize) - 1; + int lastByteInput = value & ((1 << remainingBitsToRead) - 1); + data[currentByteIndex] |= lastByteInput << lastByteRightPaddingSize; + skipBits(numBits); + assertValidOffset(); + } + private void assertValidOffset() { // It is fine for position to be at the end of the array, but no further. Assertions.checkState(byteOffset >= 0 diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java index 611584a38c..438643b933 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java @@ -169,6 +169,73 @@ public final class ParsableBitArrayTest { assertReadBitsToEnd(16); } + @Test + public void testPutBitsWithinByte() { + ParsableBitArray output = new ParsableBitArray(new byte[4]); + output.skipBits(1); + + output.putInt(0x3F, 5); + + output.setPosition(0); + assertThat(output.readBits(8)).isEqualTo(0x1F << 2); // Check that only 5 bits are modified. + } + + @Test + public void testPutBitsAcrossTwoBytes() { + ParsableBitArray output = new ParsableBitArray(new byte[4]); + output.setPosition(12); + + output.putInt(0xFF, 8); + output.setPosition(8); + + assertThat(output.readBits(16)).isEqualTo(0x0FF0); + } + + @Test + public void testPutBitsAcrossMultipleBytes() { + ParsableBitArray output = new ParsableBitArray(new byte[8]); + output.setPosition(31); // Writing starts at 31 to test the 30th bit is not modified. + + output.putInt(0xFF146098, 30); // Write only 30 to test the 61st bit is not modified. + + output.setPosition(30); + assertThat(output.readBits(32)).isEqualTo(0x3F146098 << 1); + } + + @Test + public void testPut32Bits() { + ParsableBitArray output = new ParsableBitArray(new byte[5]); + output.setPosition(4); + + output.putInt(0xFF146098, 32); + + output.setPosition(4); + assertThat(output.readBits(32)).isEqualTo(0xFF146098); + } + + @Test + public void testPutFullBytes() { + ParsableBitArray output = new ParsableBitArray(new byte[2]); + + output.putInt(0x81, 8); + + output.setPosition(0); + assertThat(output.readBits(8)).isEqualTo(0x81); + } + + @Test + public void testNoOverwriting() { + ParsableBitArray output = + new ParsableBitArray( + new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); + output.setPosition(1); + + output.putInt(0, 30); + + output.setPosition(0); + assertThat(output.readBits(32)).isEqualTo(0x80000001); + } + private void assertReadBitsToEnd(int expectedStartPosition) { int position = testArray.getPosition(); assertThat(position).isEqualTo(expectedStartPosition);