Commit 18a24a1f by cdrolle Committed by Oliver Woodman

Added the CEA-708 support to the open-source project.

Issue: #1807

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=144726542
parent 1ffe7753
......@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.text;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.text.cea.Cea608Decoder;
import com.google.android.exoplayer2.text.cea.Cea708Decoder;
import com.google.android.exoplayer2.text.subrip.SubripDecoder;
import com.google.android.exoplayer2.text.ttml.TtmlDecoder;
import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder;
......@@ -58,6 +59,7 @@ public interface SubtitleDecoderFactory {
* <li>SubRip ({@link SubripDecoder})</li>
* <li>TX3G ({@link Tx3gDecoder})</li>
* <li>Cea608 ({@link Cea608Decoder})</li>
* <li>Cea708 ({@link Cea708Decoder})</li>
* </ul>
*/
SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() {
......@@ -78,6 +80,9 @@ public interface SubtitleDecoderFactory {
|| format.sampleMimeType.equals(MimeTypes.APPLICATION_MP4CEA608)) {
return clazz.asSubclass(SubtitleDecoder.class).getConstructor(String.class, Integer.TYPE)
.newInstance(format.sampleMimeType, format.accessibilityChannel);
} else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_CEA708)) {
return clazz.asSubclass(SubtitleDecoder.class).getConstructor(Integer.TYPE)
.newInstance(format.accessibilityChannel);
} else {
return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance();
}
......@@ -105,6 +110,8 @@ public interface SubtitleDecoderFactory {
case MimeTypes.APPLICATION_CEA608:
case MimeTypes.APPLICATION_MP4CEA608:
return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder");
case MimeTypes.APPLICATION_CEA708:
return Class.forName("com.google.android.exoplayer2.text.cea.Cea708Decoder");
default:
return null;
}
......
/*
* Copyright (C) 2016 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.exoplayer2.text.cea;
import android.text.Layout.Alignment;
import com.google.android.exoplayer2.text.Cue;
/**
* A {@link Cue} for CEA-708.
*/
/* package */ final class Cea708Cue extends Cue implements Comparable<Cea708Cue> {
/**
* An unset priority.
*/
public static final int PRIORITY_UNSET = -1;
/**
* The priority of the cue box.
*/
public final int priority;
/**
* @param text See {@link #text}.
* @param textAlignment See {@link #textAlignment}.
* @param line See {@link #line}.
* @param lineType See {@link #lineType}.
* @param lineAnchor See {@link #lineAnchor}.
* @param position See {@link #position}.
* @param positionAnchor See {@link #positionAnchor}.
* @param size See {@link #size}.
* @param windowColorSet See {@link #windowColorSet}.
* @param windowColor See {@link #windowColor}.
* @param priority See (@link #priority}.
*/
public Cea708Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size,
boolean windowColorSet, int windowColor, int priority) {
super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, size,
windowColorSet, windowColor);
this.priority = priority;
}
@Override
public int compareTo(Cea708Cue other) {
if (other.priority < priority) {
return -1;
} else if (other.priority > priority) {
return 1;
}
return 0;
}
}
/*
* Copyright (C) 2016 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.exoplayer2.text.cea;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.Layout.Alignment;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.Cue.AnchorType;
import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.text.SubtitleDecoder;
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* A {@link SubtitleDecoder} for CEA-708 (also known as "EIA-708").
*
* <p>This implementation does not provide full compatibility with the CEA-708 specification. Note
* that only the default pen/text and window/cue colors (i.e. text with
* {@link CueBuilder#COLOR_SOLID_WHITE} foreground and {@link CueBuilder#COLOR_SOLID_BLACK}
* background, and cues with {@link CueBuilder#COLOR_SOLID_BLACK} fill) will be overridden with
* device accessibility settings; all others will use the colors and opacity specified by the
* caption data.
*/
public final class Cea708Decoder extends CeaDecoder {
private static final String TAG = "Cea708Decoder";
private static final int NUM_WINDOWS = 8;
private static final int DTVCC_PACKET_DATA = 0x02;
private static final int DTVCC_PACKET_START = 0x03;
private static final int CC_VALID_FLAG = 0x04;
// Base Commands
private static final int GROUP_C0_END = 0x1F; // Miscellaneous Control Codes
private static final int GROUP_G0_END = 0x7F; // ASCII Printable Characters
private static final int GROUP_C1_END = 0x9F; // Captioning Command Control Codes
private static final int GROUP_G1_END = 0xFF; // ISO 8859-1 LATIN-1 Character Set
// Extended Commands
private static final int GROUP_C2_END = 0x1F; // Extended Control Code Set 1
private static final int GROUP_G2_END = 0x7F; // Extended Miscellaneous Characters
private static final int GROUP_C3_END = 0x9F; // Extended Control Code Set 2
private static final int GROUP_G3_END = 0xFF; // Future Expansion
// Group C0 Commands
private static final int COMMAND_NUL = 0x00; // Nul
private static final int COMMAND_ETX = 0x03; // EndOfText
private static final int COMMAND_BS = 0x08; // Backspace
private static final int COMMAND_FF = 0x0C; // FormFeed (Flush)
private static final int COMMAND_CR = 0x0D; // CarriageReturn
private static final int COMMAND_HCR = 0x0E; // ClearLine
private static final int COMMAND_EXT1 = 0x10; // Extended Control Code Flag
private static final int COMMAND_EXT1_START = 0x11;
private static final int COMMAND_EXT1_END = 0x17;
private static final int COMMAND_P16_START = 0x18;
private static final int COMMAND_P16_END = 0x1F;
// Group C1 Commands
private static final int COMMAND_CW0 = 0x80; // SetCurrentWindow to 0
private static final int COMMAND_CW1 = 0x81; // SetCurrentWindow to 1
private static final int COMMAND_CW2 = 0x82; // SetCurrentWindow to 2
private static final int COMMAND_CW3 = 0x83; // SetCurrentWindow to 3
private static final int COMMAND_CW4 = 0x84; // SetCurrentWindow to 4
private static final int COMMAND_CW5 = 0x85; // SetCurrentWindow to 5
private static final int COMMAND_CW6 = 0x86; // SetCurrentWindow to 6
private static final int COMMAND_CW7 = 0x87; // SetCurrentWindow to 7
private static final int COMMAND_CLW = 0x88; // ClearWindows (+1 byte)
private static final int COMMAND_DSW = 0x89; // DisplayWindows (+1 byte)
private static final int COMMAND_HDW = 0x8A; // HideWindows (+1 byte)
private static final int COMMAND_TGW = 0x8B; // ToggleWindows (+1 byte)
private static final int COMMAND_DLW = 0x8C; // DeleteWindows (+1 byte)
private static final int COMMAND_DLY = 0x8D; // Delay (+1 byte)
private static final int COMMAND_DLC = 0x8E; // DelayCancel
private static final int COMMAND_RST = 0x8F; // Reset
private static final int COMMAND_SPA = 0x90; // SetPenAttributes (+2 bytes)
private static final int COMMAND_SPC = 0x91; // SetPenColor (+3 bytes)
private static final int COMMAND_SPL = 0x92; // SetPenLocation (+2 bytes)
private static final int COMMAND_SWA = 0x97; // SetWindowAttributes (+4 bytes)
private static final int COMMAND_DF0 = 0x98; // DefineWindow 0 (+6 bytes)
private static final int COMMAND_DF1 = 0x99; // DefineWindow 1 (+6 bytes)
private static final int COMMAND_DF2 = 0x9A; // DefineWindow 2 (+6 bytes)
private static final int COMMAND_DF3 = 0x9B; // DefineWindow 3 (+6 bytes)
private static final int COMMAND_DS4 = 0x9C; // DefineWindow 4 (+6 bytes)
private static final int COMMAND_DF5 = 0x9D; // DefineWindow 5 (+6 bytes)
private static final int COMMAND_DF6 = 0x9E; // DefineWindow 6 (+6 bytes)
private static final int COMMAND_DF7 = 0x9F; // DefineWindow 7 (+6 bytes)
// G0 Table Special Chars
private static final int CHARACTER_MN = 0x7F; // MusicNote
// G2 Table Special Chars
private static final int CHARACTER_TSP = 0x20;
private static final int CHARACTER_NBTSP = 0x21;
private static final int CHARACTER_ELLIPSIS = 0x25;
private static final int CHARACTER_BIG_CARONS = 0x2A;
private static final int CHARACTER_BIG_OE = 0x2C;
private static final int CHARACTER_SOLID_BLOCK = 0x30;
private static final int CHARACTER_OPEN_SINGLE_QUOTE = 0x31;
private static final int CHARACTER_CLOSE_SINGLE_QUOTE = 0x32;
private static final int CHARACTER_OPEN_DOUBLE_QUOTE = 0x33;
private static final int CHARACTER_CLOSE_DOUBLE_QUOTE = 0x34;
private static final int CHARACTER_BOLD_BULLET = 0x35;
private static final int CHARACTER_TM = 0x39;
private static final int CHARACTER_SMALL_CARONS = 0x3A;
private static final int CHARACTER_SMALL_OE = 0x3C;
private static final int CHARACTER_SM = 0x3D;
private static final int CHARACTER_DIAERESIS_Y = 0x3F;
private static final int CHARACTER_ONE_EIGHTH = 0x76;
private static final int CHARACTER_THREE_EIGHTHS = 0x77;
private static final int CHARACTER_FIVE_EIGHTHS = 0x78;
private static final int CHARACTER_SEVEN_EIGHTHS = 0x79;
private static final int CHARACTER_VERTICAL_BORDER = 0x7A;
private static final int CHARACTER_UPPER_RIGHT_BORDER = 0x7B;
private static final int CHARACTER_LOWER_LEFT_BORDER = 0x7C;
private static final int CHARACTER_HORIZONTAL_BORDER = 0x7D;
private static final int CHARACTER_LOWER_RIGHT_BORDER = 0x7E;
private static final int CHARACTER_UPPER_LEFT_BORDER = 0x7F;
private final ParsableByteArray ccData;
private final ParsableBitArray serviceBlockPacket;
private final int selectedServiceNumber;
private final CueBuilder[] cueBuilders;
private CueBuilder currentCueBuilder;
private List<Cue> cues;
private List<Cue> lastCues;
private DtvCcPacket currentDtvCcPacket;
private int currentWindow;
public Cea708Decoder(int accessibilityChannel) {
ccData = new ParsableByteArray();
serviceBlockPacket = new ParsableBitArray();
selectedServiceNumber = (accessibilityChannel == Format.NO_VALUE) ? 1 : accessibilityChannel;
cueBuilders = new CueBuilder[NUM_WINDOWS];
for (int i = 0; i < NUM_WINDOWS; i++) {
cueBuilders[i] = new CueBuilder();
}
currentCueBuilder = cueBuilders[0];
resetCueBuilders();
}
@Override
public String getName() {
return "Cea708Decoder";
}
@Override
public void flush() {
super.flush();
cues = null;
lastCues = null;
currentWindow = 0;
currentCueBuilder = cueBuilders[currentWindow];
resetCueBuilders();
currentDtvCcPacket = null;
}
@Override
protected boolean isNewSubtitleDataAvailable() {
return cues != lastCues;
}
@Override
protected Subtitle createSubtitle() {
lastCues = cues;
return new CeaSubtitle(cues);
}
@Override
protected void decode(SubtitleInputBuffer inputBuffer) {
ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit());
while (ccData.bytesLeft() >= 3) {
int ccTypeAndValid = (ccData.readUnsignedByte() & 0x07);
int ccType = ccTypeAndValid & (DTVCC_PACKET_DATA | DTVCC_PACKET_START);
boolean ccValid = (ccTypeAndValid & CC_VALID_FLAG) == CC_VALID_FLAG;
byte ccData1 = (byte) ccData.readUnsignedByte();
byte ccData2 = (byte) ccData.readUnsignedByte();
// Ignore any non-CEA-708 data
if (ccType != DTVCC_PACKET_DATA && ccType != DTVCC_PACKET_START) {
continue;
}
if (!ccValid) {
finalizeCurrentPacket();
continue;
}
if (ccType == DTVCC_PACKET_START) {
finalizeCurrentPacket();
int sequenceNumber = (ccData1 & 0xC0) >> 6; // first 2 bits
int packetSize = ccData1 & 0x3F; // last 6 bits
if (packetSize == 0) {
packetSize = 64;
}
currentDtvCcPacket = new DtvCcPacket(sequenceNumber, packetSize);
currentDtvCcPacket.packetData[currentDtvCcPacket.currentIndex++] = ccData2;
} else {
// The only remaining valid packet type is DTVCC_PACKET_DATA
Assertions.checkArgument(ccType == DTVCC_PACKET_DATA);
if (currentDtvCcPacket == null) {
Log.e(TAG, "Encountered DTVCC_PACKET_DATA before DTVCC_PACKET_START");
continue;
}
currentDtvCcPacket.packetData[currentDtvCcPacket.currentIndex++] = ccData1;
currentDtvCcPacket.packetData[currentDtvCcPacket.currentIndex++] = ccData2;
}
if (currentDtvCcPacket.currentIndex == (currentDtvCcPacket.packetSize * 2 - 1)) {
finalizeCurrentPacket();
}
}
}
private void finalizeCurrentPacket() {
if (currentDtvCcPacket == null) {
// No packet to finalize;
return;
}
processCurrentPacket();
currentDtvCcPacket = null;
}
private void processCurrentPacket() {
if (currentDtvCcPacket.currentIndex != (currentDtvCcPacket.packetSize * 2 - 1)) {
Log.w(TAG, "DtvCcPacket ended prematurely; size is " + (currentDtvCcPacket.packetSize * 2 - 1)
+ ", but current index is " + currentDtvCcPacket.currentIndex + " (sequence number "
+ currentDtvCcPacket.sequenceNumber + ")");
}
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);
}
// 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;
}
while (serviceBlockPacket.bitsLeft() > 0) {
int command = serviceBlockPacket.readBits(8);
if (command != COMMAND_EXT1) {
if (command <= GROUP_C0_END) {
handleC0Command(command);
} else if (command <= GROUP_G0_END) {
handleG0Character(command);
} else if (command <= GROUP_C1_END) {
handleC1Command(command);
// Cues are always updated after a C1 command
cues = getDisplayCues();
} else if (command <= GROUP_G1_END) {
handleG1Character(command);
} else {
Log.w(TAG, "Invalid base command: " + command);
}
} else {
// Read the extended command
command = serviceBlockPacket.readBits(8);
if (command <= GROUP_C2_END) {
handleC2Command(command);
} else if (command <= GROUP_G2_END) {
handleG2Character(command);
} else if (command <= GROUP_C3_END) {
handleC3Command(command);
} else if (command <= GROUP_G3_END) {
handleG3Character(command);
} else {
Log.w(TAG, "Invalid extended command: " + command);
}
}
}
}
private void handleC0Command(int command) {
switch (command) {
case COMMAND_NUL:
// Do nothing.
break;
case COMMAND_ETX:
cues = getDisplayCues();
break;
case COMMAND_BS:
currentCueBuilder.backspace();
break;
case COMMAND_FF:
resetCueBuilders();
break;
case COMMAND_CR:
currentCueBuilder.append('\n');
break;
case COMMAND_HCR:
// TODO: Add support for this command.
break;
default:
if (command >= COMMAND_EXT1_START && command <= COMMAND_EXT1_END) {
Log.w(TAG, "Currently unsupported COMMAND_EXT1 Command: " + command);
serviceBlockPacket.skipBits(8);
} else if (command >= COMMAND_P16_START && command <= COMMAND_P16_END) {
Log.w(TAG, "Currently unsupported COMMAND_P16 Command: " + command);
serviceBlockPacket.skipBits(16);
} else {
Log.w(TAG, "Invalid C0 command: " + command);
}
}
}
private void handleC1Command(int command) {
int window;
switch (command) {
case COMMAND_CW0:
case COMMAND_CW1:
case COMMAND_CW2:
case COMMAND_CW3:
case COMMAND_CW4:
case COMMAND_CW5:
case COMMAND_CW6:
case COMMAND_CW7:
window = (command - COMMAND_CW0);
if (currentWindow != window) {
currentWindow = window;
currentCueBuilder = cueBuilders[window];
}
break;
case COMMAND_CLW:
for (int i = 1; i <= NUM_WINDOWS; i++) {
if (serviceBlockPacket.readBit()) {
cueBuilders[NUM_WINDOWS - i].clear();
}
}
break;
case COMMAND_DSW:
for (int i = 1; i <= NUM_WINDOWS; i++) {
if (serviceBlockPacket.readBit()) {
cueBuilders[NUM_WINDOWS - i].setVisibility(true);
}
}
break;
case COMMAND_HDW:
for (int i = 1; i <= NUM_WINDOWS; i++) {
if (serviceBlockPacket.readBit()) {
cueBuilders[NUM_WINDOWS - i].setVisibility(false);
}
}
break;
case COMMAND_TGW:
for (int i = 1; i <= NUM_WINDOWS; i++) {
if (serviceBlockPacket.readBit()) {
CueBuilder cueBuilder = cueBuilders[NUM_WINDOWS - i];
cueBuilder.setVisibility(!cueBuilder.isVisible());
}
}
break;
case COMMAND_DLW:
for (int i = 1; i <= NUM_WINDOWS; i++) {
if (serviceBlockPacket.readBit()) {
cueBuilders[NUM_WINDOWS - i].reset();
}
}
break;
case COMMAND_DLY:
// TODO: Add support for delay commands.
serviceBlockPacket.skipBits(8);
break;
case COMMAND_DLC:
// TODO: Add support for delay commands.
break;
case COMMAND_RST:
resetCueBuilders();
break;
case COMMAND_SPA:
if (!currentCueBuilder.isDefined()) {
// ignore this command if the current window/cue isn't defined
serviceBlockPacket.skipBits(16);
} else {
handleSetPenAttributes();
}
break;
case COMMAND_SPC:
if (!currentCueBuilder.isDefined()) {
// ignore this command if the current window/cue isn't defined
serviceBlockPacket.skipBits(24);
} else {
handleSetPenColor();
}
break;
case COMMAND_SPL:
if (!currentCueBuilder.isDefined()) {
// ignore this command if the current window/cue isn't defined
serviceBlockPacket.skipBits(16);
} else {
handleSetPenLocation();
}
break;
case COMMAND_SWA:
if (!currentCueBuilder.isDefined()) {
// ignore this command if the current window/cue isn't defined
serviceBlockPacket.skipBits(32);
} else {
handleSetWindowAttributes();
}
break;
case COMMAND_DF0:
case COMMAND_DF1:
case COMMAND_DF2:
case COMMAND_DF3:
case COMMAND_DS4:
case COMMAND_DF5:
case COMMAND_DF6:
case COMMAND_DF7:
window = (command - COMMAND_DF0);
handleDefineWindow(window);
break;
default:
Log.w(TAG, "Invalid C1 command: " + command);
}
}
private void handleC2Command(int command) {
// C2 Table doesn't contain any commands in CEA-708-B, but we do need to skip bytes
if (command <= 0x0F) {
// Do nothing.
} else if (command <= 0x0F) {
serviceBlockPacket.skipBits(8);
} else if (command <= 0x17) {
serviceBlockPacket.skipBits(16);
} else if (command <= 0x1F) {
serviceBlockPacket.skipBits(24);
}
}
private void handleC3Command(int command) {
// C3 Table doesn't contain any commands in CEA-708-B, but we do need to skip bytes
if (command <= 0x87) {
serviceBlockPacket.skipBits(32);
} else if (command <= 0x8F) {
serviceBlockPacket.skipBits(40);
} else if (command <= 0x9F) {
// 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
// command in bytes
serviceBlockPacket.skipBits(2);
int length = serviceBlockPacket.readBits(6);
serviceBlockPacket.skipBits(8 * length);
}
}
private void handleG0Character(int characterCode) {
if (characterCode == CHARACTER_MN) {
currentCueBuilder.append('\u266B');
} else {
currentCueBuilder.append((char) (characterCode & 0xFF));
}
}
private void handleG1Character(int characterCode) {
currentCueBuilder.append((char) (characterCode & 0xFF));
}
private void handleG2Character(int characterCode) {
switch (characterCode) {
case CHARACTER_TSP:
currentCueBuilder.append('\u0020');
break;
case CHARACTER_NBTSP:
currentCueBuilder.append('\u00A0');
break;
case CHARACTER_ELLIPSIS:
currentCueBuilder.append('\u2026');
break;
case CHARACTER_BIG_CARONS:
currentCueBuilder.append('\u0160');
break;
case CHARACTER_BIG_OE:
currentCueBuilder.append('\u0152');
break;
case CHARACTER_SOLID_BLOCK:
currentCueBuilder.append('\u2588');
break;
case CHARACTER_OPEN_SINGLE_QUOTE:
currentCueBuilder.append('\u2018');
break;
case CHARACTER_CLOSE_SINGLE_QUOTE:
currentCueBuilder.append('\u2019');
break;
case CHARACTER_OPEN_DOUBLE_QUOTE:
currentCueBuilder.append('\u201C');
break;
case CHARACTER_CLOSE_DOUBLE_QUOTE:
currentCueBuilder.append('\u201D');
break;
case CHARACTER_BOLD_BULLET:
currentCueBuilder.append('\u2022');
break;
case CHARACTER_TM:
currentCueBuilder.append('\u2122');
break;
case CHARACTER_SMALL_CARONS:
currentCueBuilder.append('\u0161');
break;
case CHARACTER_SMALL_OE:
currentCueBuilder.append('\u0153');
break;
case CHARACTER_SM:
currentCueBuilder.append('\u2120');
break;
case CHARACTER_DIAERESIS_Y:
currentCueBuilder.append('\u0178');
break;
case CHARACTER_ONE_EIGHTH:
currentCueBuilder.append('\u215B');
break;
case CHARACTER_THREE_EIGHTHS:
currentCueBuilder.append('\u215C');
break;
case CHARACTER_FIVE_EIGHTHS:
currentCueBuilder.append('\u215D');
break;
case CHARACTER_SEVEN_EIGHTHS:
currentCueBuilder.append('\u215E');
break;
case CHARACTER_VERTICAL_BORDER:
currentCueBuilder.append('\u2502');
break;
case CHARACTER_UPPER_RIGHT_BORDER:
currentCueBuilder.append('\u2510');
break;
case CHARACTER_LOWER_LEFT_BORDER:
currentCueBuilder.append('\u2514');
break;
case CHARACTER_HORIZONTAL_BORDER:
currentCueBuilder.append('\u2500');
break;
case CHARACTER_LOWER_RIGHT_BORDER:
currentCueBuilder.append('\u2518');
break;
case CHARACTER_UPPER_LEFT_BORDER:
currentCueBuilder.append('\u250C');
break;
default:
Log.w(TAG, "Invalid G2 character: " + characterCode);
// The CEA-708 specification doesn't specify what to do in the case of an unexpected
// value in the G2 character range, so we ignore it.
}
}
private void handleG3Character(int characterCode) {
if (characterCode == 0xA0) {
currentCueBuilder.append('\u33C4');
} else {
Log.w(TAG, "Invalid G3 character: " + characterCode);
// Substitute any unsupported G3 character with an underscore as per CEA-708 specification.
currentCueBuilder.append('_');
}
}
private void handleSetPenAttributes() {
// the SetPenAttributes command contains 2 bytes of data
// first byte
int textTag = serviceBlockPacket.readBits(4);
int offset = serviceBlockPacket.readBits(2);
int penSize = serviceBlockPacket.readBits(2);
// second byte
boolean italicsToggle = serviceBlockPacket.readBit();
boolean underlineToggle = serviceBlockPacket.readBit();
int edgeType = serviceBlockPacket.readBits(3);
int fontStyle = serviceBlockPacket.readBits(3);
currentCueBuilder.setPenAttributes(textTag, offset, penSize, italicsToggle, underlineToggle,
edgeType, fontStyle);
}
private void handleSetPenColor() {
// the SetPenColor command contains 3 bytes of data
// first byte
int foregroundO = serviceBlockPacket.readBits(2);
int foregroundR = serviceBlockPacket.readBits(2);
int foregroundG = serviceBlockPacket.readBits(2);
int foregroundB = serviceBlockPacket.readBits(2);
int foregroundColor = CueBuilder.getArgbColorFromCeaColor(foregroundR, foregroundG, foregroundB,
foregroundO);
// second byte
int backgroundO = serviceBlockPacket.readBits(2);
int backgroundR = serviceBlockPacket.readBits(2);
int backgroundG = serviceBlockPacket.readBits(2);
int backgroundB = serviceBlockPacket.readBits(2);
int backgroundColor = CueBuilder.getArgbColorFromCeaColor(backgroundR, backgroundG, backgroundB,
backgroundO);
// third byte
serviceBlockPacket.skipBits(2); // null padding
int edgeR = serviceBlockPacket.readBits(2);
int edgeG = serviceBlockPacket.readBits(2);
int edgeB = serviceBlockPacket.readBits(2);
int edgeColor = CueBuilder.getArgbColorFromCeaColor(edgeR, edgeG, edgeB);
currentCueBuilder.setPenColor(foregroundColor, backgroundColor, edgeColor);
}
private void handleSetPenLocation() {
// the SetPenLocation command contains 2 bytes of data
// first byte
serviceBlockPacket.skipBits(4);
int row = serviceBlockPacket.readBits(4);
// second byte
serviceBlockPacket.skipBits(2);
int column = serviceBlockPacket.readBits(6);
currentCueBuilder.setPenLocation(row, column);
}
private void handleSetWindowAttributes() {
// the SetWindowAttributes command contains 4 bytes of data
// first byte
int fillO = serviceBlockPacket.readBits(2);
int fillR = serviceBlockPacket.readBits(2);
int fillG = serviceBlockPacket.readBits(2);
int fillB = serviceBlockPacket.readBits(2);
int fillColor = CueBuilder.getArgbColorFromCeaColor(fillR, fillG, fillB, fillO);
// second byte
int borderType = serviceBlockPacket.readBits(2); // only the lower 2 bits of borderType
int borderR = serviceBlockPacket.readBits(2);
int borderG = serviceBlockPacket.readBits(2);
int borderB = serviceBlockPacket.readBits(2);
int borderColor = CueBuilder.getArgbColorFromCeaColor(borderR, borderG, borderB);
// third byte
if (serviceBlockPacket.readBit()) {
borderType |= 0x04; // set the top bit of the 3-bit borderType
}
boolean wordWrapToggle = serviceBlockPacket.readBit();
int printDirection = serviceBlockPacket.readBits(2);
int scrollDirection = serviceBlockPacket.readBits(2);
int justification = serviceBlockPacket.readBits(2);
// fourth byte
// Note that we don't intend to support display effects
serviceBlockPacket.skipBits(8); // effectSpeed(4), effectDirection(2), displayEffect(2)
currentCueBuilder.setWindowAttributes(fillColor, borderColor, wordWrapToggle, borderType,
printDirection, scrollDirection, justification);
}
private void handleDefineWindow(int window) {
CueBuilder cueBuilder = cueBuilders[window];
// the DefineWindow command contains 6 bytes of data
// first byte
serviceBlockPacket.skipBits(2); // null padding
boolean visible = serviceBlockPacket.readBit();
boolean rowLock = serviceBlockPacket.readBit();
boolean columnLock = serviceBlockPacket.readBit();
int priority = serviceBlockPacket.readBits(3);
// second byte
boolean relativePositioning = serviceBlockPacket.readBit();
int verticalAnchor = serviceBlockPacket.readBits(7);
// third byte
int horizontalAnchor = serviceBlockPacket.readBits(8);
// fourth byte
int anchorId = serviceBlockPacket.readBits(4);
int rowCount = serviceBlockPacket.readBits(4);
// fifth byte
serviceBlockPacket.skipBits(2); // null padding
int columnCount = serviceBlockPacket.readBits(6);
// sixth byte
serviceBlockPacket.skipBits(2); // null padding
int windowStyle = serviceBlockPacket.readBits(3);
int penStyle = serviceBlockPacket.readBits(3);
cueBuilder.defineWindow(visible, rowLock, columnLock, priority, relativePositioning,
verticalAnchor, horizontalAnchor, rowCount, columnCount, anchorId, windowStyle, penStyle);
}
private List<Cue> getDisplayCues() {
List<Cea708Cue> displayCues = new ArrayList<>();
for (int i = 0; i < NUM_WINDOWS; i++) {
if (!cueBuilders[i].isEmpty() && cueBuilders[i].isVisible()) {
displayCues.add(cueBuilders[i].build());
}
}
Collections.sort(displayCues);
return Collections.<Cue>unmodifiableList(displayCues);
}
private void resetCueBuilders() {
for (int i = 0; i < NUM_WINDOWS; i++) {
cueBuilders[i].reset();
}
}
private static final class DtvCcPacket {
public final int sequenceNumber;
public final int packetSize;
public final byte[] packetData;
int currentIndex;
public DtvCcPacket(int sequenceNumber, int packetSize) {
this.sequenceNumber = sequenceNumber;
this.packetSize = packetSize;
packetData = new byte[2 * packetSize - 1];
currentIndex = 0;
}
}
// TODO: There is a lot of overlap between Cea708Decoder.CueBuilder and Cea608Decoder.CueBuilder
// which could be refactored into a separate class.
private static final class CueBuilder {
private static final int RELATIVE_CUE_SIZE = 99;
private static final int VERTICAL_SIZE = 74;
private static final int HORIZONTAL_SIZE = 209;
private static final int DEFAULT_PRIORITY = 4;
private static final int MAXIMUM_ROW_COUNT = 15;
private static final int JUSTIFICATION_LEFT = 0;
private static final int JUSTIFICATION_RIGHT = 1;
private static final int JUSTIFICATION_CENTER = 2;
private static final int JUSTIFICATION_FULL = 3;
private static final int DIRECTION_LEFT_TO_RIGHT = 0;
private static final int DIRECTION_RIGHT_TO_LEFT = 1;
private static final int DIRECTION_TOP_TO_BOTTOM = 2;
private static final int DIRECTION_BOTTOM_TO_TOP = 3;
// TODO: Add other border/edge types when utilized.
private static final int BORDER_AND_EDGE_TYPE_NONE = 0;
private static final int BORDER_AND_EDGE_TYPE_UNIFORM = 3;
public static final int COLOR_SOLID_WHITE = getArgbColorFromCeaColor(2, 2, 2, 0);
public static final int COLOR_SOLID_BLACK = getArgbColorFromCeaColor(0, 0, 0, 0);
public static final int COLOR_TRANSPARENT = getArgbColorFromCeaColor(0, 0, 0, 3);
// TODO: Add other sizes when utilized.
private static final int PEN_SIZE_STANDARD = 1;
// TODO: Add other pen font styles when utilized.
private static final int PEN_FONT_STYLE_DEFAULT = 0;
private static final int PEN_FONT_STYLE_MONOSPACED_WITH_SERIFS = 1;
private static final int PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITH_SERIFS = 2;
private static final int PEN_FONT_STYLE_MONOSPACED_WITHOUT_SERIFS = 3;
private static final int PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITHOUT_SERIFS = 4;
// TODO: Add other pen offsets when utilized.
private static final int PEN_OFFSET_NORMAL = 1;
// The window style properties are specified in the CEA-708 specification.
private static final int[] WINDOW_STYLE_JUSTIFICATION = new int[]{
JUSTIFICATION_LEFT, JUSTIFICATION_LEFT, JUSTIFICATION_LEFT,
JUSTIFICATION_LEFT, JUSTIFICATION_LEFT, JUSTIFICATION_CENTER,
JUSTIFICATION_LEFT
};
private static final int[] WINDOW_STYLE_PRINT_DIRECTION = new int[]{
DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT,
DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT,
DIRECTION_TOP_TO_BOTTOM
};
private static final int[] WINDOW_STYLE_SCROLL_DIRECTION = new int[]{
DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP,
DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP,
DIRECTION_RIGHT_TO_LEFT
};
private static final boolean[] WINDOW_STYLE_WORD_WRAP = new boolean[]{
false, false, false, true, true, true, false
};
private static final int[] WINDOW_STYLE_FILL = new int[]{
COLOR_SOLID_BLACK, COLOR_TRANSPARENT, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK,
COLOR_TRANSPARENT, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK
};
// The pen style properties are specified in the CEA-708 specification.
private static final int[] PEN_STYLE_FONT_STYLE = new int[]{
PEN_FONT_STYLE_DEFAULT, PEN_FONT_STYLE_MONOSPACED_WITH_SERIFS,
PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITH_SERIFS, PEN_FONT_STYLE_MONOSPACED_WITHOUT_SERIFS,
PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITHOUT_SERIFS,
PEN_FONT_STYLE_MONOSPACED_WITHOUT_SERIFS,
PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITHOUT_SERIFS
};
private static final int[] PEN_STYLE_EDGE_TYPE = new int[]{
BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_NONE,
BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_UNIFORM,
BORDER_AND_EDGE_TYPE_UNIFORM
};
private static final int[] PEN_STYLE_BACKGROUND = new int[]{
COLOR_SOLID_BLACK, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK,
COLOR_SOLID_BLACK, COLOR_TRANSPARENT, COLOR_TRANSPARENT};
private final List<SpannableString> rolledUpCaptions;
private final SpannableStringBuilder captionStringBuilder;
// Window/Cue properties
private boolean defined;
private boolean visible;
private int priority;
private boolean relativePositioning;
private int verticalAnchor;
private int horizontalAnchor;
private int anchorId;
private int rowCount;
private boolean rowLock;
private int justification;
private int windowStyleId;
private int penStyleId;
private int windowFillColor;
// Pen/Text properties
private int italicsStartPosition;
private int underlineStartPosition;
private int foregroundColorStartPosition;
private int foregroundColor;
private int backgroundColorStartPosition;
private int backgroundColor;
public CueBuilder() {
rolledUpCaptions = new LinkedList<>();
captionStringBuilder = new SpannableStringBuilder();
reset();
}
public boolean isEmpty() {
return !isDefined() || (rolledUpCaptions.isEmpty() && captionStringBuilder.length() == 0);
}
public void reset() {
clear();
defined = false;
visible = false;
priority = DEFAULT_PRIORITY;
relativePositioning = false;
verticalAnchor = 0;
horizontalAnchor = 0;
anchorId = 0;
rowCount = MAXIMUM_ROW_COUNT;
rowLock = true;
justification = JUSTIFICATION_LEFT;
windowStyleId = 0;
penStyleId = 0;
windowFillColor = COLOR_SOLID_BLACK;
foregroundColor = COLOR_SOLID_WHITE;
backgroundColor = COLOR_SOLID_BLACK;
}
public void clear() {
rolledUpCaptions.clear();
captionStringBuilder.clear();
italicsStartPosition = C.POSITION_UNSET;
underlineStartPosition = C.POSITION_UNSET;
foregroundColorStartPosition = C.POSITION_UNSET;
backgroundColorStartPosition = C.POSITION_UNSET;
}
public boolean isDefined() {
return defined;
}
public void setVisibility(boolean visible) {
this.visible = visible;
}
public boolean isVisible() {
return visible;
}
public void defineWindow(boolean visible, boolean rowLock, boolean columnLock, int priority,
boolean relativePositioning, int verticalAnchor, int horizontalAnchor, int rowCount,
int columnCount, int anchorId, int windowStyleId, int penStyleId) {
this.defined = true;
this.visible = visible;
this.rowLock = rowLock;
this.priority = priority;
this.relativePositioning = relativePositioning;
this.verticalAnchor = verticalAnchor;
this.horizontalAnchor = horizontalAnchor;
this.anchorId = anchorId;
// Decoders must add one to rowCount to get the desired number of rows.
if (this.rowCount != rowCount + 1) {
this.rowCount = rowCount + 1;
// Trim any rolled up captions that are no longer valid, if applicable.
while ((rowLock && (rolledUpCaptions.size() >= this.rowCount))
|| (rolledUpCaptions.size() >= MAXIMUM_ROW_COUNT)) {
rolledUpCaptions.remove(0);
}
}
// TODO: Add support for column lock and count.
if (windowStyleId != 0 && this.windowStyleId != windowStyleId) {
this.windowStyleId = windowStyleId;
// windowStyleId is 1-based.
int windowStyleIdIndex = windowStyleId - 1;
// Note that Border type and border color are the same for all window styles.
setWindowAttributes(WINDOW_STYLE_FILL[windowStyleIdIndex], COLOR_TRANSPARENT,
WINDOW_STYLE_WORD_WRAP[windowStyleIdIndex], BORDER_AND_EDGE_TYPE_NONE,
WINDOW_STYLE_PRINT_DIRECTION[windowStyleIdIndex],
WINDOW_STYLE_SCROLL_DIRECTION[windowStyleIdIndex],
WINDOW_STYLE_JUSTIFICATION[windowStyleIdIndex]);
}
if (penStyleId != 0 && this.penStyleId != penStyleId) {
this.penStyleId = penStyleId;
// penStyleId is 1-based.
int penStyleIdIndex = penStyleId - 1;
// Note that pen size, offset, italics, underline, foreground color, and foreground
// opacity are the same for all pen styles.
setPenAttributes(0, PEN_OFFSET_NORMAL, PEN_SIZE_STANDARD, false, false,
PEN_STYLE_EDGE_TYPE[penStyleIdIndex], PEN_STYLE_FONT_STYLE[penStyleIdIndex]);
setPenColor(COLOR_SOLID_WHITE, PEN_STYLE_BACKGROUND[penStyleIdIndex], COLOR_SOLID_BLACK);
}
}
public void setWindowAttributes(int fillColor, int borderColor, boolean wordWrapToggle,
int borderType, int printDirection, int scrollDirection, int justification) {
this.windowFillColor = fillColor;
// TODO: Add support for border color and types.
// TODO: Add support for word wrap.
// TODO: Add support for other scroll directions.
// TODO: Add support for other print directions.
this.justification = justification;
}
public void setPenAttributes(int textTag, int offset, int penSize, boolean italicsToggle,
boolean underlineToggle, int edgeType, int fontStyle) {
// TODO: Add support for text tags.
// TODO: Add support for other offsets.
// TODO: Add support for other pen sizes.
if (italicsStartPosition != C.POSITION_UNSET) {
if (!italicsToggle) {
captionStringBuilder.setSpan(new StyleSpan(Typeface.ITALIC), italicsStartPosition,
captionStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
italicsStartPosition = C.POSITION_UNSET;
}
} else if (italicsToggle) {
italicsStartPosition = captionStringBuilder.length();
}
if (underlineStartPosition != C.POSITION_UNSET) {
if (!underlineToggle) {
captionStringBuilder.setSpan(new UnderlineSpan(), underlineStartPosition,
captionStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
underlineStartPosition = C.POSITION_UNSET;
}
} else if (underlineToggle) {
underlineStartPosition = captionStringBuilder.length();
}
// TODO: Add support for edge types.
// TODO: Add support for other font styles.
}
public void setPenColor(int foregroundColor, int backgroundColor, int edgeColor) {
if (foregroundColorStartPosition != C.POSITION_UNSET) {
if (this.foregroundColor != foregroundColor) {
captionStringBuilder.setSpan(new ForegroundColorSpan(this.foregroundColor),
foregroundColorStartPosition, captionStringBuilder.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
if (foregroundColor != COLOR_SOLID_WHITE) {
foregroundColorStartPosition = captionStringBuilder.length();
this.foregroundColor = foregroundColor;
}
if (backgroundColorStartPosition != C.POSITION_UNSET) {
if (this.backgroundColor != backgroundColor) {
captionStringBuilder.setSpan(new BackgroundColorSpan(this.backgroundColor),
backgroundColorStartPosition, captionStringBuilder.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
if (backgroundColor != COLOR_SOLID_BLACK) {
backgroundColorStartPosition = captionStringBuilder.length();
this.backgroundColor = backgroundColor;
}
// TODO: Add support for edge color.
}
public void setPenLocation(int row, int column) {
// TODO: Support moving the pen location with a window.
}
public void backspace() {
int length = captionStringBuilder.length();
if (length > 0) {
captionStringBuilder.delete(length - 1, length);
}
}
public void append(char text) {
if (text == '\n') {
rolledUpCaptions.add(buildSpannableString());
captionStringBuilder.clear();
if (italicsStartPosition != C.POSITION_UNSET) {
italicsStartPosition = 0;
}
if (underlineStartPosition != C.POSITION_UNSET) {
underlineStartPosition = 0;
}
if (foregroundColorStartPosition != C.POSITION_UNSET) {
foregroundColorStartPosition = 0;
}
if (backgroundColorStartPosition != C.POSITION_UNSET) {
backgroundColorStartPosition = 0;
}
while ((rowLock && (rolledUpCaptions.size() >= rowCount))
|| (rolledUpCaptions.size() >= MAXIMUM_ROW_COUNT)) {
rolledUpCaptions.remove(0);
}
} else {
captionStringBuilder.append(text);
}
}
public SpannableString buildSpannableString() {
SpannableStringBuilder spannableStringBuilder =
new SpannableStringBuilder(captionStringBuilder);
int length = spannableStringBuilder.length();
if (length > 0) {
if (italicsStartPosition != C.POSITION_UNSET) {
spannableStringBuilder.setSpan(new StyleSpan(Typeface.ITALIC), italicsStartPosition,
length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (underlineStartPosition != C.POSITION_UNSET) {
spannableStringBuilder.setSpan(new UnderlineSpan(), underlineStartPosition,
length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (foregroundColorStartPosition != C.POSITION_UNSET) {
spannableStringBuilder.setSpan(new ForegroundColorSpan(foregroundColor),
foregroundColorStartPosition, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (backgroundColorStartPosition != C.POSITION_UNSET) {
spannableStringBuilder.setSpan(new BackgroundColorSpan(backgroundColor),
backgroundColorStartPosition, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return new SpannableString(spannableStringBuilder);
}
public Cea708Cue build() {
if (isEmpty()) {
// The cue is empty.
return null;
}
SpannableStringBuilder cueString = new SpannableStringBuilder();
// Add any rolled up captions, separated by new lines.
for (int i = 0; i < rolledUpCaptions.size(); i++) {
cueString.append(rolledUpCaptions.get(i));
cueString.append('\n');
}
// Add the current line.
cueString.append(buildSpannableString());
// TODO: Add support for right-to-left languages (i.e. where right would correspond to normal
// alignment).
Alignment alignment;
switch (justification) {
case JUSTIFICATION_FULL:
// TODO: Add support for full justification.
case JUSTIFICATION_LEFT:
alignment = Alignment.ALIGN_NORMAL;
break;
case JUSTIFICATION_RIGHT:
alignment = Alignment.ALIGN_OPPOSITE;
break;
case JUSTIFICATION_CENTER:
alignment = Alignment.ALIGN_CENTER;
break;
default:
throw new IllegalArgumentException("Unexpected justification value: " + justification);
}
float position;
float line;
if (relativePositioning) {
position = (float) horizontalAnchor / RELATIVE_CUE_SIZE;
line = (float) verticalAnchor / RELATIVE_CUE_SIZE;
} else {
position = (float) horizontalAnchor / HORIZONTAL_SIZE;
line = (float) verticalAnchor / VERTICAL_SIZE;
}
// Apply screen-edge padding to the line and position.
position = (position * 0.9f) + 0.05f;
line = (line * 0.9f) + 0.05f;
// anchorId specifies where the anchor should be placed on the caption cue/window. The 9
// possible configurations are as follows:
// 0-----1-----2
// | |
// 3 4 5
// | |
// 6-----7-----8
@AnchorType int verticalAnchorType;
if (anchorId % 3 == 0) {
verticalAnchorType = Cue.ANCHOR_TYPE_START;
} else if (anchorId % 3 == 1) {
verticalAnchorType = Cue.ANCHOR_TYPE_MIDDLE;
} else {
verticalAnchorType = Cue.ANCHOR_TYPE_END;
}
// TODO: Add support for right-to-left languages (i.e. where start is on the right).
@AnchorType int horizontalAnchorType;
if (anchorId / 3 == 0) {
horizontalAnchorType = Cue.ANCHOR_TYPE_START;
} else if (anchorId / 3 == 1) {
horizontalAnchorType = Cue.ANCHOR_TYPE_MIDDLE;
} else {
horizontalAnchorType = Cue.ANCHOR_TYPE_END;
}
boolean windowColorSet = (windowFillColor != COLOR_SOLID_BLACK);
return new Cea708Cue(cueString, alignment, line, Cue.LINE_TYPE_FRACTION, verticalAnchorType,
position, horizontalAnchorType, Cue.DIMEN_UNSET, windowColorSet, windowFillColor,
priority);
}
public static int getArgbColorFromCeaColor(int red, int green, int blue) {
return getArgbColorFromCeaColor(red, green, blue, 0);
}
public static int getArgbColorFromCeaColor(int red, int green, int blue, int opacity) {
Assertions.checkIndex(red, 0, 4);
Assertions.checkIndex(green, 0, 4);
Assertions.checkIndex(blue, 0, 4);
Assertions.checkIndex(opacity, 0, 4);
int alpha;
switch (opacity) {
case 0:
case 1:
// Note the value of '1' is actually FLASH, but we don't support that.
alpha = 255;
break;
case 2:
alpha = 127;
break;
case 3:
alpha = 0;
break;
default:
alpha = 255;
}
// TODO: Add support for the Alternative Minimum Color List or the full 64 RGB combinations.
// Return values based on the Minimum Color List
return Color.argb(alpha,
(red > 1 ? 255 : 0),
(green > 1 ? 255 : 0),
(blue > 1 ? 255 : 0));
}
}
}
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