Skip to content

Commit

Permalink
Add little endian and 14-bit mode support for DtsReader
Browse files Browse the repository at this point in the history
Issue:#3340

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=185973510
  • Loading branch information
AquilesCanta authored and ojw28 committed Feb 20, 2018
1 parent 454ec5a commit b36db1a
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 18 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ###

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@
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.
*/
Expand All @@ -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.
Expand All @@ -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);
Expand All @@ -79,8 +103,21 @@ public static Format parseDtsFormat(byte[] frame, String trackId, String languag
* @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;
}

Expand All @@ -94,8 +131,21 @@ public static int parseDtsAudioSampleCount(byte[] data) {
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;
}

Expand All @@ -106,9 +156,59 @@ public static int parseDtsAudioSampleCount(ByteBuffer buffer) {
* @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() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -96,7 +90,6 @@ public void consume(ParsableByteArray data) {
switch (state) {
case STATE_FINDING_SYNC:
if (skipToNextSync(data)) {
bytesRead = SYNC_VALUE_SIZE;
state = STATE_READING_HEADER;
}
break;
Expand Down Expand Up @@ -154,7 +147,12 @@ private boolean skipToNextSync(ParsableByteArray pesBuffer) {
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,40 @@ public void skipBytes(int length) {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,73 @@ public void testByteAlignFromByteAligned() {
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);
Expand Down

0 comments on commit b36db1a

Please sign in to comment.