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