• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
clear()248     public void clear() {
249         mHandler.sendEmptyMessage(MSG_CAPTION_CLEAR);
250     }
251 
reset()252     public void reset() {
253         mCurrentWindowLayout = null;
254         mIsDelayed = false;
255         mPendingCaptionEvents.clear();
256         for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) {
257             if (mCaptionWindowLayouts[i] != null) {
258                 mCaptionWindowLayouts[i].removeFromCaptionView();
259             }
260             mCaptionWindowLayouts[i] = null;
261         }
262         mCaptionLayout.setVisibility(View.INVISIBLE);
263         mHandler.removeMessages(MSG_CAPTION_CLEAR);
264     }
265 
setWindowAttr(CaptionWindowAttr windowAttr)266     private void setWindowAttr(CaptionWindowAttr windowAttr) {
267         if (mCurrentWindowLayout != null) {
268             mCurrentWindowLayout.setWindowAttr(windowAttr);
269         }
270     }
271 
defineWindow(CaptionWindow window)272     private void defineWindow(CaptionWindow window) {
273         if (window == null) {
274             return;
275         }
276         int windowId = window.id;
277         if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) {
278             return;
279         }
280         CaptionWindowLayout windowLayout = mCaptionWindowLayouts[windowId];
281         if (windowLayout == null) {
282             windowLayout = new CaptionWindowLayout(mCaptionLayout.getContext());
283         }
284         windowLayout.initWindow(mCaptionLayout, window);
285         mCurrentWindowLayout = mCaptionWindowLayouts[windowId] = windowLayout;
286     }
287 
288     // The job related caption commands
delay(int tenthsOfSeconds)289     private void delay(int tenthsOfSeconds) {
290         if (tenthsOfSeconds < 0 || tenthsOfSeconds > 255) {
291             return;
292         }
293         mIsDelayed = true;
294         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DELAY_CANCEL),
295                 tenthsOfSeconds * DELAY_IN_MILLIS);
296     }
297 
delayCancel()298     private void delayCancel() {
299         mIsDelayed = false;
300         processPendingBuffer();
301     }
302 
processPendingBuffer()303     private void processPendingBuffer() {
304         for (CaptionEvent event : mPendingCaptionEvents) {
305             processCaptionEvent(event);
306         }
307         mPendingCaptionEvents.clear();
308     }
309 
310     // The implicit write caption commands
sendControlToCurrentWindow(char control)311     private void sendControlToCurrentWindow(char control) {
312         if (mCurrentWindowLayout != null) {
313             mCurrentWindowLayout.sendControl(control);
314         }
315     }
316 
sendBufferToCurrentWindow(String buffer)317     private void sendBufferToCurrentWindow(String buffer) {
318         if (mCurrentWindowLayout != null) {
319             mCurrentWindowLayout.sendBuffer(buffer);
320             mHandler.removeMessages(MSG_CAPTION_CLEAR);
321             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CAPTION_CLEAR),
322                     CAPTION_CLEAR_INTERVAL_MS);
323         }
324     }
325 
326     // The pen related caption commands
setPenAttr(CaptionPenAttr attr)327     private void setPenAttr(CaptionPenAttr attr) {
328         if (mCurrentWindowLayout != null) {
329             mCurrentWindowLayout.setPenAttr(attr);
330         }
331     }
332 
setPenColor(CaptionPenColor color)333     private void setPenColor(CaptionPenColor color) {
334         if (mCurrentWindowLayout != null) {
335             mCurrentWindowLayout.setPenColor(color);
336         }
337     }
338 
setPenLocation(CaptionPenLocation location)339     private void setPenLocation(CaptionPenLocation location) {
340         if (mCurrentWindowLayout != null) {
341             mCurrentWindowLayout.setPenLocation(location.row, location.column);
342         }
343     }
344 }
345