Home | History | Annotate | Download | only in cc
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.usbtuner.cc;
     18 
     19 import android.os.SystemClock;
     20 import android.support.annotation.IntDef;
     21 import android.util.Log;
     22 import android.util.SparseIntArray;
     23 
     24 import com.android.usbtuner.data.Cea708Data;
     25 import com.android.usbtuner.data.Cea708Data.CaptionColor;
     26 import com.android.usbtuner.data.Cea708Data.CaptionEvent;
     27 import com.android.usbtuner.data.Cea708Data.CaptionPenAttr;
     28 import com.android.usbtuner.data.Cea708Data.CaptionPenColor;
     29 import com.android.usbtuner.data.Cea708Data.CaptionPenLocation;
     30 import com.android.usbtuner.data.Cea708Data.CaptionWindow;
     31 import com.android.usbtuner.data.Cea708Data.CaptionWindowAttr;
     32 import com.android.usbtuner.data.Cea708Data.CcPacket;
     33 import com.android.usbtuner.util.ByteArrayBuffer;
     34 
     35 import java.io.UnsupportedEncodingException;
     36 import java.lang.annotation.Retention;
     37 import java.lang.annotation.RetentionPolicy;
     38 import java.nio.ByteBuffer;
     39 import java.nio.charset.StandardCharsets;
     40 import java.util.Arrays;
     41 import java.util.TreeSet;
     42 
     43 /**
     44  * A class for parsing CEA-708, which is the standard for closed captioning for ATSC DTV.
     45  *
     46  * <p>ATSC DTV closed caption data are carried on picture user data of video streams.
     47  * This class starts to parse from picture user data payload, so extraction process of user_data
     48  * from video streams is up to outside of this code.
     49  *
     50  * <p>There are 4 steps to decode user_data to provide closed caption services.
     51  *
     52  * <h3>Step 1. user_data -&gt; CcPacket ({@link #parseClosedCaption} method)</h3>
     53  *
     54  * <p>First, user_data consists of cc_data packets, which are 3-byte segments. Here, CcPacket is a
     55  * collection of cc_data packets in a frame along with same presentation timestamp. Because cc_data
     56  * packets must be reassembled in the frame display order, CcPackets are reordered.
     57  *
     58  * <h3>Step 2. CcPacket -&gt; DTVCC packet ({@link #parseCcPacket} method)</h3>
     59  *
     60  * <p>Each cc_data packet has a one byte for declaring a type of itself and data validity, and the
     61  * subsequent two bytes for input data of a DTVCC packet. There are 4 types for cc_data packet.
     62  * We're interested in DTVCC_PACKET_START(type 3) and DTVCC_PACKET_DATA(type 2). Each DTVCC packet
     63  * begins with DTVCC_PACKET_START(type 3) and the following cc_data packets which has
     64  * DTVCC_PACKET_DATA(type 2) are appended into the DTVCC packet being assembled.
     65  *
     66  * <h3>Step 3. DTVCC packet -&gt; Service Blocks ({@link #parseDtvCcPacket} method)</h3>
     67  *
     68  * <p>A DTVCC packet consists of multiple service blocks. Each service block represents a caption
     69  * track and has a service number, which ranges from 1 to 63, that denotes caption track identity.
     70  * In here, we listen at most one chosen caption track by {@link #mListenServiceNumber}.
     71  * Otherwise, just skip the other service blocks.
     72  *
     73  * <h3>Step 4. Interpreting Service Block Data ({@link #parseServiceBlockData}, {@code parseXX},
     74  * and {@link #parseExt1} methods)</h3>
     75  *
     76  * <p>Service block data is actual caption stream. it looks similar to telnet. It uses most parts of
     77  * ASCII table and consists of specially defined commands and some ASCII control codes which work
     78  * in a behavior slightly different from their original purpose. ASCII control codes and caption
     79  * commands are explicit instructions that control the state of a closed caption service and the
     80  * other ASCII and text codes are implicit instructions that send their characters to buffer.
     81  *
     82  * <p>There are 4 main code groups and 4 extended code groups. Both the range of code groups are the
     83  * same as the range of a byte.
     84  *
     85  * <p>4 main code groups: C0, C1, G0, G1
     86  * <br>4 extended code groups: C2, C3, G2, G3
     87  *
     88  * <p>Each code group has its own handle method. For example, {@link #parseC0} handles C0 code group
     89  * and so on. And {@link #parseServiceBlockData} method maps a stream on the main code groups while
     90  * {@link #parseExt1} method maps on the extended code groups.
     91  *
     92  * <p>The main code groups:
     93  * <ul>
     94  * <li>C0 - contains modified ASCII control codes. It is not intended by CEA-708 but Korea TTA
     95  *      standard for ATSC CC uses P16 character heavily, which is unclear entity in CEA-708 doc,
     96  *      even for the alphanumeric characters instead of ASCII characters.</li>
     97  * <li>C1 - contains the caption commands. There are 3 categories of a caption command.</li>
     98  * <ul>
     99  * <li>Window commands: The window commands control a caption window which is addressable area being
    100  *                  with in the Safe title area. (CWX, CLW, DSW, HDW, TGW, DLW, SWA, DFX)</li>
    101  * <li>Pen commands: Th pen commands control text style and location. (SPA, SPC, SPL)</li>
    102  * <li>Job commands: The job commands make a delay and recover from the delay. (DLY, DLC, RST)</li>
    103  * </ul>
    104  * <li>G0 - same as printable ASCII character set except music note character.</li>
    105  * <li>G1 - same as ISO 8859-1 Latin 1 character set.</li>
    106  * </ul>
    107  * <p>Most of the extended code groups are being skipped.
    108  *
    109  */
    110 public class Cea708Parser {
    111     private static final String TAG = "Cea708Parser";
    112     private static final boolean DEBUG = false;
    113 
    114     // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps.
    115     private static final int MAX_ALLOCATED_SIZE = 9600 / 8;
    116     private static final String MUSIC_NOTE_CHAR = new String(
    117             "\u266B".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
    118 
    119     // The following values are denoting the type of closed caption data.
    120     // See CEA-708B section 4.4.1.
    121     private static final int CC_TYPE_DTVCC_PACKET_START = 3;
    122     private static final int CC_TYPE_DTVCC_PACKET_DATA = 2;
    123 
    124     // The following values are defined in CEA-708B Figure 4 and 6.
    125     private static final int DTVCC_MAX_PACKET_SIZE = 64;
    126     private static final int DTVCC_PACKET_SIZE_SCALE_FACTOR = 2;
    127     private static final int DTVCC_EXTENDED_SERVICE_NUMBER_POINT = 7;
    128 
    129     // The following values are for seeking closed caption tracks.
    130     private static final int DISCOVERY_PERIOD_MS = 10000; // 10 sec
    131     private static final int DISCOVERY_NUM_BYTES_THRESHOLD = 10; // 10 bytes
    132     private static final int DISCOVERY_CC_SERVICE_NUMBER_START = 1; // CC1
    133     private static final int DISCOVERY_CC_SERVICE_NUMBER_END = 4; // CC4
    134 
    135     private final ByteArrayBuffer mDtvCcPacket = new ByteArrayBuffer(MAX_ALLOCATED_SIZE);
    136     private final TreeSet<CcPacket> mCcPackets = new TreeSet<>();
    137     private final StringBuffer mBuffer = new StringBuffer();
    138     private final SparseIntArray mDiscoveredNumBytes = new SparseIntArray(); // per service number
    139     private long mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime();
    140     private int mCommand = 0;
    141     private int mListenServiceNumber = 0;
    142     private boolean mDtvCcPacking = false;
    143 
    144     // Assign a dummy listener in order to avoid null checks.
    145     private OnCea708ParserListener mListener = new OnCea708ParserListener() {
    146         @Override
    147         public void emitEvent(CaptionEvent event) {
    148             // do nothing
    149         }
    150 
    151         @Override
    152         public void discoverServiceNumber(int serviceNumber) {
    153             // do nothing
    154         }
    155     };
    156 
    157     /**
    158      * {@link Cea708Parser} emits caption event of three different types.
    159      * {@link OnCea708ParserListener#emitEvent} is invoked with the parameter
    160      * {@link CaptionEvent} to pass all the results to an observer of the decoding process.
    161      *
    162      * <p>{@link CaptionEvent#type} determines the type of the result and
    163      * {@link CaptionEvent#obj} contains the output value of a caption event.
    164      * The observer must do the casting to the corresponding type.
    165      *
    166      * <ul><li>{@code CAPTION_EMIT_TYPE_BUFFER}: Passes a caption text buffer to a observer.
    167      * {@code obj} must be of {@link String}.</li>
    168      *
    169      * <li>{@code CAPTION_EMIT_TYPE_CONTROL}: Passes a caption character control code to a observer.
    170      * {@code obj} must be of {@link Character}.</li>
    171      *
    172      * <li>{@code CAPTION_EMIT_TYPE_CLEAR_COMMAND}: Passes a clear command to a observer.
    173      * {@code obj} must be {@code NULL}.</li></ul>
    174      */
    175     @IntDef({CAPTION_EMIT_TYPE_BUFFER, CAPTION_EMIT_TYPE_CONTROL, CAPTION_EMIT_TYPE_COMMAND_CWX,
    176         CAPTION_EMIT_TYPE_COMMAND_CLW, CAPTION_EMIT_TYPE_COMMAND_DSW, CAPTION_EMIT_TYPE_COMMAND_HDW,
    177         CAPTION_EMIT_TYPE_COMMAND_TGW, CAPTION_EMIT_TYPE_COMMAND_DLW, CAPTION_EMIT_TYPE_COMMAND_DLY,
    178         CAPTION_EMIT_TYPE_COMMAND_DLC, CAPTION_EMIT_TYPE_COMMAND_RST, CAPTION_EMIT_TYPE_COMMAND_SPA,
    179         CAPTION_EMIT_TYPE_COMMAND_SPC, CAPTION_EMIT_TYPE_COMMAND_SPL, CAPTION_EMIT_TYPE_COMMAND_SWA,
    180         CAPTION_EMIT_TYPE_COMMAND_DFX})
    181     @Retention(RetentionPolicy.SOURCE)
    182     public @interface CaptionEmitType {}
    183     public static final int CAPTION_EMIT_TYPE_BUFFER = 1;
    184     public static final int CAPTION_EMIT_TYPE_CONTROL = 2;
    185     public static final int CAPTION_EMIT_TYPE_COMMAND_CWX = 3;
    186     public static final int CAPTION_EMIT_TYPE_COMMAND_CLW = 4;
    187     public static final int CAPTION_EMIT_TYPE_COMMAND_DSW = 5;
    188     public static final int CAPTION_EMIT_TYPE_COMMAND_HDW = 6;
    189     public static final int CAPTION_EMIT_TYPE_COMMAND_TGW = 7;
    190     public static final int CAPTION_EMIT_TYPE_COMMAND_DLW = 8;
    191     public static final int CAPTION_EMIT_TYPE_COMMAND_DLY = 9;
    192     public static final int CAPTION_EMIT_TYPE_COMMAND_DLC = 10;
    193     public static final int CAPTION_EMIT_TYPE_COMMAND_RST = 11;
    194     public static final int CAPTION_EMIT_TYPE_COMMAND_SPA = 12;
    195     public static final int CAPTION_EMIT_TYPE_COMMAND_SPC = 13;
    196     public static final int CAPTION_EMIT_TYPE_COMMAND_SPL = 14;
    197     public static final int CAPTION_EMIT_TYPE_COMMAND_SWA = 15;
    198     public static final int CAPTION_EMIT_TYPE_COMMAND_DFX = 16;
    199 
    200     public interface OnCea708ParserListener {
    201         void emitEvent(CaptionEvent event);
    202         void discoverServiceNumber(int serviceNumber);
    203     }
    204 
    205     public void setListener(OnCea708ParserListener listener) {
    206         if (listener != null) {
    207             mListener = listener;
    208         }
    209     }
    210 
    211     public void setListenServiceNumber(int serviceNumber) {
    212         mListenServiceNumber = serviceNumber;
    213     }
    214 
    215     private void emitCaptionEvent(CaptionEvent captionEvent) {
    216         // Emit the existing string buffer before a new event is arrived.
    217         emitCaptionBuffer();
    218         mListener.emitEvent(captionEvent);
    219     }
    220 
    221     private void emitCaptionBuffer() {
    222         if (mBuffer.length() > 0) {
    223             mListener.emitEvent(new CaptionEvent(CAPTION_EMIT_TYPE_BUFFER, mBuffer.toString()));
    224             mBuffer.setLength(0);
    225         }
    226     }
    227 
    228     // Step 1. user_data -> CcPacket ({@link #parseClosedCaption} method)
    229     public void parseClosedCaption(ByteBuffer data, long framePtsUs) {
    230         int ccCount = data.limit() / 3;
    231         byte[] ccBytes = new byte[3 * ccCount];
    232         for (int i = 0; i < 3 * ccCount; i++) {
    233             ccBytes[i] = data.get(i);
    234         }
    235         CcPacket ccPacket = new CcPacket(ccBytes, ccCount, framePtsUs);
    236         mCcPackets.add(ccPacket);
    237     }
    238 
    239     public boolean processClosedCaptions(long framePtsUs) {
    240         // To get the sorted cc packets that have lower frame pts than current frame pts,
    241         // the following offset divides off the lower side of the packets.
    242         CcPacket offsetPacket = new CcPacket(new byte[0], 0, framePtsUs);
    243         offsetPacket = mCcPackets.lower(offsetPacket);
    244         boolean processed = false;
    245         if (offsetPacket != null) {
    246             while (!mCcPackets.isEmpty() && offsetPacket.compareTo(mCcPackets.first()) >= 0) {
    247                 CcPacket packet = mCcPackets.pollFirst();
    248                 parseCcPacket(packet);
    249                 processed = true;
    250             }
    251         }
    252         return processed;
    253     }
    254 
    255     // Step 2. CcPacket -> DTVCC packet ({@link #parseCcPacket} method)
    256     private void parseCcPacket(CcPacket ccPacket) {
    257         // For the details of cc packet, see ATSC TSG-676 - Table A8.
    258         byte[] bytes = ccPacket.bytes;
    259         int pos = 0;
    260         for (int i = 0; i < ccPacket.ccCount; ++i) {
    261             boolean ccValid = (bytes[pos] & 0x04) != 0;
    262             int ccType = bytes[pos] & 0x03;
    263 
    264             // The dtvcc should be considered complete:
    265             // - if either ccValid is set and ccType is 3
    266             // - or ccValid is clear and ccType is 2 or 3.
    267             if (ccValid) {
    268                 if (ccType == CC_TYPE_DTVCC_PACKET_START) {
    269                     if (mDtvCcPacking) {
    270                         parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length());
    271                         mDtvCcPacket.clear();
    272                     }
    273                     mDtvCcPacking = true;
    274                     mDtvCcPacket.append(bytes[pos + 1]);
    275                     mDtvCcPacket.append(bytes[pos + 2]);
    276                 } else if (mDtvCcPacking && ccType == CC_TYPE_DTVCC_PACKET_DATA) {
    277                     mDtvCcPacket.append(bytes[pos + 1]);
    278                     mDtvCcPacket.append(bytes[pos + 2]);
    279                 }
    280             } else {
    281                 if ((ccType == CC_TYPE_DTVCC_PACKET_START || ccType == CC_TYPE_DTVCC_PACKET_DATA)
    282                         && mDtvCcPacking) {
    283                     mDtvCcPacking = false;
    284                     parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length());
    285                     mDtvCcPacket.clear();
    286                 }
    287             }
    288             pos += 3;
    289         }
    290     }
    291 
    292     // Step 3. DTVCC packet -> Service Blocks ({@link #parseDtvCcPacket} method)
    293     private void parseDtvCcPacket(byte[] data, int limit) {
    294         // For the details of DTVCC packet, see CEA-708B Figure 4.
    295         int pos = 0;
    296         int packetSize = data[pos] & 0x3f;
    297         if (packetSize == 0) {
    298             packetSize = DTVCC_MAX_PACKET_SIZE;
    299         }
    300         int calculatedPacketSize = packetSize * DTVCC_PACKET_SIZE_SCALE_FACTOR;
    301         if (limit != calculatedPacketSize) {
    302             return;
    303         }
    304         ++pos;
    305         int len = pos + calculatedPacketSize;
    306         while (pos < len) {
    307             // For the details of Service Block, see CEA-708B Figure 5 and 6.
    308             int serviceNumber = (data[pos] & 0xe0) >> 5;
    309             int blockSize = data[pos] & 0x1f;
    310             ++pos;
    311             if (serviceNumber == DTVCC_EXTENDED_SERVICE_NUMBER_POINT) {
    312                 serviceNumber = (data[pos] & 0x3f);
    313                 ++pos;
    314 
    315                 // Return if invalid service number
    316                 if (serviceNumber < DTVCC_EXTENDED_SERVICE_NUMBER_POINT) {
    317                     return;
    318                 }
    319             }
    320             if (pos + blockSize > limit) {
    321                 return;
    322             }
    323 
    324             // Send parsed service number in order to find unveiled closed caption tracks which
    325             // are not specified in any ATSC PSIP sections. Since some broadcasts send empty closed
    326             // caption tracks, it detects the proper closed caption tracks by counting the number of
    327             // bytes sent with the same service number during a discovery period.
    328             // The viewer in most TV sets chooses between CC1, CC2, CC3, CC4 to view different
    329             // language captions. Therefore, only CC1, CC2, CC3, CC4 are allowed to be reported.
    330             if (blockSize > 0 && serviceNumber >= DISCOVERY_CC_SERVICE_NUMBER_START
    331                     && serviceNumber <= DISCOVERY_CC_SERVICE_NUMBER_END) {
    332                 mDiscoveredNumBytes.put(
    333                         serviceNumber, blockSize + mDiscoveredNumBytes.get(serviceNumber, 0));
    334             }
    335             if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime()) {
    336                 for (int i = 0; i < mDiscoveredNumBytes.size(); ++i) {
    337                     int discoveredNumBytes = mDiscoveredNumBytes.valueAt(i);
    338                     if (discoveredNumBytes >= DISCOVERY_NUM_BYTES_THRESHOLD) {
    339                         int discoveredServiceNumber = mDiscoveredNumBytes.keyAt(i);
    340                         mListener.discoverServiceNumber(discoveredServiceNumber);
    341                     }
    342                 }
    343                 mDiscoveredNumBytes.clear();
    344                 mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime();
    345             }
    346 
    347             // Skip current service block if either there is no block data or the service number
    348             // is not same as listening service number.
    349             if (blockSize == 0 || serviceNumber != mListenServiceNumber) {
    350                 pos += blockSize;
    351                 continue;
    352             }
    353 
    354             // From this point, starts to read DTVCC coding layer.
    355             // First, identify code groups, which is defined in CEA-708B Section 7.1.
    356             int blockLimit = pos + blockSize;
    357             while (pos < blockLimit) {
    358                 pos = parseServiceBlockData(data, pos);
    359             }
    360 
    361             // Emit the buffer after reading codes.
    362             emitCaptionBuffer();
    363             pos = blockLimit;
    364         }
    365     }
    366 
    367     // Step 4. Main code groups
    368     private int parseServiceBlockData(byte[] data, int pos) {
    369         // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6.
    370         mCommand = data[pos] & 0xff;
    371         ++pos;
    372         if (mCommand == Cea708Data.CODE_C0_EXT1) {
    373             pos = parseExt1(data, pos);
    374         } else if (mCommand >= Cea708Data.CODE_C0_RANGE_START
    375                 && mCommand <= Cea708Data.CODE_C0_RANGE_END) {
    376             pos = parseC0(data, pos);
    377         } else if (mCommand >= Cea708Data.CODE_C1_RANGE_START
    378                 && mCommand <= Cea708Data.CODE_C1_RANGE_END) {
    379             pos = parseC1(data, pos);
    380         } else if (mCommand >= Cea708Data.CODE_G0_RANGE_START
    381                 && mCommand <= Cea708Data.CODE_G0_RANGE_END) {
    382             pos = parseG0(data, pos);
    383         } else if (mCommand >= Cea708Data.CODE_G1_RANGE_START
    384                 && mCommand <= Cea708Data.CODE_G1_RANGE_END) {
    385             pos = parseG1(data, pos);
    386         }
    387         return pos;
    388     }
    389 
    390     private int parseC0(byte[] data, int pos) {
    391         // For the details of C0 code group, see CEA-708B Section 7.4.1.
    392         // CL Group: C0 Subset of ASCII Control codes
    393         if (mCommand >= Cea708Data.CODE_C0_SKIP2_RANGE_START
    394                 && mCommand <= Cea708Data.CODE_C0_SKIP2_RANGE_END) {
    395             if (mCommand == Cea708Data.CODE_C0_P16) {
    396                 // TODO : P16 escapes next two bytes for the large character maps.(no standard rule)
    397                 // TODO : For korea broadcasting, express whole letters by using this.
    398                 try {
    399                     if (data[pos] == 0) {
    400                         mBuffer.append((char) data[pos + 1]);
    401                     } else {
    402                         String value = new String(
    403                                 Arrays.copyOfRange(data, pos, pos + 2),
    404                                 "EUC-KR");
    405                         mBuffer.append(value);
    406                     }
    407                 } catch (UnsupportedEncodingException e) {
    408                     Log.e(TAG, "P16 Code - Could not find supported encoding", e);
    409                 }
    410             }
    411             pos += 2;
    412         } else if (mCommand >= Cea708Data.CODE_C0_SKIP1_RANGE_START
    413                 && mCommand <= Cea708Data.CODE_C0_SKIP1_RANGE_END) {
    414             ++pos;
    415         } else {
    416             // NUL, BS, FF, CR interpreted as they are in ASCII control codes.
    417             // HCR moves the pen location to th beginning of the current line and deletes contents.
    418             // FF clears the screen and moves the pen location to (0,0).
    419             // ETX is the NULL command which is used to flush text to the current window when no
    420             // other command is pending.
    421             switch (mCommand) {
    422                 case Cea708Data.CODE_C0_NUL:
    423                     break;
    424                 case Cea708Data.CODE_C0_ETX:
    425                     emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
    426                     break;
    427                 case Cea708Data.CODE_C0_BS:
    428                     emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
    429                     break;
    430                 case Cea708Data.CODE_C0_FF:
    431                     emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
    432                     break;
    433                 case Cea708Data.CODE_C0_CR:
    434                     mBuffer.append('\n');
    435                     break;
    436                 case Cea708Data.CODE_C0_HCR:
    437                     emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
    438                     break;
    439                 default:
    440                     break;
    441             }
    442         }
    443         return pos;
    444     }
    445 
    446     private int parseC1(byte[] data, int pos) {
    447         // For the details of C1 code group, see CEA-708B Section 8.10.
    448         // CR Group: C1 Caption Control Codes
    449         switch (mCommand) {
    450             case Cea708Data.CODE_C1_CW0:
    451             case Cea708Data.CODE_C1_CW1:
    452             case Cea708Data.CODE_C1_CW2:
    453             case Cea708Data.CODE_C1_CW3:
    454             case Cea708Data.CODE_C1_CW4:
    455             case Cea708Data.CODE_C1_CW5:
    456             case Cea708Data.CODE_C1_CW6:
    457             case Cea708Data.CODE_C1_CW7: {
    458                 // SetCurrentWindow0-7
    459                 int windowId = mCommand - Cea708Data.CODE_C1_CW0;
    460                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CWX, windowId));
    461                 if (DEBUG) {
    462                     Log.d(TAG, String.format("CaptionCommand CWX windowId: %d", windowId));
    463                 }
    464                 break;
    465             }
    466 
    467             case Cea708Data.CODE_C1_CLW: {
    468                 // ClearWindows
    469                 int windowBitmap = data[pos] & 0xff;
    470                 ++pos;
    471                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CLW, windowBitmap));
    472                 if (DEBUG) {
    473                     Log.d(TAG, String.format("CaptionCommand CLW windowBitmap: %d", windowBitmap));
    474                 }
    475                 break;
    476             }
    477 
    478             case Cea708Data.CODE_C1_DSW: {
    479                 // DisplayWindows
    480                 int windowBitmap = data[pos] & 0xff;
    481                 ++pos;
    482                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DSW, windowBitmap));
    483                 if (DEBUG) {
    484                     Log.d(TAG, String.format("CaptionCommand DSW windowBitmap: %d", windowBitmap));
    485                 }
    486                 break;
    487             }
    488 
    489             case Cea708Data.CODE_C1_HDW: {
    490                 // HideWindows
    491                 int windowBitmap = data[pos] & 0xff;
    492                 ++pos;
    493                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_HDW, windowBitmap));
    494                 if (DEBUG) {
    495                     Log.d(TAG, String.format("CaptionCommand HDW windowBitmap: %d", windowBitmap));
    496                 }
    497                 break;
    498             }
    499 
    500             case Cea708Data.CODE_C1_TGW: {
    501                 // ToggleWindows
    502                 int windowBitmap = data[pos] & 0xff;
    503                 ++pos;
    504                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_TGW, windowBitmap));
    505                 if (DEBUG) {
    506                     Log.d(TAG, String.format("CaptionCommand TGW windowBitmap: %d", windowBitmap));
    507                 }
    508                 break;
    509             }
    510 
    511             case Cea708Data.CODE_C1_DLW: {
    512                 // DeleteWindows
    513                 int windowBitmap = data[pos] & 0xff;
    514                 ++pos;
    515                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLW, windowBitmap));
    516                 if (DEBUG) {
    517                     Log.d(TAG, String.format("CaptionCommand DLW windowBitmap: %d", windowBitmap));
    518                 }
    519                 break;
    520             }
    521 
    522             case Cea708Data.CODE_C1_DLY: {
    523                 // Delay
    524                 int tenthsOfSeconds = data[pos] & 0xff;
    525                 ++pos;
    526                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLY, tenthsOfSeconds));
    527                 if (DEBUG) {
    528                     Log.d(TAG, String.format("CaptionCommand DLY %d tenths of seconds",
    529                             tenthsOfSeconds));
    530                 }
    531                 break;
    532             }
    533             case Cea708Data.CODE_C1_DLC: {
    534                 // DelayCancel
    535                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLC, null));
    536                 if (DEBUG) {
    537                     Log.d(TAG, "CaptionCommand DLC");
    538                 }
    539                 break;
    540             }
    541 
    542             case Cea708Data.CODE_C1_RST: {
    543                 // Reset
    544                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_RST, null));
    545                 if (DEBUG) {
    546                     Log.d(TAG, "CaptionCommand RST");
    547                 }
    548                 break;
    549             }
    550 
    551             case Cea708Data.CODE_C1_SPA: {
    552                 // SetPenAttributes
    553                 int textTag = (data[pos] & 0xf0) >> 4;
    554                 int penSize = data[pos] & 0x03;
    555                 int penOffset = (data[pos] & 0x0c) >> 2;
    556                 boolean italic = (data[pos + 1] & 0x80) != 0;
    557                 boolean underline = (data[pos + 1] & 0x40) != 0;
    558                 int edgeType = (data[pos + 1] & 0x38) >> 3;
    559                 int fontTag = data[pos + 1] & 0x7;
    560                 pos += 2;
    561                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPA,
    562                         new CaptionPenAttr(penSize, penOffset, textTag, fontTag, edgeType,
    563                                 underline, italic)));
    564                 if (DEBUG) {
    565                     Log.d(TAG, String.format(
    566                             "CaptionCommand SPA penSize: %d, penOffset: %d, textTag: %d, "
    567                                     + "fontTag: %d, edgeType: %d, underline: %s, italic: %s",
    568                             penSize, penOffset, textTag, fontTag, edgeType, underline, italic));
    569                 }
    570                 break;
    571             }
    572 
    573             case Cea708Data.CODE_C1_SPC: {
    574                 // SetPenColor
    575                 int opacity = (data[pos] & 0xc0) >> 6;
    576                 int red = (data[pos] & 0x30) >> 4;
    577                 int green = (data[pos] & 0x0c) >> 2;
    578                 int blue = data[pos] & 0x03;
    579                 CaptionColor foregroundColor = new CaptionColor(opacity, red, green, blue);
    580                 ++pos;
    581                 opacity = (data[pos] & 0xc0) >> 6;
    582                 red = (data[pos] & 0x30) >> 4;
    583                 green = (data[pos] & 0x0c) >> 2;
    584                 blue = data[pos] & 0x03;
    585                 CaptionColor backgroundColor = new CaptionColor(opacity, red, green, blue);
    586                 ++pos;
    587                 red = (data[pos] & 0x30) >> 4;
    588                 green = (data[pos] & 0x0c) >> 2;
    589                 blue = data[pos] & 0x03;
    590                 CaptionColor edgeColor = new CaptionColor(
    591                         CaptionColor.OPACITY_SOLID, red, green, blue);
    592                 ++pos;
    593                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPC,
    594                         new CaptionPenColor(foregroundColor, backgroundColor, edgeColor)));
    595                 if (DEBUG) {
    596                     Log.d(TAG, String.format(
    597                             "CaptionCommand SPC foregroundColor %s backgroundColor %s edgeColor %s",
    598                             foregroundColor, backgroundColor, edgeColor));
    599                 }
    600                 break;
    601             }
    602 
    603             case Cea708Data.CODE_C1_SPL: {
    604                 // SetPenLocation
    605                 // column is normally 0-31 for 4:3 formats, and 0-41 for 16:9 formats
    606                 int row = data[pos] & 0x0f;
    607                 int column = data[pos + 1] & 0x3f;
    608                 pos += 2;
    609                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPL,
    610                         new CaptionPenLocation(row, column)));
    611                 if (DEBUG) {
    612                     Log.d(TAG, String.format("CaptionCommand SPL row: %d, column: %d",
    613                             row, column));
    614                 }
    615                 break;
    616             }
    617 
    618             case Cea708Data.CODE_C1_SWA: {
    619                 // SetWindowAttributes
    620                 int opacity = (data[pos] & 0xc0) >> 6;
    621                 int red = (data[pos] & 0x30) >> 4;
    622                 int green = (data[pos] & 0x0c) >> 2;
    623                 int blue = data[pos] & 0x03;
    624                 CaptionColor fillColor = new CaptionColor(opacity, red, green, blue);
    625                 int borderType = (data[pos + 1] & 0xc0) >> 6 | (data[pos + 2] & 0x80) >> 5;
    626                 red = (data[pos + 1] & 0x30) >> 4;
    627                 green = (data[pos + 1] & 0x0c) >> 2;
    628                 blue = data[pos + 1] & 0x03;
    629                 CaptionColor borderColor = new CaptionColor(
    630                         CaptionColor.OPACITY_SOLID, red, green, blue);
    631                 boolean wordWrap = (data[pos + 2] & 0x40) != 0;
    632                 int printDirection = (data[pos + 2] & 0x30) >> 4;
    633                 int scrollDirection = (data[pos + 2] & 0x0c) >> 2;
    634                 int justify = (data[pos + 2] & 0x03);
    635                 int effectSpeed = (data[pos + 3] & 0xf0) >> 4;
    636                 int effectDirection = (data[pos + 3] & 0x0c) >> 2;
    637                 int displayEffect = data[pos + 3] & 0x3;
    638                 pos += 4;
    639                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SWA,
    640                         new CaptionWindowAttr(fillColor, borderColor, borderType, wordWrap,
    641                                 printDirection, scrollDirection, justify,
    642                                 effectDirection, effectSpeed, displayEffect)));
    643                 if (DEBUG) {
    644                     Log.d(TAG, String.format(
    645                             "CaptionCommand SWA fillColor: %s, borderColor: %s, borderType: %d"
    646                                     + "wordWrap: %s, printDirection: %d, scrollDirection: %d, "
    647                                     + "justify: %s, effectDirection: %d, effectSpeed: %d, "
    648                                     + "displayEffect: %d",
    649                             fillColor, borderColor, borderType, wordWrap, printDirection,
    650                             scrollDirection, justify, effectDirection, effectSpeed, displayEffect));
    651                 }
    652                 break;
    653             }
    654 
    655             case Cea708Data.CODE_C1_DF0:
    656             case Cea708Data.CODE_C1_DF1:
    657             case Cea708Data.CODE_C1_DF2:
    658             case Cea708Data.CODE_C1_DF3:
    659             case Cea708Data.CODE_C1_DF4:
    660             case Cea708Data.CODE_C1_DF5:
    661             case Cea708Data.CODE_C1_DF6:
    662             case Cea708Data.CODE_C1_DF7: {
    663                 // DefineWindow0-7
    664                 int windowId = mCommand - Cea708Data.CODE_C1_DF0;
    665                 boolean visible = (data[pos] & 0x20) != 0;
    666                 boolean rowLock = (data[pos] & 0x10) != 0;
    667                 boolean columnLock = (data[pos] & 0x08) != 0;
    668                 int priority = data[pos] & 0x07;
    669                 boolean relativePositioning = (data[pos + 1] & 0x80) != 0;
    670                 int anchorVertical = data[pos + 1] & 0x7f;
    671                 int anchorHorizontal = data[pos + 2] & 0xff;
    672                 int anchorId = (data[pos + 3] & 0xf0) >> 4;
    673                 int rowCount = data[pos + 3] & 0x0f;
    674                 int columnCount = data[pos + 4] & 0x3f;
    675                 int windowStyle = (data[pos + 5] & 0x38) >> 3;
    676                 int penStyle = data[pos + 5] & 0x07;
    677                 pos += 6;
    678                 emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DFX,
    679                         new CaptionWindow(windowId, visible, rowLock, columnLock, priority,
    680                                 relativePositioning, anchorVertical, anchorHorizontal, anchorId,
    681                                 rowCount, columnCount, penStyle, windowStyle)));
    682                 if (DEBUG) {
    683                     Log.d(TAG, String.format(
    684                             "CaptionCommand DFx windowId: %d, priority: %d, columnLock: %s, "
    685                                     + "rowLock: %s, visible: %s, anchorVertical: %d, "
    686                                     + "relativePositioning: %s, anchorHorizontal: %d, "
    687                                     + "rowCount: %d, anchorId: %d, columnCount: %d, penStyle: %d, "
    688                                     + "windowStyle: %d",
    689                             windowId, priority, columnLock, rowLock, visible, anchorVertical,
    690                             relativePositioning, anchorHorizontal, rowCount, anchorId, columnCount,
    691                             penStyle, windowStyle));
    692                 }
    693                 break;
    694             }
    695 
    696             default:
    697                 break;
    698         }
    699         return pos;
    700     }
    701 
    702     private int parseG0(byte[] data, int pos) {
    703         // For the details of G0 code group, see CEA-708B Section 7.4.3.
    704         // GL Group: G0 Modified version of ANSI X3.4 Printable Character Set (ASCII)
    705         if (mCommand == Cea708Data.CODE_G0_MUSICNOTE) {
    706             // Music note.
    707             mBuffer.append(MUSIC_NOTE_CHAR);
    708         } else {
    709             // Put ASCII code into buffer.
    710             mBuffer.append((char) mCommand);
    711         }
    712         return pos;
    713     }
    714 
    715     private int parseG1(byte[] data, int pos) {
    716         // For the details of G0 code group, see CEA-708B Section 7.4.4.
    717         // GR Group: G1 ISO 8859-1 Latin 1 Characters
    718         // Put ASCII Extended character set into buffer.
    719         mBuffer.append((char) mCommand);
    720         return pos;
    721     }
    722 
    723     // Step 4. Extended code groups
    724     private int parseExt1(byte[] data, int pos) {
    725         // For the details of EXT1 code group, see CEA-708B Section 7.2.
    726         mCommand = data[pos] & 0xff;
    727         ++pos;
    728         if (mCommand >= Cea708Data.CODE_C2_RANGE_START
    729                 && mCommand <= Cea708Data.CODE_C2_RANGE_END) {
    730             pos = parseC2(data, pos);
    731         } else if (mCommand >= Cea708Data.CODE_C3_RANGE_START
    732                 && mCommand <= Cea708Data.CODE_C3_RANGE_END) {
    733             pos = parseC3(data, pos);
    734         } else if (mCommand >= Cea708Data.CODE_G2_RANGE_START
    735                 && mCommand <= Cea708Data.CODE_G2_RANGE_END) {
    736             pos = parseG2(data, pos);
    737         } else if (mCommand >= Cea708Data.CODE_G3_RANGE_START
    738                 && mCommand <= Cea708Data.CODE_G3_RANGE_END) {
    739             pos = parseG3(data ,pos);
    740         }
    741         return pos;
    742     }
    743 
    744     private int parseC2(byte[] data, int pos) {
    745         // For the details of C2 code group, see CEA-708B Section 7.4.7.
    746         // Extended Miscellaneous Control Codes
    747         // C2 Table : No commands as of CEA-708B. A decoder must skip.
    748         if (mCommand >= Cea708Data.CODE_C2_SKIP0_RANGE_START
    749                 && mCommand <= Cea708Data.CODE_C2_SKIP0_RANGE_END) {
    750             // Do nothing.
    751         } else if (mCommand >= Cea708Data.CODE_C2_SKIP1_RANGE_START
    752                 && mCommand <= Cea708Data.CODE_C2_SKIP1_RANGE_END) {
    753             ++pos;
    754         } else if (mCommand >= Cea708Data.CODE_C2_SKIP2_RANGE_START
    755                 && mCommand <= Cea708Data.CODE_C2_SKIP2_RANGE_END) {
    756             pos += 2;
    757         } else if (mCommand >= Cea708Data.CODE_C2_SKIP3_RANGE_START
    758                 && mCommand <= Cea708Data.CODE_C2_SKIP3_RANGE_END) {
    759             pos += 3;
    760         }
    761         return pos;
    762     }
    763 
    764     private int parseC3(byte[] data, int pos) {
    765         // For the details of C3 code group, see CEA-708B Section 7.4.8.
    766         // Extended Control Code Set 2
    767         // C3 Table : No commands as of CEA-708B. A decoder must skip.
    768         if (mCommand >= Cea708Data.CODE_C3_SKIP4_RANGE_START
    769                 && mCommand <= Cea708Data.CODE_C3_SKIP4_RANGE_END) {
    770             pos += 4;
    771         } else if (mCommand >= Cea708Data.CODE_C3_SKIP5_RANGE_START
    772                 && mCommand <= Cea708Data.CODE_C3_SKIP5_RANGE_END) {
    773             pos += 5;
    774         }
    775         return pos;
    776     }
    777 
    778     private int parseG2(byte[] data, int pos) {
    779         // For the details of C3 code group, see CEA-708B Section 7.4.5.
    780         // Extended Control Code Set 1(G2 Table)
    781         switch (mCommand) {
    782             case Cea708Data.CODE_G2_TSP:
    783                 // TODO : TSP is the Transparent space
    784                 break;
    785             case Cea708Data.CODE_G2_NBTSP:
    786                 // TODO : NBTSP is Non-Breaking Transparent Space.
    787                 break;
    788             case Cea708Data.CODE_G2_BLK:
    789                 // TODO : BLK indicates a solid block which fills the entire character block
    790                 // TODO : with a solid foreground color.
    791                 break;
    792             default:
    793                 break;
    794         }
    795         return pos;
    796     }
    797 
    798     private int parseG3(byte[] data, int pos) {
    799         // For the details of C3 code group, see CEA-708B Section 7.4.6.
    800         // Future characters and icons(G3 Table)
    801         if (mCommand == Cea708Data.CODE_G3_CC) {
    802             // TODO : [CC] icon with square corners
    803         }
    804 
    805         // Do nothing
    806         return pos;
    807     }
    808 }
    809