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.cc; 18 19 import android.os.Handler; 20 import android.os.Message; 21 import android.util.Log; 22 import android.view.View; 23 import com.android.tv.tuner.data.Cea708Data.CaptionEvent; 24 import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr; 25 import com.android.tv.tuner.data.Cea708Data.CaptionPenColor; 26 import com.android.tv.tuner.data.Cea708Data.CaptionPenLocation; 27 import com.android.tv.tuner.data.Cea708Data.CaptionWindow; 28 import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr; 29 import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; 30 import java.util.ArrayList; 31 32 /** Decodes and renders CEA-708. */ 33 public class CaptionTrackRenderer implements Handler.Callback { 34 // TODO: Remaining works 35 // CaptionTrackRenderer does not support the full spec of CEA-708. The remaining works are 36 // described in the follows. 37 // C0 Table: Backspace, FF, and HCR are not supported. The rule for P16 is not standardized but 38 // it is handled as EUC-KR charset for korea broadcasting. 39 // C1 Table: All styles of windows and pens except underline, italic, pen size, and pen offset 40 // specified in CEA-708 are ignored and this follows system wide cc preferences for 41 // look and feel. SetPenLocation is not implemented. 42 // G2 Table: TSP, NBTSP and BLK are not supported. 43 // Text/commands: Word wrapping, fonts, row and column locking are not supported. 44 45 private static final String TAG = "CaptionTrackRenderer"; 46 private static final boolean DEBUG = false; 47 48 private static final long DELAY_IN_MILLIS = 100 /* milliseconds */; 49 50 // According to CEA-708B, there can exist up to 8 caption windows. 51 private static final int CAPTION_WINDOWS_MAX = 8; 52 private static final int CAPTION_ALL_WINDOWS_BITMAP = 255; 53 54 private static final int MSG_DELAY_CANCEL = 1; 55 private static final int MSG_CAPTION_CLEAR = 2; 56 57 private static final long CAPTION_CLEAR_INTERVAL_MS = 60000; 58 59 private final CaptionLayout mCaptionLayout; 60 private boolean mIsDelayed = false; 61 private CaptionWindowLayout mCurrentWindowLayout; 62 private final CaptionWindowLayout[] mCaptionWindowLayouts = 63 new CaptionWindowLayout[CAPTION_WINDOWS_MAX]; 64 private final ArrayList<CaptionEvent> mPendingCaptionEvents = new ArrayList<>(); 65 private final Handler mHandler; 66 67 public CaptionTrackRenderer(CaptionLayout captionLayout) { 68 mCaptionLayout = captionLayout; 69 mHandler = new Handler(this); 70 } 71 72 @Override 73 public boolean handleMessage(Message msg) { 74 switch (msg.what) { 75 case MSG_DELAY_CANCEL: 76 delayCancel(); 77 return true; 78 case MSG_CAPTION_CLEAR: 79 clearWindows(CAPTION_ALL_WINDOWS_BITMAP); 80 return true; 81 } 82 return false; 83 } 84 85 public void start(AtscCaptionTrack captionTrack) { 86 if (captionTrack == null) { 87 stop(); 88 return; 89 } 90 if (DEBUG) { 91 Log.d(TAG, "Start captionTrack " + captionTrack.language); 92 } 93 reset(); 94 mCaptionLayout.setCaptionTrack(captionTrack); 95 mCaptionLayout.setVisibility(View.VISIBLE); 96 } 97 98 public void stop() { 99 if (DEBUG) { 100 Log.d(TAG, "Stop captionTrack"); 101 } 102 mCaptionLayout.setVisibility(View.INVISIBLE); 103 mHandler.removeMessages(MSG_CAPTION_CLEAR); 104 } 105 106 public void processCaptionEvent(CaptionEvent event) { 107 if (mIsDelayed) { 108 mPendingCaptionEvents.add(event); 109 return; 110 } 111 switch (event.type) { 112 case Cea708Parser.CAPTION_EMIT_TYPE_BUFFER: 113 sendBufferToCurrentWindow((String) event.obj); 114 break; 115 case Cea708Parser.CAPTION_EMIT_TYPE_CONTROL: 116 sendControlToCurrentWindow((char) event.obj); 117 break; 118 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_CWX: 119 setCurrentWindowLayout((int) event.obj); 120 break; 121 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_CLW: 122 clearWindows((int) event.obj); 123 break; 124 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DSW: 125 displayWindows((int) event.obj); 126 break; 127 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_HDW: 128 hideWindows((int) event.obj); 129 break; 130 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_TGW: 131 toggleWindows((int) event.obj); 132 break; 133 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLW: 134 deleteWindows((int) event.obj); 135 break; 136 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLY: 137 delay((int) event.obj); 138 break; 139 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLC: 140 delayCancel(); 141 break; 142 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_RST: 143 reset(); 144 break; 145 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPA: 146 setPenAttr((CaptionPenAttr) event.obj); 147 break; 148 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPC: 149 setPenColor((CaptionPenColor) event.obj); 150 break; 151 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPL: 152 setPenLocation((CaptionPenLocation) event.obj); 153 break; 154 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SWA: 155 setWindowAttr((CaptionWindowAttr) event.obj); 156 break; 157 case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DFX: 158 defineWindow((CaptionWindow) event.obj); 159 break; 160 } 161 } 162 163 // The window related caption commands 164 private void setCurrentWindowLayout(int windowId) { 165 if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) { 166 return; 167 } 168 CaptionWindowLayout windowLayout = mCaptionWindowLayouts[windowId]; 169 if (windowLayout == null) { 170 return; 171 } 172 if (DEBUG) { 173 Log.d(TAG, "setCurrentWindowLayout to " + windowId); 174 } 175 mCurrentWindowLayout = windowLayout; 176 } 177 178 // Each bit of windowBitmap indicates a window. 179 // If a bit is set, the window id is the same as the number of the trailing zeros of the bit. 180 private ArrayList<CaptionWindowLayout> getWindowsFromBitmap(int windowBitmap) { 181 ArrayList<CaptionWindowLayout> windows = new ArrayList<>(); 182 for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) { 183 if ((windowBitmap & (1 << i)) != 0) { 184 CaptionWindowLayout windowLayout = mCaptionWindowLayouts[i]; 185 if (windowLayout != null) { 186 windows.add(windowLayout); 187 } 188 } 189 } 190 return windows; 191 } 192 193 private void clearWindows(int windowBitmap) { 194 if (windowBitmap == 0) { 195 return; 196 } 197 for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) { 198 windowLayout.clear(); 199 } 200 } 201 202 private void displayWindows(int windowBitmap) { 203 if (windowBitmap == 0) { 204 return; 205 } 206 for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) { 207 windowLayout.show(); 208 } 209 } 210 211 private void hideWindows(int windowBitmap) { 212 if (windowBitmap == 0) { 213 return; 214 } 215 for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) { 216 windowLayout.hide(); 217 } 218 } 219 220 private void toggleWindows(int windowBitmap) { 221 if (windowBitmap == 0) { 222 return; 223 } 224 for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) { 225 if (windowLayout.isShown()) { 226 windowLayout.hide(); 227 } else { 228 windowLayout.show(); 229 } 230 } 231 } 232 233 private void deleteWindows(int windowBitmap) { 234 if (windowBitmap == 0) { 235 return; 236 } 237 for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) { 238 windowLayout.removeFromCaptionView(); 239 mCaptionWindowLayouts[windowLayout.getCaptionWindowId()] = null; 240 } 241 } 242 243 public void clear() { 244 mHandler.sendEmptyMessage(MSG_CAPTION_CLEAR); 245 } 246 247 public void reset() { 248 mCurrentWindowLayout = null; 249 mIsDelayed = false; 250 mPendingCaptionEvents.clear(); 251 for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) { 252 if (mCaptionWindowLayouts[i] != null) { 253 mCaptionWindowLayouts[i].removeFromCaptionView(); 254 } 255 mCaptionWindowLayouts[i] = null; 256 } 257 mCaptionLayout.setVisibility(View.INVISIBLE); 258 mHandler.removeMessages(MSG_CAPTION_CLEAR); 259 } 260 261 private void setWindowAttr(CaptionWindowAttr windowAttr) { 262 if (mCurrentWindowLayout != null) { 263 mCurrentWindowLayout.setWindowAttr(windowAttr); 264 } 265 } 266 267 private void defineWindow(CaptionWindow window) { 268 if (window == null) { 269 return; 270 } 271 int windowId = window.id; 272 if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) { 273 return; 274 } 275 CaptionWindowLayout windowLayout = mCaptionWindowLayouts[windowId]; 276 if (windowLayout == null) { 277 windowLayout = new CaptionWindowLayout(mCaptionLayout.getContext()); 278 } 279 windowLayout.initWindow(mCaptionLayout, window); 280 mCurrentWindowLayout = mCaptionWindowLayouts[windowId] = windowLayout; 281 } 282 283 // The job related caption commands 284 private void delay(int tenthsOfSeconds) { 285 if (tenthsOfSeconds < 0 || tenthsOfSeconds > 255) { 286 return; 287 } 288 mIsDelayed = true; 289 mHandler.sendMessageDelayed( 290 mHandler.obtainMessage(MSG_DELAY_CANCEL), tenthsOfSeconds * DELAY_IN_MILLIS); 291 } 292 293 private void delayCancel() { 294 mIsDelayed = false; 295 processPendingBuffer(); 296 } 297 298 private void processPendingBuffer() { 299 for (CaptionEvent event : mPendingCaptionEvents) { 300 processCaptionEvent(event); 301 } 302 mPendingCaptionEvents.clear(); 303 } 304 305 // The implicit write caption commands 306 private void sendControlToCurrentWindow(char control) { 307 if (mCurrentWindowLayout != null) { 308 mCurrentWindowLayout.sendControl(control); 309 } 310 } 311 312 private void sendBufferToCurrentWindow(String buffer) { 313 if (mCurrentWindowLayout != null) { 314 mCurrentWindowLayout.sendBuffer(buffer); 315 mHandler.removeMessages(MSG_CAPTION_CLEAR); 316 mHandler.sendMessageDelayed( 317 mHandler.obtainMessage(MSG_CAPTION_CLEAR), CAPTION_CLEAR_INTERVAL_MS); 318 } 319 } 320 321 // The pen related caption commands 322 private void setPenAttr(CaptionPenAttr attr) { 323 if (mCurrentWindowLayout != null) { 324 mCurrentWindowLayout.setPenAttr(attr); 325 } 326 } 327 328 private void setPenColor(CaptionPenColor color) { 329 if (mCurrentWindowLayout != null) { 330 mCurrentWindowLayout.setPenColor(color); 331 } 332 } 333 334 private void setPenLocation(CaptionPenLocation location) { 335 if (mCurrentWindowLayout != null) { 336 mCurrentWindowLayout.setPenLocation(location.row, location.column); 337 } 338 } 339 } 340