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.
|
completed.
|
||||||
* ID3: Better handle malformed ID3 data
|
* ID3: Better handle malformed ID3 data
|
||||||
([#3792](https://github.com/google/ExoPlayer/issues/3792).
|
([#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 ###
|
### 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.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility methods for parsing DTS frames.
|
* Utility methods for parsing DTS frames.
|
||||||
*/
|
*/
|
||||||
public final class DtsUtil {
|
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.
|
* 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,
|
384, 448, 512, 640, 768, 896, 1024, 1152, 1280, 1536, 1920, 2048, 2304, 2560, 2688, 2816,
|
||||||
2823, 2944, 3072, 3840, 4096, 6144, 7680};
|
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
|
* Returns the DTS format given {@code data} containing the DTS frame according to ETSI TS 102 114
|
||||||
* subsections 5.3/5.4.
|
* subsections 5.3/5.4.
|
||||||
@ -57,8 +81,8 @@ public final class DtsUtil {
|
|||||||
*/
|
*/
|
||||||
public static Format parseDtsFormat(byte[] frame, String trackId, String language,
|
public static Format parseDtsFormat(byte[] frame, String trackId, String language,
|
||||||
DrmInitData drmInitData) {
|
DrmInitData drmInitData) {
|
||||||
ParsableBitArray frameBits = new ParsableBitArray(frame);
|
ParsableBitArray frameBits = getNormalizedFrameHeader(frame);
|
||||||
frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
|
frameBits.skipBits(32 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
|
||||||
int amode = frameBits.readBits(6);
|
int amode = frameBits.readBits(6);
|
||||||
int channelCount = CHANNELS_BY_AMODE[amode];
|
int channelCount = CHANNELS_BY_AMODE[amode];
|
||||||
int sfreq = frameBits.readBits(4);
|
int sfreq = frameBits.readBits(4);
|
||||||
@ -79,8 +103,21 @@ public final class DtsUtil {
|
|||||||
* @return The number of audio samples represented by the frame.
|
* @return The number of audio samples represented by the frame.
|
||||||
*/
|
*/
|
||||||
public static int parseDtsAudioSampleCount(byte[] data) {
|
public static int parseDtsAudioSampleCount(byte[] data) {
|
||||||
// See ETSI TS 102 114 subsection 5.4.1.
|
int nblks;
|
||||||
int nblks = ((data[4] & 0x01) << 6) | ((data[5] & 0xFC) >> 2);
|
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;
|
return (nblks + 1) * 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,8 +131,21 @@ public final class DtsUtil {
|
|||||||
public static int parseDtsAudioSampleCount(ByteBuffer buffer) {
|
public static int parseDtsAudioSampleCount(ByteBuffer buffer) {
|
||||||
// See ETSI TS 102 114 subsection 5.4.1.
|
// See ETSI TS 102 114 subsection 5.4.1.
|
||||||
int position = buffer.position();
|
int position = buffer.position();
|
||||||
int nblks = ((buffer.get(position + 4) & 0x01) << 6)
|
int nblks;
|
||||||
| ((buffer.get(position + 5) & 0xFC) >> 2);
|
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;
|
return (nblks + 1) * 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,9 +156,59 @@ public final class DtsUtil {
|
|||||||
* @return The frame's size in bytes.
|
* @return The frame's size in bytes.
|
||||||
*/
|
*/
|
||||||
public static int getDtsFrameSize(byte[] data) {
|
public static int getDtsFrameSize(byte[] data) {
|
||||||
return (((data[5] & 0x02) << 12)
|
int fsize;
|
||||||
| ((data[6] & 0xFF) << 4)
|
boolean uses14BitPerWord = false;
|
||||||
| ((data[7] & 0xF0) >> 4)) + 1;
|
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() {}
|
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_HEADER = 1;
|
||||||
private static final int STATE_READING_SAMPLE = 2;
|
private static final int STATE_READING_SAMPLE = 2;
|
||||||
|
|
||||||
private static final int HEADER_SIZE = 15;
|
private static final int HEADER_SIZE = 18;
|
||||||
private static final int SYNC_VALUE = 0x7FFE8001;
|
|
||||||
private static final int SYNC_VALUE_SIZE = 4;
|
|
||||||
|
|
||||||
private final ParsableByteArray headerScratchBytes;
|
private final ParsableByteArray headerScratchBytes;
|
||||||
private final String language;
|
private final String language;
|
||||||
@ -63,10 +61,6 @@ public final class DtsReader implements ElementaryStreamReader {
|
|||||||
*/
|
*/
|
||||||
public DtsReader(String language) {
|
public DtsReader(String language) {
|
||||||
headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]);
|
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;
|
state = STATE_FINDING_SYNC;
|
||||||
this.language = language;
|
this.language = language;
|
||||||
}
|
}
|
||||||
@ -96,7 +90,6 @@ public final class DtsReader implements ElementaryStreamReader {
|
|||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_FINDING_SYNC:
|
case STATE_FINDING_SYNC:
|
||||||
if (skipToNextSync(data)) {
|
if (skipToNextSync(data)) {
|
||||||
bytesRead = SYNC_VALUE_SIZE;
|
|
||||||
state = STATE_READING_HEADER;
|
state = STATE_READING_HEADER;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -154,7 +147,12 @@ public final class DtsReader implements ElementaryStreamReader {
|
|||||||
while (pesBuffer.bytesLeft() > 0) {
|
while (pesBuffer.bytesLeft() > 0) {
|
||||||
syncBytes <<= 8;
|
syncBytes <<= 8;
|
||||||
syncBytes |= pesBuffer.readUnsignedByte();
|
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;
|
syncBytes = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -263,6 +263,40 @@ public final class ParsableBitArray {
|
|||||||
assertValidOffset();
|
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() {
|
private void assertValidOffset() {
|
||||||
// It is fine for position to be at the end of the array, but no further.
|
// It is fine for position to be at the end of the array, but no further.
|
||||||
Assertions.checkState(byteOffset >= 0
|
Assertions.checkState(byteOffset >= 0
|
||||||
|
@ -169,6 +169,73 @@ public final class ParsableBitArrayTest {
|
|||||||
assertReadBitsToEnd(16);
|
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) {
|
private void assertReadBitsToEnd(int expectedStartPosition) {
|
||||||
int position = testArray.getPosition();
|
int position = testArray.getPosition();
|
||||||
assertThat(position).isEqualTo(expectedStartPosition);
|
assertThat(position).isEqualTo(expectedStartPosition);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user