CEA608: Fix repeated Special North American chars.
We currently handle most the control code logic after handling special characters. This includes filtering out repeated control codes and checking for the correct channel. As the special character sets are control codes as well, these checks should happen before parsing the characters. Issue:#6133 PiperOrigin-RevId: 256993672
This commit is contained in:
parent
ecd88c71d2
commit
e3af045adb
@ -25,6 +25,8 @@
|
|||||||
* SmoothStreaming: Parse text stream `Subtype` into `Format.roleFlags`.
|
* SmoothStreaming: Parse text stream `Subtype` into `Format.roleFlags`.
|
||||||
* FLV: Fix bug that caused playback of some live streams to not start
|
* FLV: Fix bug that caused playback of some live streams to not start
|
||||||
([#6111](https://github.com/google/ExoPlayer/issues/6111)).
|
([#6111](https://github.com/google/ExoPlayer/issues/6111)).
|
||||||
|
* CEA608: Fix repetition of special North American characters
|
||||||
|
([#6133](https://github.com/google/ExoPlayer/issues/6133)).
|
||||||
|
|
||||||
### 2.10.2 ###
|
### 2.10.2 ###
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ public final class Cea608Decoder extends CeaDecoder {
|
|||||||
private int captionMode;
|
private int captionMode;
|
||||||
private int captionRowCount;
|
private int captionRowCount;
|
||||||
|
|
||||||
private boolean captionValid;
|
private boolean isCaptionValid;
|
||||||
private boolean repeatableControlSet;
|
private boolean repeatableControlSet;
|
||||||
private byte repeatableControlCc1;
|
private byte repeatableControlCc1;
|
||||||
private byte repeatableControlCc2;
|
private byte repeatableControlCc2;
|
||||||
@ -300,7 +300,7 @@ public final class Cea608Decoder extends CeaDecoder {
|
|||||||
setCaptionMode(CC_MODE_UNKNOWN);
|
setCaptionMode(CC_MODE_UNKNOWN);
|
||||||
setCaptionRowCount(DEFAULT_CAPTIONS_ROW_COUNT);
|
setCaptionRowCount(DEFAULT_CAPTIONS_ROW_COUNT);
|
||||||
resetCueBuilders();
|
resetCueBuilders();
|
||||||
captionValid = false;
|
isCaptionValid = false;
|
||||||
repeatableControlSet = false;
|
repeatableControlSet = false;
|
||||||
repeatableControlCc1 = 0;
|
repeatableControlCc1 = 0;
|
||||||
repeatableControlCc2 = 0;
|
repeatableControlCc2 = 0;
|
||||||
@ -358,13 +358,19 @@ public final class Cea608Decoder extends CeaDecoder {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean repeatedControlPossible = repeatableControlSet;
|
boolean previousIsCaptionValid = isCaptionValid;
|
||||||
repeatableControlSet = false;
|
isCaptionValid =
|
||||||
|
(ccHeader & CC_VALID_FLAG) == CC_VALID_FLAG
|
||||||
|
&& ODD_PARITY_BYTE_TABLE[ccByte1]
|
||||||
|
&& ODD_PARITY_BYTE_TABLE[ccByte2];
|
||||||
|
|
||||||
boolean previousCaptionValid = captionValid;
|
if (isRepeatedCommand(isCaptionValid, ccData1, ccData2)) {
|
||||||
captionValid = (ccHeader & CC_VALID_FLAG) == CC_VALID_FLAG;
|
// Ignore repeated valid commands.
|
||||||
if (!captionValid) {
|
continue;
|
||||||
if (previousCaptionValid) {
|
}
|
||||||
|
|
||||||
|
if (!isCaptionValid) {
|
||||||
|
if (previousIsCaptionValid) {
|
||||||
// The encoder has flipped the validity bit to indicate captions are being turned off.
|
// The encoder has flipped the validity bit to indicate captions are being turned off.
|
||||||
resetCueBuilders();
|
resetCueBuilders();
|
||||||
captionDataProcessed = true;
|
captionDataProcessed = true;
|
||||||
@ -372,15 +378,6 @@ public final class Cea608Decoder extends CeaDecoder {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've reached this point then there is data to process; flag that work has been done.
|
|
||||||
captionDataProcessed = true;
|
|
||||||
|
|
||||||
if (!ODD_PARITY_BYTE_TABLE[ccByte1] || !ODD_PARITY_BYTE_TABLE[ccByte2]) {
|
|
||||||
// The data is invalid.
|
|
||||||
resetCueBuilders();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
maybeUpdateIsInCaptionService(ccData1, ccData2);
|
maybeUpdateIsInCaptionService(ccData1, ccData2);
|
||||||
if (!isInCaptionService) {
|
if (!isInCaptionService) {
|
||||||
// Only the Captioning service is supported. Drop all other bytes.
|
// Only the Captioning service is supported. Drop all other bytes.
|
||||||
@ -393,26 +390,29 @@ public final class Cea608Decoder extends CeaDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isCtrlCode(ccData1)) {
|
if (isCtrlCode(ccData1)) {
|
||||||
if (isSpecialChar(ccData1, ccData2)) {
|
if (isSpecialNorthAmericanChar(ccData1, ccData2)) {
|
||||||
// Special North American character.
|
currentCueBuilder.append(getSpecialNorthAmericanChar(ccData2));
|
||||||
currentCueBuilder.append(getSpecialChar(ccData2));
|
|
||||||
} else if (isExtendedWestEuropeanChar(ccData1, ccData2)) {
|
} else if (isExtendedWestEuropeanChar(ccData1, ccData2)) {
|
||||||
// Extended West European character.
|
|
||||||
// Remove standard equivalent of the special extended char before appending new one.
|
// Remove standard equivalent of the special extended char before appending new one.
|
||||||
currentCueBuilder.backspace();
|
currentCueBuilder.backspace();
|
||||||
currentCueBuilder.append(getExtendedWestEuropeanChar(ccData1, ccData2));
|
currentCueBuilder.append(getExtendedWestEuropeanChar(ccData1, ccData2));
|
||||||
} else {
|
} else if (isMidrowCtrlCode(ccData1, ccData2)) {
|
||||||
// Non-character control code.
|
handleMidrowCtrl(ccData2);
|
||||||
handleCtrl(ccData1, ccData2, repeatedControlPossible);
|
} else if (isPreambleAddressCode(ccData1, ccData2)) {
|
||||||
|
handlePreambleAddressCode(ccData1, ccData2);
|
||||||
|
} else if (isTabCtrlCode(ccData1, ccData2)) {
|
||||||
|
currentCueBuilder.tabOffset = ccData2 - 0x20;
|
||||||
|
} else if (isMiscCode(ccData1, ccData2)) {
|
||||||
|
handleMiscCode(ccData2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Basic North American character set.
|
||||||
|
currentCueBuilder.append(getBasicChar(ccData1));
|
||||||
|
if ((ccData2 & 0xE0) != 0x00) {
|
||||||
|
currentCueBuilder.append(getBasicChar(ccData2));
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic North American character set.
|
|
||||||
currentCueBuilder.append(getChar(ccData1));
|
|
||||||
if ((ccData2 & 0xE0) != 0x00) {
|
|
||||||
currentCueBuilder.append(getChar(ccData2));
|
|
||||||
}
|
}
|
||||||
|
captionDataProcessed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (captionDataProcessed) {
|
if (captionDataProcessed) {
|
||||||
@ -429,14 +429,15 @@ public final class Cea608Decoder extends CeaDecoder {
|
|||||||
return currentChannel == selectedChannel;
|
return currentChannel == selectedChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCtrl(byte cc1, byte cc2, boolean repeatedControlPossible) {
|
private boolean isRepeatedCommand(boolean captionValid, byte cc1, byte cc2) {
|
||||||
// Most control commands are sent twice in succession to ensure they are received properly. We
|
// Most control commands are sent twice in succession to ensure they are received properly. We
|
||||||
// don't want to process duplicate commands, so if we see the same repeatable command twice in a
|
// don't want to process duplicate commands, so if we see the same repeatable command twice in a
|
||||||
// row then we ignore the second one.
|
// row then we ignore the second one.
|
||||||
if (isRepeatable(cc1)) {
|
if (captionValid && isRepeatable(cc1)) {
|
||||||
if (repeatedControlPossible && repeatableControlCc1 == cc1 && repeatableControlCc2 == cc2) {
|
if (repeatableControlSet && repeatableControlCc1 == cc1 && repeatableControlCc2 == cc2) {
|
||||||
// This is a repeated command, so we ignore it.
|
// This is a repeated command, so we ignore it.
|
||||||
return;
|
repeatableControlSet = false;
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// This is the first occurrence of a repeatable command. Set the repeatable control
|
// This is the first occurrence of a repeatable command. Set the repeatable control
|
||||||
// variables so that we can recognize and ignore a duplicate (if there is one), and then
|
// variables so that we can recognize and ignore a duplicate (if there is one), and then
|
||||||
@ -445,17 +446,11 @@ public final class Cea608Decoder extends CeaDecoder {
|
|||||||
repeatableControlCc1 = cc1;
|
repeatableControlCc1 = cc1;
|
||||||
repeatableControlCc2 = cc2;
|
repeatableControlCc2 = cc2;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// This command is not repeatable.
|
||||||
|
repeatableControlSet = false;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
if (isMidrowCtrlCode(cc1, cc2)) {
|
|
||||||
handleMidrowCtrl(cc2);
|
|
||||||
} else if (isPreambleAddressCode(cc1, cc2)) {
|
|
||||||
handlePreambleAddressCode(cc1, cc2);
|
|
||||||
} else if (isTabCtrlCode(cc1, cc2)) {
|
|
||||||
currentCueBuilder.tabOffset = cc2 - 0x20;
|
|
||||||
} else if (isMiscCode(cc1, cc2)) {
|
|
||||||
handleMiscCode(cc2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMidrowCtrl(byte cc2) {
|
private void handleMidrowCtrl(byte cc2) {
|
||||||
@ -660,18 +655,18 @@ public final class Cea608Decoder extends CeaDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static char getChar(byte ccData) {
|
private static char getBasicChar(byte ccData) {
|
||||||
int index = (ccData & 0x7F) - 0x20;
|
int index = (ccData & 0x7F) - 0x20;
|
||||||
return (char) BASIC_CHARACTER_SET[index];
|
return (char) BASIC_CHARACTER_SET[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSpecialChar(byte cc1, byte cc2) {
|
private static boolean isSpecialNorthAmericanChar(byte cc1, byte cc2) {
|
||||||
// cc1 - 0|0|0|1|C|0|0|1
|
// cc1 - 0|0|0|1|C|0|0|1
|
||||||
// cc2 - 0|0|1|1|X|X|X|X
|
// cc2 - 0|0|1|1|X|X|X|X
|
||||||
return ((cc1 & 0xF7) == 0x11) && ((cc2 & 0xF0) == 0x30);
|
return ((cc1 & 0xF7) == 0x11) && ((cc2 & 0xF0) == 0x30);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static char getSpecialChar(byte ccData) {
|
private static char getSpecialNorthAmericanChar(byte ccData) {
|
||||||
int index = ccData & 0x0F;
|
int index = ccData & 0x0F;
|
||||||
return (char) SPECIAL_CHARACTER_SET[index];
|
return (char) SPECIAL_CHARACTER_SET[index];
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user