Commit b5100886 by Oliver Woodman

Fix EIA-608 issues.

- Data needs to be unescaped before it's passed to SeiReader.
- SeiReader should loop over potentially multiple child messages.
- I also changed the sample passed to the EIA-608 renderer so that
  it's the entire sei message payload. The first 8 bytes are
  unnecessary, but it seems nicer conceptually to do it this way.

Issue: #295
parent 00ec7ef0
......@@ -44,6 +44,8 @@ import java.util.List;
private final NalUnitTargetBuffer pps;
private final NalUnitTargetBuffer sei;
private int scratchEscapeCount;
private int[] scratchEscapePositions;
private boolean isKeyframe;
public H264Reader(BufferPool bufferPool, SeiReader seiReader) {
......@@ -53,6 +55,7 @@ import java.util.List;
sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128);
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);
scratchEscapePositions = new int[10];
}
@Override
......@@ -133,7 +136,8 @@ import java.util.List;
sps.endNalUnit(discardPadding);
pps.endNalUnit(discardPadding);
if (sei.endNalUnit(discardPadding)) {
seiReader.read(sei.nalData, 0, pesTimeUs);
int unescapedLength = unescapeStream(sei.nalData, sei.nalLength);
seiReader.read(sei.nalData, 0, unescapedLength, pesTimeUs);
}
}
......@@ -147,8 +151,8 @@ import java.util.List;
initializationData.add(ppsData);
// Unescape and then parse the SPS unit.
byte[] unescapedSps = unescapeStream(spsData, 0, spsData.length);
ParsableBitArray bitArray = new ParsableBitArray(unescapedSps);
unescapeStream(sps.nalData, sps.nalLength);
ParsableBitArray bitArray = new ParsableBitArray(sps.nalData);
bitArray.skipBits(32); // NAL header
int profileIdc = bitArray.readBits(8);
bitArray.skipBits(16); // constraint bits (6), reserved (2) and level_idc (8)
......@@ -242,36 +246,45 @@ import java.util.List;
}
/**
* Replaces occurrences of [0, 0, 3] with [0, 0].
* Unescapes {@code data} up to the specified limit, replacing occurrences of [0, 0, 3] with
* [0, 0]. The unescaped data is returned in-place, with the return value indicating its length.
* <p>
* See ISO/IEC 14496-10:2005(E) page 36 for more information.
*
* @param data The data to unescape.
* @param limit The limit (exclusive) of the data to unescape.
* @return The length of the unescaped data.
*/
private byte[] unescapeStream(byte[] data, int offset, int limit) {
int position = offset;
List<Integer> escapePositions = new ArrayList<Integer>();
private int unescapeStream(byte[] data, int limit) {
int position = 0;
scratchEscapeCount = 0;
while (position < limit) {
position = findNextUnescapeIndex(data, position, limit);
if (position < limit) {
escapePositions.add(position);
if (scratchEscapePositions.length <= scratchEscapeCount) {
// Grow scratchEscapePositions to hold a larger number of positions.
scratchEscapePositions = Arrays.copyOf(scratchEscapePositions,
scratchEscapePositions.length * 2);
}
scratchEscapePositions[scratchEscapeCount++] = position;
position += 3;
}
}
int escapeCount = escapePositions.size();
int escapedPosition = offset; // The position being read from.
int unescapedLength = limit - scratchEscapeCount;
int escapedPosition = 0; // The position being read from.
int unescapedPosition = 0; // The position being written to.
byte[] unescapedData = new byte[limit - offset - escapeCount];
for (int i = 0; i < escapeCount; i++) {
int nextEscapePosition = escapePositions.get(i);
for (int i = 0; i < scratchEscapeCount; i++) {
int nextEscapePosition = scratchEscapePositions[i];
int copyLength = nextEscapePosition - escapedPosition;
System.arraycopy(data, escapedPosition, unescapedData, unescapedPosition, copyLength);
System.arraycopy(data, escapedPosition, data, unescapedPosition, copyLength);
escapedPosition += copyLength + 3;
unescapedPosition += copyLength + 2;
}
int remainingLength = unescapedData.length - unescapedPosition;
System.arraycopy(data, escapedPosition, unescapedData, unescapedPosition, remainingLength);
return unescapedData;
int remainingLength = unescapedLength - unescapedPosition;
System.arraycopy(data, escapedPosition, data, unescapedPosition, remainingLength);
return unescapedLength;
}
private int findNextUnescapeIndex(byte[] bytes, int offset, int limit) {
......
......@@ -36,14 +36,33 @@ import com.google.android.exoplayer.util.ParsableByteArray;
seiBuffer = new ParsableByteArray();
}
public void read(byte[] data, int position, long pesTimeUs) {
seiBuffer.reset(data, data.length);
public void read(byte[] data, int position, int limit, long pesTimeUs) {
seiBuffer.reset(data, limit);
// Skip the NAL prefix and type.
seiBuffer.setPosition(position + 4);
int ccDataSize = Eia608Parser.parseHeader(seiBuffer);
if (ccDataSize > 0) {
startSample(pesTimeUs);
appendData(seiBuffer, ccDataSize);
commitSample(true);
int b;
while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) {
// Parse payload type.
int payloadType = 0;
do {
b = seiBuffer.readUnsignedByte();
payloadType += b;
} while (b == 0xFF);
// Parse payload size.
int payloadSize = 0;
do {
b = seiBuffer.readUnsignedByte();
payloadSize += b;
} while (b == 0xFF);
// Process the payload. We only support EIA-608 payloads currently.
if (Eia608Parser.inspectSeiMessage(payloadType, payloadSize, seiBuffer)) {
startSample(pesTimeUs);
appendData(seiBuffer, payloadSize);
commitSample(true);
} else {
seiBuffer.skip(payloadSize);
}
}
}
......
......@@ -97,14 +97,17 @@ public class Eia608Parser {
}
/* package */ ClosedCaptionList parse(SampleHolder sampleHolder) {
if (sampleHolder.size <= 0) {
if (sampleHolder.size < 10) {
return null;
}
captions.clear();
stringBuilder.setLength(0);
seiBuffer.reset(sampleHolder.data.array());
seiBuffer.skipBits(3); // reserved + process_cc_data_flag + zero_bit
// country_code (8) + provider_code (16) + user_identifier (32) + user_data_type_code (8) +
// reserved (1) + process_cc_data_flag (1) + zero_bit (1)
seiBuffer.skipBits(67);
int ccCount = seiBuffer.readBits(5);
seiBuffer.skipBits(8);
......@@ -177,52 +180,28 @@ public class Eia608Parser {
}
/**
* Parses the beginning of SEI data and returns the size of underlying contains closed captions
* data following the header. Returns 0 if the SEI doesn't contain any closed captions data.
* Inspects an sei message to determine whether it contains EIA-608.
* <p>
* The position of {@code payload} is left unchanged.
*
* @param seiBuffer The buffer to read from.
* @return The size of closed captions data.
* @param payloadType The payload type of the message.
* @param payloadLength The length of the payload.
* @param payload A {@link ParsableByteArray} containing the payload.
* @return True if the sei message contains EIA-608. False otherwise.
*/
public static int parseHeader(ParsableByteArray seiBuffer) {
int b = 0;
int payloadType = 0;
do {
b = seiBuffer.readUnsignedByte();
payloadType += b;
} while (b == 0xFF);
if (payloadType != PAYLOAD_TYPE_CC) {
return 0;
}
int payloadSize = 0;
do {
b = seiBuffer.readUnsignedByte();
payloadSize += b;
} while (b == 0xFF);
if (payloadSize <= 0) {
return 0;
}
int countryCode = seiBuffer.readUnsignedByte();
if (countryCode != COUNTRY_CODE) {
return 0;
}
int providerCode = seiBuffer.readUnsignedShort();
if (providerCode != PROVIDER_CODE) {
return 0;
}
int userIdentifier = seiBuffer.readInt();
if (userIdentifier != USER_ID) {
return 0;
}
int userDataTypeCode = seiBuffer.readUnsignedByte();
if (userDataTypeCode != USER_DATA_TYPE_CODE) {
return 0;
public static boolean inspectSeiMessage(int payloadType, int payloadLength,
ParsableByteArray payload) {
if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) {
return false;
}
return payloadSize;
int startPosition = payload.getPosition();
int countryCode = payload.readUnsignedByte();
int providerCode = payload.readUnsignedShort();
int userIdentifier = payload.readInt();
int userDataTypeCode = payload.readUnsignedByte();
payload.setPosition(startPosition);
return countryCode == COUNTRY_CODE && providerCode == PROVIDER_CODE
&& userIdentifier == USER_ID && userDataTypeCode == USER_DATA_TYPE_CODE;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment