Merge pull request #9967 from jruesga:cea708-handle-multiple-service-blocks

PiperOrigin-RevId: 444816821
This commit is contained in:
Ian Baker 2022-05-09 10:18:14 +01:00
commit 2898d41f4a
2 changed files with 146 additions and 132 deletions

View File

@ -30,6 +30,8 @@
* SSA: Support `OutlineColour` style setting when `BorderStyle == 3` (i.e.
`OutlineColour` sets the background of the cue)
([#8435](https://github.com/google/ExoPlayer/issues/8435)).
* CEA-708: Parse data into multiple service blocks and ignore blocks not
associated with the currently selected service number.
* Extractors:
* Matroska: Parse `DiscardPadding` for Opus tracks.
* Parse bitrates from `esds` boxes.

View File

@ -145,7 +145,7 @@ public final class Cea708Decoder extends CeaDecoder {
private static final int CHARACTER_UPPER_LEFT_BORDER = 0x7F;
private final ParsableByteArray ccData;
private final ParsableBitArray serviceBlockPacket;
private final ParsableBitArray captionChannelPacketData;
private int previousSequenceNumber;
// TODO: Use isWideAspectRatio in decoding.
@SuppressWarnings({"unused", "FieldCanBeLocal"})
@ -163,7 +163,7 @@ public final class Cea708Decoder extends CeaDecoder {
public Cea708Decoder(int accessibilityChannel, @Nullable List<byte[]> initializationData) {
ccData = new ParsableByteArray();
serviceBlockPacket = new ParsableBitArray();
captionChannelPacketData = new ParsableBitArray();
previousSequenceNumber = C.INDEX_UNSET;
selectedServiceNumber = accessibilityChannel == Format.NO_VALUE ? 1 : accessibilityChannel;
isWideAspectRatio =
@ -299,71 +299,83 @@ public final class Cea708Decoder extends CeaDecoder {
// we have received.
}
serviceBlockPacket.reset(currentDtvCcPacket.packetData, currentDtvCcPacket.currentIndex);
int serviceNumber = serviceBlockPacket.readBits(3);
int blockSize = serviceBlockPacket.readBits(5);
if (serviceNumber == 7) {
// extended service numbers
serviceBlockPacket.skipBits(2);
serviceNumber = serviceBlockPacket.readBits(6);
if (serviceNumber < 7) {
Log.w(TAG, "Invalid extended service number: " + serviceNumber);
}
}
// Ignore packets in which blockSize is 0
if (blockSize == 0) {
if (serviceNumber != 0) {
Log.w(TAG, "serviceNumber is non-zero (" + serviceNumber + ") when blockSize is 0");
}
return;
}
if (serviceNumber != selectedServiceNumber) {
return;
}
// The cues should be updated if we receive a C0 ETX command, any C1 command, or if after
// processing the service block any text has been added to the buffer. See CEA-708-B Section
// 8.10.4 for more details.
boolean cuesNeedUpdate = false;
int blockEndBitPosition = serviceBlockPacket.getPosition() + (blockSize * 8);
while (serviceBlockPacket.bitsLeft() > 0
&& serviceBlockPacket.getPosition() < blockEndBitPosition) {
int command = serviceBlockPacket.readBits(8);
if (command != COMMAND_EXT1) {
if (command <= GROUP_C0_END) {
handleC0Command(command);
// If the C0 command was an ETX command, the cues are updated in handleC0Command.
} else if (command <= GROUP_G0_END) {
handleG0Character(command);
cuesNeedUpdate = true;
} else if (command <= GROUP_C1_END) {
handleC1Command(command);
cuesNeedUpdate = true;
} else if (command <= GROUP_G1_END) {
handleG1Character(command);
cuesNeedUpdate = true;
} else {
Log.w(TAG, "Invalid base command: " + command);
// Streams with multiple embedded CC tracks (different language tracks) can be delivered
// in the same frame packet, so captionChannelPacketData can contain service blocks with
// different service numbers.
//
// We iterate over the full buffer until we find a null service block or until the buffer is
// exhausted. On each iteration we process a single service block. If the block has a service
// number different to the currently selected service, then we skip it and continue with the
// next service block.
captionChannelPacketData.reset(currentDtvCcPacket.packetData, currentDtvCcPacket.currentIndex);
while (captionChannelPacketData.bitsLeft() > 0) {
// Parse the Standard Service Block Header (see CEA-708B 6.2.1)
int serviceNumber = captionChannelPacketData.readBits(3);
int blockSize = captionChannelPacketData.readBits(5);
if (serviceNumber == 7) {
// Parse the Extended Service Block Header (see CEA-708B 6.2.2)
captionChannelPacketData.skipBits(2);
serviceNumber = captionChannelPacketData.readBits(6);
if (serviceNumber < 7) {
Log.w(TAG, "Invalid extended service number: " + serviceNumber);
}
} else {
// Read the extended command
command = serviceBlockPacket.readBits(8);
if (command <= GROUP_C2_END) {
handleC2Command(command);
} else if (command <= GROUP_G2_END) {
handleG2Character(command);
cuesNeedUpdate = true;
} else if (command <= GROUP_C3_END) {
handleC3Command(command);
} else if (command <= GROUP_G3_END) {
handleG3Character(command);
cuesNeedUpdate = true;
}
// Ignore packets with the Null Service Block Header (see CEA-708B 6.2.3)
if (blockSize == 0) {
if (serviceNumber != 0) {
Log.w(TAG, "serviceNumber is non-zero (" + serviceNumber + ") when blockSize is 0");
}
break;
}
if (serviceNumber != selectedServiceNumber) {
captionChannelPacketData.skipBytes(blockSize);
continue;
}
// Process only the information for the current service block (there could be
// more data in the buffer, but it is not part of the current service block).
int endBlockPosition = captionChannelPacketData.getPosition() + (blockSize * 8);
while (captionChannelPacketData.getPosition() < endBlockPosition) {
int command = captionChannelPacketData.readBits(8);
if (command != COMMAND_EXT1) {
if (command <= GROUP_C0_END) {
handleC0Command(command);
// If the C0 command was an ETX command, the cues are updated in handleC0Command.
} else if (command <= GROUP_G0_END) {
handleG0Character(command);
cuesNeedUpdate = true;
} else if (command <= GROUP_C1_END) {
handleC1Command(command);
cuesNeedUpdate = true;
} else if (command <= GROUP_G1_END) {
handleG1Character(command);
cuesNeedUpdate = true;
} else {
Log.w(TAG, "Invalid base command: " + command);
}
} else {
Log.w(TAG, "Invalid extended command: " + command);
// Read the extended command
command = captionChannelPacketData.readBits(8);
if (command <= GROUP_C2_END) {
handleC2Command(command);
} else if (command <= GROUP_G2_END) {
handleG2Character(command);
cuesNeedUpdate = true;
} else if (command <= GROUP_C3_END) {
handleC3Command(command);
} else if (command <= GROUP_G3_END) {
handleG3Character(command);
cuesNeedUpdate = true;
} else {
Log.w(TAG, "Invalid extended command: " + command);
}
}
}
}
@ -396,10 +408,10 @@ public final class Cea708Decoder extends CeaDecoder {
default:
if (command >= COMMAND_EXT1_START && command <= COMMAND_EXT1_END) {
Log.w(TAG, "Currently unsupported COMMAND_EXT1 Command: " + command);
serviceBlockPacket.skipBits(8);
captionChannelPacketData.skipBits(8);
} else if (command >= COMMAND_P16_START && command <= COMMAND_P16_END) {
Log.w(TAG, "Currently unsupported COMMAND_P16 Command: " + command);
serviceBlockPacket.skipBits(16);
captionChannelPacketData.skipBits(16);
} else {
Log.w(TAG, "Invalid C0 command: " + command);
}
@ -425,28 +437,28 @@ public final class Cea708Decoder extends CeaDecoder {
break;
case COMMAND_CLW:
for (int i = 1; i <= NUM_WINDOWS; i++) {
if (serviceBlockPacket.readBit()) {
if (captionChannelPacketData.readBit()) {
cueInfoBuilders[NUM_WINDOWS - i].clear();
}
}
break;
case COMMAND_DSW:
for (int i = 1; i <= NUM_WINDOWS; i++) {
if (serviceBlockPacket.readBit()) {
if (captionChannelPacketData.readBit()) {
cueInfoBuilders[NUM_WINDOWS - i].setVisibility(true);
}
}
break;
case COMMAND_HDW:
for (int i = 1; i <= NUM_WINDOWS; i++) {
if (serviceBlockPacket.readBit()) {
if (captionChannelPacketData.readBit()) {
cueInfoBuilders[NUM_WINDOWS - i].setVisibility(false);
}
}
break;
case COMMAND_TGW:
for (int i = 1; i <= NUM_WINDOWS; i++) {
if (serviceBlockPacket.readBit()) {
if (captionChannelPacketData.readBit()) {
CueInfoBuilder cueInfoBuilder = cueInfoBuilders[NUM_WINDOWS - i];
cueInfoBuilder.setVisibility(!cueInfoBuilder.isVisible());
}
@ -454,14 +466,14 @@ public final class Cea708Decoder extends CeaDecoder {
break;
case COMMAND_DLW:
for (int i = 1; i <= NUM_WINDOWS; i++) {
if (serviceBlockPacket.readBit()) {
if (captionChannelPacketData.readBit()) {
cueInfoBuilders[NUM_WINDOWS - i].reset();
}
}
break;
case COMMAND_DLY:
// TODO: Add support for delay commands.
serviceBlockPacket.skipBits(8);
captionChannelPacketData.skipBits(8);
break;
case COMMAND_DLC:
// TODO: Add support for delay commands.
@ -472,7 +484,7 @@ public final class Cea708Decoder extends CeaDecoder {
case COMMAND_SPA:
if (!currentCueInfoBuilder.isDefined()) {
// ignore this command if the current window/cue isn't defined
serviceBlockPacket.skipBits(16);
captionChannelPacketData.skipBits(16);
} else {
handleSetPenAttributes();
}
@ -480,7 +492,7 @@ public final class Cea708Decoder extends CeaDecoder {
case COMMAND_SPC:
if (!currentCueInfoBuilder.isDefined()) {
// ignore this command if the current window/cue isn't defined
serviceBlockPacket.skipBits(24);
captionChannelPacketData.skipBits(24);
} else {
handleSetPenColor();
}
@ -488,7 +500,7 @@ public final class Cea708Decoder extends CeaDecoder {
case COMMAND_SPL:
if (!currentCueInfoBuilder.isDefined()) {
// ignore this command if the current window/cue isn't defined
serviceBlockPacket.skipBits(16);
captionChannelPacketData.skipBits(16);
} else {
handleSetPenLocation();
}
@ -496,7 +508,7 @@ public final class Cea708Decoder extends CeaDecoder {
case COMMAND_SWA:
if (!currentCueInfoBuilder.isDefined()) {
// ignore this command if the current window/cue isn't defined
serviceBlockPacket.skipBits(32);
captionChannelPacketData.skipBits(32);
} else {
handleSetWindowAttributes();
}
@ -527,27 +539,27 @@ public final class Cea708Decoder extends CeaDecoder {
if (command <= 0x07) {
// Do nothing.
} else if (command <= 0x0F) {
serviceBlockPacket.skipBits(8);
captionChannelPacketData.skipBits(8);
} else if (command <= 0x17) {
serviceBlockPacket.skipBits(16);
captionChannelPacketData.skipBits(16);
} else if (command <= 0x1F) {
serviceBlockPacket.skipBits(24);
captionChannelPacketData.skipBits(24);
}
}
private void handleC3Command(int command) {
// C3 Table doesn't contain any commands in CEA-708-B, but we do need to skip bytes
if (command <= 0x87) {
serviceBlockPacket.skipBits(32);
captionChannelPacketData.skipBits(32);
} else if (command <= 0x8F) {
serviceBlockPacket.skipBits(40);
captionChannelPacketData.skipBits(40);
} else if (command <= 0x9F) {
// 90-9F are variable length codes; the first byte defines the header with the first
// 2 bits specifying the type and the last 6 bits specifying the remaining length of the
// command in bytes
serviceBlockPacket.skipBits(2);
int length = serviceBlockPacket.readBits(6);
serviceBlockPacket.skipBits(8 * length);
captionChannelPacketData.skipBits(2);
int length = captionChannelPacketData.readBits(6);
captionChannelPacketData.skipBits(8 * length);
}
}
@ -663,14 +675,14 @@ public final class Cea708Decoder extends CeaDecoder {
private void handleSetPenAttributes() {
// the SetPenAttributes command contains 2 bytes of data
// first byte
int textTag = serviceBlockPacket.readBits(4);
int offset = serviceBlockPacket.readBits(2);
int penSize = serviceBlockPacket.readBits(2);
int textTag = captionChannelPacketData.readBits(4);
int offset = captionChannelPacketData.readBits(2);
int penSize = captionChannelPacketData.readBits(2);
// second byte
boolean italicsToggle = serviceBlockPacket.readBit();
boolean underlineToggle = serviceBlockPacket.readBit();
int edgeType = serviceBlockPacket.readBits(3);
int fontStyle = serviceBlockPacket.readBits(3);
boolean italicsToggle = captionChannelPacketData.readBit();
boolean underlineToggle = captionChannelPacketData.readBit();
int edgeType = captionChannelPacketData.readBits(3);
int fontStyle = captionChannelPacketData.readBits(3);
currentCueInfoBuilder.setPenAttributes(
textTag, offset, penSize, italicsToggle, underlineToggle, edgeType, fontStyle);
@ -679,24 +691,24 @@ public final class Cea708Decoder extends CeaDecoder {
private void handleSetPenColor() {
// the SetPenColor command contains 3 bytes of data
// first byte
int foregroundO = serviceBlockPacket.readBits(2);
int foregroundR = serviceBlockPacket.readBits(2);
int foregroundG = serviceBlockPacket.readBits(2);
int foregroundB = serviceBlockPacket.readBits(2);
int foregroundO = captionChannelPacketData.readBits(2);
int foregroundR = captionChannelPacketData.readBits(2);
int foregroundG = captionChannelPacketData.readBits(2);
int foregroundB = captionChannelPacketData.readBits(2);
int foregroundColor =
CueInfoBuilder.getArgbColorFromCeaColor(foregroundR, foregroundG, foregroundB, foregroundO);
// second byte
int backgroundO = serviceBlockPacket.readBits(2);
int backgroundR = serviceBlockPacket.readBits(2);
int backgroundG = serviceBlockPacket.readBits(2);
int backgroundB = serviceBlockPacket.readBits(2);
int backgroundO = captionChannelPacketData.readBits(2);
int backgroundR = captionChannelPacketData.readBits(2);
int backgroundG = captionChannelPacketData.readBits(2);
int backgroundB = captionChannelPacketData.readBits(2);
int backgroundColor =
CueInfoBuilder.getArgbColorFromCeaColor(backgroundR, backgroundG, backgroundB, backgroundO);
// third byte
serviceBlockPacket.skipBits(2); // null padding
int edgeR = serviceBlockPacket.readBits(2);
int edgeG = serviceBlockPacket.readBits(2);
int edgeB = serviceBlockPacket.readBits(2);
captionChannelPacketData.skipBits(2); // null padding
int edgeR = captionChannelPacketData.readBits(2);
int edgeG = captionChannelPacketData.readBits(2);
int edgeB = captionChannelPacketData.readBits(2);
int edgeColor = CueInfoBuilder.getArgbColorFromCeaColor(edgeR, edgeG, edgeB);
currentCueInfoBuilder.setPenColor(foregroundColor, backgroundColor, edgeColor);
@ -705,11 +717,11 @@ public final class Cea708Decoder extends CeaDecoder {
private void handleSetPenLocation() {
// the SetPenLocation command contains 2 bytes of data
// first byte
serviceBlockPacket.skipBits(4);
int row = serviceBlockPacket.readBits(4);
captionChannelPacketData.skipBits(4);
int row = captionChannelPacketData.readBits(4);
// second byte
serviceBlockPacket.skipBits(2);
int column = serviceBlockPacket.readBits(6);
captionChannelPacketData.skipBits(2);
int column = captionChannelPacketData.readBits(6);
currentCueInfoBuilder.setPenLocation(row, column);
}
@ -717,28 +729,28 @@ public final class Cea708Decoder extends CeaDecoder {
private void handleSetWindowAttributes() {
// the SetWindowAttributes command contains 4 bytes of data
// first byte
int fillO = serviceBlockPacket.readBits(2);
int fillR = serviceBlockPacket.readBits(2);
int fillG = serviceBlockPacket.readBits(2);
int fillB = serviceBlockPacket.readBits(2);
int fillO = captionChannelPacketData.readBits(2);
int fillR = captionChannelPacketData.readBits(2);
int fillG = captionChannelPacketData.readBits(2);
int fillB = captionChannelPacketData.readBits(2);
int fillColor = CueInfoBuilder.getArgbColorFromCeaColor(fillR, fillG, fillB, fillO);
// second byte
int borderType = serviceBlockPacket.readBits(2); // only the lower 2 bits of borderType
int borderR = serviceBlockPacket.readBits(2);
int borderG = serviceBlockPacket.readBits(2);
int borderB = serviceBlockPacket.readBits(2);
int borderType = captionChannelPacketData.readBits(2); // only the lower 2 bits of borderType
int borderR = captionChannelPacketData.readBits(2);
int borderG = captionChannelPacketData.readBits(2);
int borderB = captionChannelPacketData.readBits(2);
int borderColor = CueInfoBuilder.getArgbColorFromCeaColor(borderR, borderG, borderB);
// third byte
if (serviceBlockPacket.readBit()) {
if (captionChannelPacketData.readBit()) {
borderType |= 0x04; // set the top bit of the 3-bit borderType
}
boolean wordWrapToggle = serviceBlockPacket.readBit();
int printDirection = serviceBlockPacket.readBits(2);
int scrollDirection = serviceBlockPacket.readBits(2);
int justification = serviceBlockPacket.readBits(2);
boolean wordWrapToggle = captionChannelPacketData.readBit();
int printDirection = captionChannelPacketData.readBits(2);
int scrollDirection = captionChannelPacketData.readBits(2);
int justification = captionChannelPacketData.readBits(2);
// fourth byte
// Note that we don't intend to support display effects
serviceBlockPacket.skipBits(8); // effectSpeed(4), effectDirection(2), displayEffect(2)
captionChannelPacketData.skipBits(8); // effectSpeed(4), effectDirection(2), displayEffect(2)
currentCueInfoBuilder.setWindowAttributes(
fillColor,
@ -755,26 +767,26 @@ public final class Cea708Decoder extends CeaDecoder {
// the DefineWindow command contains 6 bytes of data
// first byte
serviceBlockPacket.skipBits(2); // null padding
boolean visible = serviceBlockPacket.readBit();
boolean rowLock = serviceBlockPacket.readBit();
boolean columnLock = serviceBlockPacket.readBit();
int priority = serviceBlockPacket.readBits(3);
captionChannelPacketData.skipBits(2); // null padding
boolean visible = captionChannelPacketData.readBit();
boolean rowLock = captionChannelPacketData.readBit();
boolean columnLock = captionChannelPacketData.readBit();
int priority = captionChannelPacketData.readBits(3);
// second byte
boolean relativePositioning = serviceBlockPacket.readBit();
int verticalAnchor = serviceBlockPacket.readBits(7);
boolean relativePositioning = captionChannelPacketData.readBit();
int verticalAnchor = captionChannelPacketData.readBits(7);
// third byte
int horizontalAnchor = serviceBlockPacket.readBits(8);
int horizontalAnchor = captionChannelPacketData.readBits(8);
// fourth byte
int anchorId = serviceBlockPacket.readBits(4);
int rowCount = serviceBlockPacket.readBits(4);
int anchorId = captionChannelPacketData.readBits(4);
int rowCount = captionChannelPacketData.readBits(4);
// fifth byte
serviceBlockPacket.skipBits(2); // null padding
int columnCount = serviceBlockPacket.readBits(6);
captionChannelPacketData.skipBits(2); // null padding
int columnCount = captionChannelPacketData.readBits(6);
// sixth byte
serviceBlockPacket.skipBits(2); // null padding
int windowStyle = serviceBlockPacket.readBits(3);
int penStyle = serviceBlockPacket.readBits(3);
captionChannelPacketData.skipBits(2); // null padding
int windowStyle = captionChannelPacketData.readBits(3);
int penStyle = captionChannelPacketData.readBits(3);
cueInfoBuilder.defineWindow(
visible,