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