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