Home | History | Annotate | Download | only in tvinput
      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.tvinput;
     18 
     19 import android.util.Log;
     20 import android.util.SparseArray;
     21 import android.util.SparseBooleanArray;
     22 import com.android.tv.tuner.TunerHal;
     23 import com.android.tv.tuner.data.PsiData;
     24 import com.android.tv.tuner.data.PsipData;
     25 import com.android.tv.tuner.data.TunerChannel;
     26 import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
     27 import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
     28 import com.android.tv.tuner.ts.TsParser;
     29 import java.util.ArrayList;
     30 import java.util.HashSet;
     31 import java.util.List;
     32 import java.util.Set;
     33 
     34 /**
     35  * Detects channels and programs that are emerged or changed while parsing ATSC PSIP information.
     36  */
     37 public class EventDetector {
     38     private static final String TAG = "EventDetector";
     39     private static final boolean DEBUG = false;
     40     public static final int ALL_PROGRAM_NUMBERS = -1;
     41 
     42     private final TunerHal mTunerHal;
     43 
     44     private TsParser mTsParser;
     45     private final Set<Integer> mPidSet = new HashSet<>();
     46 
     47     // To prevent channel duplication
     48     private final Set<Integer> mVctProgramNumberSet = new HashSet<>();
     49     private final Set<Integer> mSdtProgramNumberSet = new HashSet<>();
     50     private final SparseArray<TunerChannel> mChannelMap = new SparseArray<>();
     51     private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray();
     52     private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray();
     53     private final List<EventListener> mEventListeners = new ArrayList<>();
     54     private int mFrequency;
     55     private String mModulation;
     56     private int mProgramNumber = ALL_PROGRAM_NUMBERS;
     57 
     58     private final TsParser.TsOutputListener mTsOutputListener =
     59             new TsParser.TsOutputListener() {
     60                 @Override
     61                 public void onPatDetected(List<PsiData.PatItem> items) {
     62                     for (PsiData.PatItem i : items) {
     63                         if (mProgramNumber == ALL_PROGRAM_NUMBERS
     64                                 || mProgramNumber == i.getProgramNo()) {
     65                             mTunerHal.addPidFilter(i.getPmtPid(), TunerHal.FILTER_TYPE_OTHER);
     66                         }
     67                     }
     68                 }
     69 
     70                 @Override
     71                 public void onEitPidDetected(int pid) {
     72                     startListening(pid);
     73                 }
     74 
     75                 @Override
     76                 public void onEitItemParsed(
     77                         PsipData.VctItem channel, List<PsipData.EitItem> items) {
     78                     TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber());
     79                     if (DEBUG) {
     80                         Log.d(
     81                                 TAG,
     82                                 "onEitItemParsed tunerChannel:"
     83                                         + tunerChannel
     84                                         + " "
     85                                         + channel.getProgramNumber());
     86                     }
     87                     int channelSourceId = channel.getSourceId();
     88 
     89                     // Source id 0 is useful for cases where a cable operator wishes to define a
     90                     // channel for
     91                     // which no EPG data is currently available.
     92                     // We don't handle such a case.
     93                     if (channelSourceId == 0) {
     94                         return;
     95                     }
     96 
     97                     // If at least a one caption track have been found in EIT items for the given
     98                     // channel,
     99                     // we starts to interpret the zero tracks as a clearance of the caption tracks.
    100                     boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId);
    101                     for (PsipData.EitItem item : items) {
    102                         if (captionTracksFound) {
    103                             break;
    104                         }
    105                         List<AtscCaptionTrack> captionTracks = item.getCaptionTracks();
    106                         if (captionTracks != null && !captionTracks.isEmpty()) {
    107                             captionTracksFound = true;
    108                         }
    109                     }
    110                     mEitCaptionTracksFound.put(channelSourceId, captionTracksFound);
    111                     if (captionTracksFound) {
    112                         for (PsipData.EitItem item : items) {
    113                             item.setHasCaptionTrack();
    114                         }
    115                     }
    116                     if (tunerChannel != null && !mEventListeners.isEmpty()) {
    117                         for (EventListener eventListener : mEventListeners) {
    118                             eventListener.onEventDetected(tunerChannel, items);
    119                         }
    120                     }
    121                 }
    122 
    123                 @Override
    124                 public void onEttPidDetected(int pid) {
    125                     startListening(pid);
    126                 }
    127 
    128                 @Override
    129                 public void onAllVctItemsParsed() {
    130                     if (!mEventListeners.isEmpty()) {
    131                         for (EventListener eventListener : mEventListeners) {
    132                             eventListener.onChannelScanDone();
    133                         }
    134                     }
    135                 }
    136 
    137                 @Override
    138                 public void onVctItemParsed(
    139                         PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
    140                     if (DEBUG) {
    141                         Log.d(TAG, "onVctItemParsed VCT " + channel);
    142                         Log.d(TAG, "                PMT " + pmtItems);
    143                     }
    144 
    145                     // Merges the audio and caption tracks located in PMT items into the tracks of
    146                     // the given
    147                     // tuner channel.
    148                     TunerChannel tunerChannel = new TunerChannel(channel, pmtItems);
    149                     List<AtscAudioTrack> audioTracks = new ArrayList<>();
    150                     List<AtscCaptionTrack> captionTracks = new ArrayList<>();
    151                     for (PsiData.PmtItem pmtItem : pmtItems) {
    152                         if (pmtItem.getAudioTracks() != null) {
    153                             audioTracks.addAll(pmtItem.getAudioTracks());
    154                         }
    155                         if (pmtItem.getCaptionTracks() != null) {
    156                             captionTracks.addAll(pmtItem.getCaptionTracks());
    157                         }
    158                     }
    159                     int channelProgramNumber = channel.getProgramNumber();
    160 
    161                     // If at least a one caption track have been found in VCT items for the given
    162                     // channel,
    163                     // we starts to interpret the zero tracks as a clearance of the caption tracks.
    164                     boolean captionTracksFound =
    165                             mVctCaptionTracksFound.get(channelProgramNumber)
    166                                     || !captionTracks.isEmpty();
    167                     mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound);
    168                     if (captionTracksFound) {
    169                         tunerChannel.setHasCaptionTrack();
    170                     }
    171                     tunerChannel.setAudioTracks(audioTracks);
    172                     tunerChannel.setCaptionTracks(captionTracks);
    173                     tunerChannel.setFrequency(mFrequency);
    174                     tunerChannel.setModulation(mModulation);
    175                     mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
    176                     boolean found = mVctProgramNumberSet.contains(channelProgramNumber);
    177                     if (!found) {
    178                         mVctProgramNumberSet.add(channelProgramNumber);
    179                     }
    180                     if (!mEventListeners.isEmpty()) {
    181                         for (EventListener eventListener : mEventListeners) {
    182                             eventListener.onChannelDetected(tunerChannel, !found);
    183                         }
    184                     }
    185                 }
    186 
    187                 @Override
    188                 public void onSdtItemParsed(
    189                         PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
    190                     if (DEBUG) {
    191                         Log.d(TAG, "onSdtItemParsed SDT " + channel);
    192                         Log.d(TAG, "                PMT " + pmtItems);
    193                     }
    194 
    195                     // Merges the audio and caption tracks located in PMT items into the tracks of
    196                     // the given
    197                     // tuner channel.
    198                     TunerChannel tunerChannel = new TunerChannel(channel, pmtItems);
    199                     List<AtscAudioTrack> audioTracks = new ArrayList<>();
    200                     List<AtscCaptionTrack> captionTracks = new ArrayList<>();
    201                     for (PsiData.PmtItem pmtItem : pmtItems) {
    202                         if (pmtItem.getAudioTracks() != null) {
    203                             audioTracks.addAll(pmtItem.getAudioTracks());
    204                         }
    205                         if (pmtItem.getCaptionTracks() != null) {
    206                             captionTracks.addAll(pmtItem.getCaptionTracks());
    207                         }
    208                     }
    209                     int channelProgramNumber = channel.getServiceId();
    210                     tunerChannel.setAudioTracks(audioTracks);
    211                     tunerChannel.setCaptionTracks(captionTracks);
    212                     tunerChannel.setFrequency(mFrequency);
    213                     tunerChannel.setModulation(mModulation);
    214                     mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
    215                     boolean found = mSdtProgramNumberSet.contains(channelProgramNumber);
    216                     if (!found) {
    217                         mSdtProgramNumberSet.add(channelProgramNumber);
    218                     }
    219                     if (!mEventListeners.isEmpty()) {
    220                         for (EventListener eventListener : mEventListeners) {
    221                             eventListener.onChannelDetected(tunerChannel, !found);
    222                         }
    223                     }
    224                 }
    225             };
    226 
    227     /** Listener for detecting ATSC TV channels and receiving EPG data. */
    228     public interface EventListener {
    229 
    230         /**
    231          * Fired when new information of an ATSC TV channel arrived.
    232          *
    233          * @param channel an ATSC TV channel
    234          * @param channelArrivedAtFirstTime tells whether this channel arrived at first time
    235          */
    236         void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime);
    237 
    238         /**
    239          * Fired when new program events of an ATSC TV channel arrived.
    240          *
    241          * @param channel an ATSC TV channel
    242          * @param items a list of EIT items that were received
    243          */
    244         void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items);
    245 
    246         /**
    247          * Fired when information of all detectable ATSC TV channels in current frequency arrived.
    248          */
    249         void onChannelScanDone();
    250     }
    251 
    252     /**
    253      * Creates a detector for ATSC TV channles and program information.
    254      *
    255      * @param usbTunerInteface {@link TunerHal}
    256      */
    257     public EventDetector(TunerHal usbTunerInteface) {
    258         mTunerHal = usbTunerInteface;
    259     }
    260 
    261     private void reset() {
    262         // TODO: Use TsParser.reset()
    263         int deliverySystemType = mTunerHal.getDeliverySystemType();
    264         mTsParser =
    265                 new TsParser(
    266                         mTsOutputListener,
    267                         TunerHal.isDvbDeliverySystem(mTunerHal.getDeliverySystemType()));
    268         mPidSet.clear();
    269         mVctProgramNumberSet.clear();
    270         mSdtProgramNumberSet.clear();
    271         mVctCaptionTracksFound.clear();
    272         mEitCaptionTracksFound.clear();
    273         mChannelMap.clear();
    274     }
    275 
    276     /**
    277      * Starts detecting channel and program information.
    278      *
    279      * @param frequency The frequency to listen to.
    280      * @param modulation The modulation type.
    281      * @param programNumber The program number if this is for handling tune request. For scanning
    282      *     purpose, supply {@link #ALL_PROGRAM_NUMBERS}.
    283      */
    284     public void startDetecting(int frequency, String modulation, int programNumber) {
    285         reset();
    286         mFrequency = frequency;
    287         mModulation = modulation;
    288         mProgramNumber = programNumber;
    289     }
    290 
    291     private void startListening(int pid) {
    292         if (mPidSet.contains(pid)) {
    293             return;
    294         }
    295         mPidSet.add(pid);
    296         mTunerHal.addPidFilter(pid, TunerHal.FILTER_TYPE_OTHER);
    297     }
    298 
    299     /**
    300      * Feeds ATSC TS stream to detect channel and program information.
    301      *
    302      * @param data buffer for ATSC TS stream
    303      * @param startOffset the offset where buffer starts
    304      * @param length The length of available data
    305      */
    306     public void feedTSStream(byte[] data, int startOffset, int length) {
    307         if (mPidSet.isEmpty()) {
    308             startListening(TsParser.ATSC_SI_BASE_PID);
    309         }
    310         if (mTsParser != null) {
    311             mTsParser.feedTSData(data, startOffset, length);
    312         }
    313     }
    314 
    315     /**
    316      * Retrieves the channel information regardless of being well-formed.
    317      *
    318      * @return {@link List} of {@link TunerChannel}
    319      */
    320     public List<TunerChannel> getMalFormedChannels() {
    321         return mTsParser.getMalFormedChannels();
    322     }
    323 
    324     /**
    325      * Registers an EventListener.
    326      *
    327      * @param eventListener the listener to be registered
    328      */
    329     public void registerListener(EventListener eventListener) {
    330         if (mTsParser != null) {
    331             // Resets the version numbers so that the new listener can receive the EIT items.
    332             // Otherwise, each EIT session is handled only once unless there is a new version.
    333             mTsParser.resetDataVersions();
    334         }
    335         mEventListeners.add(eventListener);
    336     }
    337 
    338     /**
    339      * Unregisters an EventListener.
    340      *
    341      * @param eventListener the listener to be unregistered
    342      */
    343     public void unregisterListener(EventListener eventListener) {
    344         boolean removed = mEventListeners.remove(eventListener);
    345         if (!removed && DEBUG) {
    346             Log.d(TAG, "Cannot unregister a non-registered listener!");
    347         }
    348     }
    349 }
    350