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.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