Commit 3d14c724 by cdrolle Committed by Oliver Woodman

Refactored Eia608Parser so that it manipulates the caption string builder…

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
parent ab3489ef
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
}
}
......@@ -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<SubtitleInputBuffer> queuedInputBuffers;
private final ParsableBitArray seiBuffer;
private final StringBuilder textStringBuilder;
private final ArrayList<ClosedCaption> 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 (!captionDataProcessed) {
return false;
}
if (captions.isEmpty()) {
return null;
if (!isRepeatableControl) {
repeatableControlSet = false;
}
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
captionString = getDisplayCaption();
}
ClosedCaption[] captionArray = new ClosedCaption[captions.size()];
captions.toArray(captionArray);
return new ClosedCaptionList(inputBuffer.timeUs, captionArray);
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;
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment