mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add little endian and 14-bit mode support for DtsReader
Issue:#3340 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185973510
This commit is contained in:
parent
454ec5a2d7
commit
b36db1a87e
@ -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 ###
|
||||
|
||||
|
@ -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() {}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user