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 
     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 
     72     public CaptionTrackRenderer(CaptionLayout captionLayout) {
     73         mCaptionLayout = captionLayout;
     74         mHandler = new Handler(this);
     75     }
     76 
     77     @Override
     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 
     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 
    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 
    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
    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.
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    262     private void setWindowAttr(CaptionWindowAttr windowAttr) {
    263         if (mCurrentWindowLayout != null) {
    264             mCurrentWindowLayout.setWindowAttr(windowAttr);
    265         }
    266     }
    267 
    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
    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 
    294     private void delayCancel() {
    295         mIsDelayed = false;
    296         processPendingBuffer();
    297     }
    298 
    299     private void processPendingBuffer() {
    300         for (CaptionEvent event : mPendingCaptionEvents) {
    301             processCaptionEvent(event);
    302         }
    303         mPendingCaptionEvents.clear();
    304     }
    305 
    306     // The implicit write caption commands
    307     private void sendControlToCurrentWindow(char control) {
    308         if (mCurrentWindowLayout != null) {
    309             mCurrentWindowLayout.sendControl(control);
    310         }
    311     }
    312 
    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
    323     private void setPenAttr(CaptionPenAttr attr) {
    324         if (mCurrentWindowLayout != null) {
    325             mCurrentWindowLayout.setPenAttr(attr);
    326         }
    327     }
    328 
    329     private void setPenColor(CaptionPenColor color) {
    330         if (mCurrentWindowLayout != null) {
    331             mCurrentWindowLayout.setPenColor(color);
    332         }
    333     }
    334 
    335     private void setPenLocation(CaptionPenLocation location) {
    336         if (mCurrentWindowLayout != null) {
    337             mCurrentWindowLayout.setPenLocation(location.row, location.column);
    338         }
    339     }
    340 }
    341