From 3d14c7242d3a3d260fb6734129d5aad94a21b0bd Mon Sep 17 00:00:00 2001 From: cdrolle Date: Wed, 27 Apr 2016 10:04:17 -0700 Subject: [PATCH] Refactored Eia608Parser so that it manipulates the caption string builder directly, avoiding unnecessary object allocation. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=120926283 --- .../exoplayer/text/eia608/ClosedCaption.java | 41 --- .../text/eia608/ClosedCaptionCtrl.java | 99 -------- .../text/eia608/ClosedCaptionList.java | 28 -- .../text/eia608/ClosedCaptionText.java | 27 -- .../exoplayer/text/eia608/Eia608Parser.java | 239 ++++++++++-------- 5 files changed, 140 insertions(+), 294 deletions(-) delete mode 100644 library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaption.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionCtrl.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionList.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionText.java diff --git a/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaption.java b/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaption.java deleted file mode 100644 index 1961cc7a76..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaption.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer.text.eia608; - -/** - * A Closed Caption that contains textual data associated with time indices. - */ -/* package */ abstract class ClosedCaption { - - /** - * Identifies closed captions with control characters. - */ - public static final int TYPE_CTRL = 0; - /** - * Identifies closed captions with textual information. - */ - public static final int TYPE_TEXT = 1; - - /** - * The type of the closed caption data. - */ - public final int type; - - protected ClosedCaption(int type) { - this.type = type; - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionCtrl.java b/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionCtrl.java deleted file mode 100644 index d057b7e653..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionCtrl.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer.text.eia608; - -/* package */ final class ClosedCaptionCtrl extends ClosedCaption { - - /** - * Command initiating pop-on style captioning. Subsequent data should be loaded into a - * non-displayed memory and held there until the {@link #END_OF_CAPTION} command is received, at - * which point the non-displayed memory becomes the displayed memory (and vice versa). - */ - public static final byte RESUME_CAPTION_LOADING = 0x20; - /** - * Command initiating roll-up style captioning, with the maximum of 2 rows displayed - * simultaneously. - */ - public static final byte ROLL_UP_CAPTIONS_2_ROWS = 0x25; - /** - * Command initiating roll-up style captioning, with the maximum of 3 rows displayed - * simultaneously. - */ - public static final byte ROLL_UP_CAPTIONS_3_ROWS = 0x26; - /** - * Command initiating roll-up style captioning, with the maximum of 4 rows displayed - * simultaneously. - */ - public static final byte ROLL_UP_CAPTIONS_4_ROWS = 0x27; - /** - * Command initiating paint-on style captioning. Subsequent data should be addressed immediately - * to displayed memory without need for the {@link #RESUME_CAPTION_LOADING} command. - */ - public static final byte 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 #RESUME_CAPTION_LOADING} command has been received, this command forces the receiver - * into pop-on style. - */ - public static final byte END_OF_CAPTION = 0x2F; - - public static final byte ERASE_DISPLAYED_MEMORY = 0x2C; - public static final byte CARRIAGE_RETURN = 0x2D; - public static final byte ERASE_NON_DISPLAYED_MEMORY = 0x2E; - - public static final byte BACKSPACE = 0x21; - - - public static final byte MID_ROW_CHAN_1 = 0x11; - public static final byte MID_ROW_CHAN_2 = 0x19; - - public static final byte MISC_CHAN_1 = 0x14; - public static final byte MISC_CHAN_2 = 0x1C; - - public static final byte TAB_OFFSET_CHAN_1 = 0x17; - public static final byte TAB_OFFSET_CHAN_2 = 0x1F; - - public final byte cc1; - public final byte cc2; - - protected ClosedCaptionCtrl(byte cc1, byte cc2) { - super(ClosedCaption.TYPE_CTRL); - this.cc1 = cc1; - this.cc2 = cc2; - } - - public boolean isMidRowCode() { - return (cc1 == MID_ROW_CHAN_1 || cc1 == MID_ROW_CHAN_2) && (cc2 >= 0x20 && cc2 <= 0x2F); - } - - public boolean isMiscCode() { - return (cc1 == MISC_CHAN_1 || cc1 == MISC_CHAN_2) && (cc2 >= 0x20 && cc2 <= 0x2F); - } - - public boolean isTabOffsetCode() { - return (cc1 == TAB_OFFSET_CHAN_1 || cc1 == TAB_OFFSET_CHAN_2) && (cc2 >= 0x21 && cc2 <= 0x23); - } - - public boolean isPreambleAddressCode() { - return (cc1 >= 0x10 && cc1 <= 0x1F) && (cc2 >= 0x40 && cc2 <= 0x7F); - } - - public boolean isRepeatable() { - return cc1 >= 0x10 && cc1 <= 0x1F; - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionList.java b/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionList.java deleted file mode 100644 index 9e9cb84d30..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionList.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer.text.eia608; - -/* package */ final class ClosedCaptionList { - - public final long timeUs; - public final ClosedCaption[] captions; - - public ClosedCaptionList(long timeUs, ClosedCaption[] captions) { - this.timeUs = timeUs; - this.captions = captions; - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionText.java b/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionText.java deleted file mode 100644 index 98e93ea493..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionText.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer.text.eia608; - -/* package */ final class ClosedCaptionText extends ClosedCaption { - - public final String text; - - public ClosedCaptionText(String text) { - super(ClosedCaption.TYPE_TEXT); - this.text = text; - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java b/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java index de6e662667..6af6f1ad56 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java @@ -24,7 +24,6 @@ import com.google.android.exoplayer.util.ParsableBitArray; import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.extensions.Decoder; -import java.util.ArrayList; import java.util.LinkedList; import java.util.TreeSet; @@ -52,6 +51,55 @@ public final class Eia608Parser implements // The default number of rows to display in roll-up captions mode. private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4; + /** + * Command initiating pop-on style captioning. Subsequent data should be loaded into a + * non-displayed memory and held there until the {@link #CTRL_END_OF_CAPTION} command is received, + * at which point the non-displayed memory becomes the displayed memory (and vice versa). + */ + private static final byte CTRL_RESUME_CAPTION_LOADING = 0x20; + /** + * Command initiating roll-up style captioning, with the maximum of 2 rows displayed + * simultaneously. + */ + private static final byte CTRL_ROLL_UP_CAPTIONS_2_ROWS = 0x25; + /** + * Command initiating roll-up style captioning, with the maximum of 3 rows displayed + * simultaneously. + */ + private static final byte CTRL_ROLL_UP_CAPTIONS_3_ROWS = 0x26; + /** + * Command initiating roll-up style captioning, with the maximum of 4 rows displayed + * simultaneously. + */ + private static final byte CTRL_ROLL_UP_CAPTIONS_4_ROWS = 0x27; + /** + * 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. + */ + private static final byte CTRL_END_OF_CAPTION = 0x2F; + + 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_BACKSPACE = 0x21; + + private static final byte CTRL_MID_ROW_CHAN_1 = 0x11; + private static final byte CTRL_MID_ROW_CHAN_2 = 0x19; + + private static final byte CTRL_MISC_CHAN_1 = 0x14; + private static final byte CTRL_MISC_CHAN_2 = 0x1C; + + private static final byte CTRL_TAB_OFFSET_CHAN_1 = 0x17; + private static final byte CTRL_TAB_OFFSET_CHAN_2 = 0x1F; + // Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20). private static final int[] BASIC_CHARACTER_SET = new int[] { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // ! " # $ % & ' @@ -125,8 +173,6 @@ public final class Eia608Parser implements private final TreeSet queuedInputBuffers; private final ParsableBitArray seiBuffer; - private final StringBuilder textStringBuilder; - private final ArrayList captions; private final StringBuilder captionStringBuilder; @@ -135,7 +181,12 @@ public final class Eia608Parser implements private int captionMode; private int captionRowCount; private String captionString; - private ClosedCaptionCtrl repeatableControl; + + private String lastCaptionString; + + private boolean repeatableControlSet; + private byte repeatableControlCc1; + private byte repeatableControlCc2; public Eia608Parser() { availableInputBuffers = new LinkedList<>(); @@ -149,8 +200,6 @@ public final class Eia608Parser implements queuedInputBuffers = new TreeSet<>(); seiBuffer = new ParsableBitArray(); - textStringBuilder = new StringBuilder(); - captions = new ArrayList<>(); captionStringBuilder = new StringBuilder(); @@ -182,21 +231,28 @@ public final class Eia608Parser implements return null; } - SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst(); + SubtitleOutputBuffer outputBuffer = null; SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst(); // TODO: investigate ways of batching multiple SubtitleInputBuffers into a single - // SubtitleOutputBuffer + // SubtitleOutputBuffer; it isn't as simple as just iterating through all of the queued input + // buffers until there is a change because for pop-on captions this will result in the input + // buffers being processed almost sequentially as they are queued, eliminating the re-ordering, + // resulting in the content having characters out of order if (inputBuffer.isEndOfStream()) { + outputBuffer = availableOutputBuffers.pollFirst(); outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); - return outputBuffer; - } - ClosedCaptionList captionList = decode(inputBuffer); - Eia608Subtitle subtitle = generateSubtitle(captionList); - outputBuffer.setOutput(inputBuffer.timeUs, subtitle, 0); - if (inputBuffer.isDecodeOnly()) { - outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + } else if (decode(inputBuffer) + && ((captionString == null && lastCaptionString != null) + || (captionString != null && !captionString.equals((lastCaptionString))))) { + lastCaptionString = captionString; + outputBuffer = availableOutputBuffers.pollFirst(); + outputBuffer.setOutput(inputBuffer.timeUs, new Eia608Subtitle(captionString), 0); + if (inputBuffer.isDecodeOnly()) { + outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + } } + releaseInputBuffer(inputBuffer); return outputBuffer; } @@ -217,7 +273,10 @@ public final class Eia608Parser implements captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; captionStringBuilder.setLength(0); captionString = null; - repeatableControl = null; + lastCaptionString = null; + repeatableControlSet = false; + repeatableControlCc1 = 0; + repeatableControlCc2 = 0; while (!queuedInputBuffers.isEmpty()) { releaseInputBuffer(queuedInputBuffers.pollFirst()); } @@ -232,13 +291,10 @@ public final class Eia608Parser implements // Do nothing } - private ClosedCaptionList decode(SubtitleInputBuffer inputBuffer) { + private boolean decode(SubtitleInputBuffer inputBuffer) { if (inputBuffer.size < 10) { - return null; + return false; } - - captions.clear(); - textStringBuilder.setLength(0); seiBuffer.reset(inputBuffer.data.array()); // country_code (8) + provider_code (16) + user_identifier (32) + user_data_type_code (8) + @@ -247,6 +303,8 @@ public final class Eia608Parser implements int ccCount = seiBuffer.readBits(5); seiBuffer.skipBits(8); + boolean captionDataProcessed = false; + boolean isRepeatableControl = false; for (int i = 0; i < ccCount; i++) { seiBuffer.skipBits(5); // one_bit + reserved boolean ccValid = seiBuffer.readBit(); @@ -269,11 +327,14 @@ public final class Eia608Parser implements continue; } + // If we've reached this point then there is data to process; flag that work has been done. + captionDataProcessed = true; + // Special North American character set. // ccData2 - P|0|1|1|X|X|X|X if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) { - textStringBuilder.append(getSpecialChar(ccData2)); + captionStringBuilder.append(getSpecialChar(ccData2)); continue; } @@ -282,7 +343,7 @@ public final class Eia608Parser implements if ((ccData1 == 0x12 || ccData1 == 0x1A) && ((ccData2 & 0x60) == 0x20)) { backspace(); // Remove standard equivalent of the special extended char. - textStringBuilder.append(getExtendedEsFrChar(ccData2)); + captionStringBuilder.append(getExtendedEsFrChar(ccData2)); continue; } @@ -291,89 +352,76 @@ public final class Eia608Parser implements if ((ccData1 == 0x13 || ccData1 == 0x1B) && ((ccData2 & 0x60) == 0x20)) { backspace(); // Remove standard equivalent of the special extended char. - textStringBuilder.append(getExtendedPtDeChar(ccData2)); + captionStringBuilder.append(getExtendedPtDeChar(ccData2)); continue; } // Control character. if (ccData1 < 0x20) { - addCtrl(ccData1, ccData2); + isRepeatableControl = handleCtrl(ccData1, ccData2); continue; } // Basic North American character set. - textStringBuilder.append(getChar(ccData1)); + captionStringBuilder.append(getChar(ccData1)); if (ccData2 >= 0x20) { - textStringBuilder.append(getChar(ccData2)); + captionStringBuilder.append(getChar(ccData2)); } } - addBufferedText(); - - if (captions.isEmpty()) { - return null; + if (!captionDataProcessed) { + return false; } - ClosedCaption[] captionArray = new ClosedCaption[captions.size()]; - captions.toArray(captionArray); - return new ClosedCaptionList(inputBuffer.timeUs, captionArray); + if (!isRepeatableControl) { + repeatableControlSet = false; + } + if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { + captionString = getDisplayCaption(); + } + + return true; } - public Eia608Subtitle generateSubtitle(ClosedCaptionList captionList) { - int captionBufferSize = (captionList == null) ? 0 : captionList.captions.length; - if (captionBufferSize != 0) { - boolean isRepeatableControl = false; - for (ClosedCaption caption : captionList.captions) { - if (caption.type == ClosedCaption.TYPE_CTRL) { - ClosedCaptionCtrl captionCtrl = (ClosedCaptionCtrl) caption; - isRepeatableControl = captionBufferSize == 1 && captionCtrl.isRepeatable(); - if (isRepeatableControl && repeatableControl != null - && repeatableControl.cc1 == captionCtrl.cc1 - && repeatableControl.cc2 == captionCtrl.cc2) { - repeatableControl = null; - continue; - } else if (isRepeatableControl) { - repeatableControl = captionCtrl; - } - if (captionCtrl.isMiscCode()) { - handleMiscCode(captionCtrl); - } else if (captionCtrl.isPreambleAddressCode()) { - handlePreambleAddressCode(); - } - } else { - handleText((ClosedCaptionText) caption); - } - } - - if (!isRepeatableControl) { - repeatableControl = null; - } - if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { - captionString = getDisplayCaption(); - } + private boolean handleCtrl(byte cc1, byte cc2) { + boolean isRepeatableControl = isRepeatable(cc1, cc2); + if (isRepeatableControl && repeatableControlSet + && repeatableControlCc1 == cc1 + && repeatableControlCc2 == cc2) { + repeatableControlSet = false; + return true; + } else if (isRepeatableControl) { + repeatableControlSet = true; + repeatableControlCc1 = cc1; + repeatableControlCc2 = cc2; } - - return new Eia608Subtitle(captionString); + if (isMiscCode(cc1, cc2)) { + handleMiscCode(cc2); + } else if (isPreambleAddressCode(cc1, cc2)) { + // TODO: Add better handling of this with specific positioning. + maybeAppendNewline(); + } + return isRepeatableControl; } - private void handleMiscCode(ClosedCaptionCtrl caption) { - switch (caption.cc2) { - case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_2_ROWS: + private void handleMiscCode(byte cc2) { + switch (cc2) { + case CTRL_ROLL_UP_CAPTIONS_2_ROWS: captionRowCount = 2; setCaptionMode(CC_MODE_ROLL_UP); return; - case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_3_ROWS: + case CTRL_ROLL_UP_CAPTIONS_3_ROWS: captionRowCount = 3; setCaptionMode(CC_MODE_ROLL_UP); return; - case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_4_ROWS: + case CTRL_ROLL_UP_CAPTIONS_4_ROWS: captionRowCount = 4; setCaptionMode(CC_MODE_ROLL_UP); return; - case ClosedCaptionCtrl.RESUME_CAPTION_LOADING: + case CTRL_RESUME_CAPTION_LOADING: setCaptionMode(CC_MODE_POP_ON); return; - case ClosedCaptionCtrl.RESUME_DIRECT_CAPTIONING: + case CTRL_RESUME_DIRECT_CAPTIONING: setCaptionMode(CC_MODE_PAINT_ON); return; } @@ -382,24 +430,24 @@ public final class Eia608Parser implements return; } - switch (caption.cc2) { - case ClosedCaptionCtrl.ERASE_DISPLAYED_MEMORY: + switch (cc2) { + case CTRL_ERASE_DISPLAYED_MEMORY: captionString = null; if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { captionStringBuilder.setLength(0); } return; - case ClosedCaptionCtrl.ERASE_NON_DISPLAYED_MEMORY: + case CTRL_ERASE_NON_DISPLAYED_MEMORY: captionStringBuilder.setLength(0); return; - case ClosedCaptionCtrl.END_OF_CAPTION: + case CTRL_END_OF_CAPTION: captionString = getDisplayCaption(); captionStringBuilder.setLength(0); return; - case ClosedCaptionCtrl.CARRIAGE_RETURN: + case CTRL_CARRIAGE_RETURN: maybeAppendNewline(); return; - case ClosedCaptionCtrl.BACKSPACE: + case CTRL_BACKSPACE: if (captionStringBuilder.length() > 0) { captionStringBuilder.setLength(captionStringBuilder.length() - 1); } @@ -407,13 +455,10 @@ public final class Eia608Parser implements } } - private void handlePreambleAddressCode() { - // TODO: Add better handling of this with specific positioning. - maybeAppendNewline(); - } - - private void handleText(ClosedCaptionText caption) { - captionStringBuilder.append(caption.text); + private void backspace() { + if (captionStringBuilder.length() > 0) { + captionStringBuilder.setLength(captionStringBuilder.length() - 1); + } } private void maybeAppendNewline() { @@ -485,21 +530,17 @@ public final class Eia608Parser implements return (char) SPECIAL_PT_DE_CHARACTER_SET[index]; } - private void addBufferedText() { - if (textStringBuilder.length() > 0) { - String textSnippet = textStringBuilder.toString(); - captions.add(new ClosedCaptionText(textSnippet)); - textStringBuilder.setLength(0); - } + private static boolean isMiscCode(byte cc1, byte cc2) { + return (cc1 == CTRL_MISC_CHAN_1 || cc1 == CTRL_MISC_CHAN_2) + && (cc2 >= 0x20 && cc2 <= 0x2F); } - private void addCtrl(byte ccData1, byte ccData2) { - addBufferedText(); - captions.add(new ClosedCaptionCtrl(ccData1, ccData2)); + private static boolean isPreambleAddressCode(byte cc1, byte cc2) { + return (cc1 >= 0x10 && cc1 <= 0x1F) && (cc2 >= 0x40 && cc2 <= 0x7F); } - private void backspace() { - addCtrl((byte) 0x14, ClosedCaptionCtrl.BACKSPACE); + private static boolean isRepeatable(byte cc1, byte cc2) { + return cc1 >= 0x10 && cc1 <= 0x1F; } /**