From 2f356badd231302ae1780f28285cb6741b2a49d7 Mon Sep 17 00:00:00 2001 From: Zsolt Matyas Date: Fri, 26 Apr 2019 14:37:26 -0700 Subject: [PATCH 1/2] Handling XDS and TEXT modes [Problem] There are 3 services / modes transported on line 21: - Captioning - TEXT (generally not program related) - XDS (eXtended Data Services) Bytes belonging to the unsupported modes are interleaved with the bytes of the captioning mode. See Chapter 7, Chapter 8.5 and Chapter 8.6 of the CEA608 Standard for more details. [Solution] Drop all bytes belonging to unsupported modes. [Test] - All streams containing only captioning services should not be influenced - Test all 4 CEA 608 channels with live over-the-air content and using all available TEXT and XDS streams. --- .../exoplayer2/text/cea/Cea608Decoder.java | 80 +++++++++++++++++-- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index 9316e4fb866..a30758c8e00 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -80,6 +80,17 @@ public final class Cea608Decoder extends CeaDecoder { * at which point the non-displayed memory becomes the displayed memory (and vice versa). */ private static final byte CTRL_RESUME_CAPTION_LOADING = 0x20; + + private static final byte CTRL_BACKSPACE = 0x21; + + @SuppressWarnings("unused") + private static final byte CTRL_ALARM_OFF= 0x22; // not supported any more + + @SuppressWarnings("unused") + private static final byte CTRL_ALARM_ON= 0x23; // not supported any more + + private static final byte CTRL_DELETE_TO_END_OF_ROW = 0x24; + /** * Command initiating roll-up style captioning, with the maximum of 2 rows displayed * simultaneously. @@ -95,25 +106,32 @@ public final class Cea608Decoder extends CeaDecoder { * simultaneously. */ private static final byte CTRL_ROLL_UP_CAPTIONS_4_ROWS = 0x27; + + @SuppressWarnings("unused") + private static final byte CTRL_FLASH_ON = 0x28; // not supported any more /** * Command initiating paint-on style captioning. Subsequent data should be addressed immediately * to displayed memory without need for the {@link #CTRL_RESUME_CAPTION_LOADING} command. */ private static final byte CTRL_RESUME_DIRECT_CAPTIONING = 0x29; /** - * Command indicating the end of a pop-on style caption. At this point the caption loaded in - * non-displayed memory should be swapped with the one in displayed memory. If no - * {@link #CTRL_RESUME_CAPTION_LOADING} command has been received, this command forces the - * receiver into pop-on style. + * TEXT commands are switching to TEXT mode from CAPTION mode. All consecutive incoming + * data must be filtered out until a command is received that switches back to CAPTION mode */ - private static final byte CTRL_END_OF_CAPTION = 0x2F; + private static final byte CTRL_TEXT_RESTART = 0x2A; + private static final byte CTRL_RESUME_TEXT_DISPLAY = 0x2B; private static final byte CTRL_ERASE_DISPLAYED_MEMORY = 0x2C; private static final byte CTRL_CARRIAGE_RETURN = 0x2D; private static final byte CTRL_ERASE_NON_DISPLAYED_MEMORY = 0x2E; - private static final byte CTRL_DELETE_TO_END_OF_ROW = 0x24; - private static final byte CTRL_BACKSPACE = 0x21; + /** + * Command indicating the end of a pop-on style caption. At this point the caption loaded in + * non-displayed memory should be swapped with the one in displayed memory. If no + * {@link #CTRL_RESUME_CAPTION_LOADING} command has been received, this command forces the + * receiver into pop-on style. + */ + private static final byte CTRL_END_OF_CAPTION = 0x2F; // Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20). private static final int[] BASIC_CHARACTER_SET = new int[] { @@ -237,6 +255,11 @@ public final class Cea608Decoder extends CeaDecoder { private byte repeatableControlCc2; private int currentChannel; + // The incoming characters may belong to 3 different services based on the last received control + // codes. The 3 services are Captioning, Text and XDS. In this decoder we only intend to process + // bytes belonging to the Captioning service. + private boolean isInCaptionMode = true; + public Cea608Decoder(String mimeType, int accessibilityChannel) { ccData = new ParsableByteArray(); cueBuilders = new ArrayList<>(); @@ -288,6 +311,7 @@ public void flush() { repeatableControlCc1 = 0; repeatableControlCc2 = 0; currentChannel = NTSC_CC_CHANNEL_1; + isInCaptionMode = true; } @Override @@ -306,6 +330,38 @@ protected Subtitle createSubtitle() { return new CeaSubtitle(cues); } + private boolean isCodeForUnsupportedMode(byte cc1, byte cc2) { + // Control codes from 0x01 to 0x0F indicate the beginning of XDS Data + if (0x01 <= cc1 && cc1 <= 0x0F) { + return true; + } + + // 2 commands switch to TEXT mode. + if (((cc1 & 0xF7) == 0x14) // first byte must be 0x14 or 0x1C based on channel + && (cc2 == CTRL_TEXT_RESTART || cc2 == CTRL_RESUME_TEXT_DISPLAY)) { + return true; + } + + return false; + } + + private static boolean isControlCodeSwitchingToCaptionMode(byte cc1, byte cc2) { + if ((cc1 & 0xF7) != 0x14) { // Matching commands must have the CC1 value: 0|0|0|1|CH|1|0|0 where CH is the channel bit + return false; + } + + switch (cc2) { + case CTRL_END_OF_CAPTION: + case CTRL_RESUME_CAPTION_LOADING: + case CTRL_RESUME_DIRECT_CAPTIONING: + case CTRL_ROLL_UP_CAPTIONS_2_ROWS: + case CTRL_ROLL_UP_CAPTIONS_3_ROWS: + case CTRL_ROLL_UP_CAPTIONS_4_ROWS: + return true; + } + return false; + } + @SuppressWarnings("ByteBufferBackingArray") @Override protected void decode(SubtitleInputBuffer inputBuffer) { @@ -363,6 +419,16 @@ protected void decode(SubtitleInputBuffer inputBuffer) { continue; } + if (isCodeForUnsupportedMode(ccData1, ccData2)) { + isInCaptionMode = false; + continue; + } else if (!isInCaptionMode) { + if (!isControlCodeSwitchingToCaptionMode(ccData1, ccData2)) { + continue; + } else { + isInCaptionMode = true; + } + } // Special North American character set. // ccData1 - 0|0|0|1|C|0|0|1 // ccData2 - 0|0|1|1|X|X|X|X From 3e14ce1094a5bf932fd33c225ed8b53b573d9db3 Mon Sep 17 00:00:00 2001 From: Zsolt Matyas Date: Mon, 29 Apr 2019 12:56:25 -0700 Subject: [PATCH 2/2] Code changes suggested by tonihei --- .../exoplayer2/text/cea/Cea608Decoder.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index a30758c8e00..332f8443750 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -83,12 +83,6 @@ public final class Cea608Decoder extends CeaDecoder { private static final byte CTRL_BACKSPACE = 0x21; - @SuppressWarnings("unused") - private static final byte CTRL_ALARM_OFF= 0x22; // not supported any more - - @SuppressWarnings("unused") - private static final byte CTRL_ALARM_ON= 0x23; // not supported any more - private static final byte CTRL_DELETE_TO_END_OF_ROW = 0x24; /** @@ -258,7 +252,7 @@ public final class Cea608Decoder extends CeaDecoder { // The incoming characters may belong to 3 different services based on the last received control // codes. The 3 services are Captioning, Text and XDS. In this decoder we only intend to process // bytes belonging to the Captioning service. - private boolean isInCaptionMode = true; + private boolean isInCaptionMode; public Cea608Decoder(String mimeType, int accessibilityChannel) { ccData = new ParsableByteArray(); @@ -291,6 +285,7 @@ public Cea608Decoder(String mimeType, int accessibilityChannel) { setCaptionMode(CC_MODE_UNKNOWN); resetCueBuilders(); + isInCaptionMode = true; } @Override @@ -330,14 +325,14 @@ protected Subtitle createSubtitle() { return new CeaSubtitle(cues); } - private boolean isCodeForUnsupportedMode(byte cc1, byte cc2) { + private static boolean isCodeForUnsupportedMode(byte cc1, byte cc2) { // Control codes from 0x01 to 0x0F indicate the beginning of XDS Data if (0x01 <= cc1 && cc1 <= 0x0F) { return true; } // 2 commands switch to TEXT mode. - if (((cc1 & 0xF7) == 0x14) // first byte must be 0x14 or 0x1C based on channel + if ((isModeSwitchCommand(cc1)) && (cc2 == CTRL_TEXT_RESTART || cc2 == CTRL_RESUME_TEXT_DISPLAY)) { return true; } @@ -345,8 +340,13 @@ private boolean isCodeForUnsupportedMode(byte cc1, byte cc2) { return false; } + // first byte of these commands must be 0x14 or 0x1C based on channel + private static boolean isModeSwitchCommand(byte cc1) { + return (cc1 & 0xF7) == 0x14; + } + private static boolean isControlCodeSwitchingToCaptionMode(byte cc1, byte cc2) { - if ((cc1 & 0xF7) != 0x14) { // Matching commands must have the CC1 value: 0|0|0|1|CH|1|0|0 where CH is the channel bit + if (!isModeSwitchCommand(cc1)) { return false; }