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.usbtuner.tvinput;
     18 
     19 import android.util.Log;
     20 import android.util.SparseArray;
     21 import android.util.SparseBooleanArray;
     22 
     23 import com.android.usbtuner.FileDataSource;
     24 import com.android.usbtuner.data.PsiData.PatItem;
     25 import com.android.usbtuner.data.PsiData.PmtItem;
     26 import com.android.usbtuner.data.PsipData.EitItem;
     27 import com.android.usbtuner.data.PsipData.VctItem;
     28 import com.android.usbtuner.data.Track.AtscAudioTrack;
     29 import com.android.usbtuner.data.Track.AtscCaptionTrack;
     30 import com.android.usbtuner.data.TunerChannel;
     31 import com.android.usbtuner.ts.TsParser;
     32 import com.android.usbtuner.ts.TsParser.TsOutputListener;
     33 import com.android.usbtuner.tvinput.EventDetector.EventListener;
     34 
     35 import java.util.ArrayList;
     36 import java.util.HashSet;
     37 import java.util.List;
     38 import java.util.Set;
     39 
     40 /**
     41  * PSIP event detector for a file source.
     42  *
     43  * <p>Uses {@link TsParser} to analyze input MPEG-2 transport stream, detects and reports
     44  * various PSIP-related events via {@link TsOutputListener}.
     45  */
     46 public class FileSourceEventDetector {
     47     private static final String TAG = "FileSourceEventDetector";
     48     private static final boolean DEBUG = true;
     49 
     50     private TsParser mTsParser;
     51     private final Set<Integer> mVctProgramNumberSet = new HashSet<>();
     52     private final SparseArray<TunerChannel> mChannelMap = new SparseArray<>();
     53     private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray();
     54     private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray();
     55     private final EventListener mEventListener;
     56     private FileDataSource.StreamProvider mSource;
     57 
     58     public FileSourceEventDetector(EventDetector.EventListener listener) {
     59         mEventListener = listener;
     60     }
     61 
     62     public void start(FileDataSource.StreamProvider source) {
     63         mSource = source;
     64         reset();
     65     }
     66 
     67     private void reset() {
     68         mTsParser = new TsParser(mTsOutputListener); // TODO: Use TsParser.reset()
     69         mSource.clearPidFilter();
     70         mVctProgramNumberSet.clear();
     71         mVctCaptionTracksFound.clear();
     72         mEitCaptionTracksFound.clear();
     73         mChannelMap.clear();
     74     }
     75 
     76     public void feedTSStream(byte[] data, int startOffset, int length) {
     77         if (mSource.isFilterEmpty()) {
     78             startListening(TsParser.ATSC_SI_BASE_PID);
     79             startListening(TsParser.PAT_PID);
     80         }
     81         if (mTsParser != null) {
     82             mTsParser.feedTSData(data, startOffset, length);
     83         }
     84     }
     85 
     86     private void startListening(int pid) {
     87         if (mSource.isInFilter(pid)) {
     88             return;
     89         }
     90         mSource.addPidFilter(pid);
     91     }
     92 
     93     private TsOutputListener mTsOutputListener = new TsOutputListener() {
     94         @Override
     95         public void onPatDetected(List<PatItem> items) {
     96             for (PatItem i : items) {
     97                 mSource.addPidFilter(i.getPmtPid());
     98             }
     99         }
    100 
    101         @Override
    102         public void onEitPidDetected(int pid) {
    103             startListening(pid);
    104         }
    105 
    106         @Override
    107         public void onEitItemParsed(VctItem channel, List<EitItem> items) {
    108             TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber());
    109             if (DEBUG) {
    110                 Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " "
    111                         + channel.getProgramNumber());
    112             }
    113             int channelSourceId = channel.getSourceId();
    114 
    115             // Source id 0 is useful for cases where a cable operator wishes to define a channel for
    116             // which no EPG data is currently available.
    117             // We don't handle such a case.
    118             if (channelSourceId == 0) {
    119                 return;
    120             }
    121 
    122             // If at least a one caption track have been found in EIT items for the given channel,
    123             // we starts to interpret the zero tracks as a clearance of the caption tracks.
    124             boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId);
    125             for (EitItem item : items) {
    126                 if (captionTracksFound) {
    127                     break;
    128                 }
    129                 List<AtscCaptionTrack> captionTracks = item.getCaptionTracks();
    130                 if (captionTracks != null && !captionTracks.isEmpty()) {
    131                     captionTracksFound = true;
    132                 }
    133             }
    134             mEitCaptionTracksFound.put(channelSourceId, captionTracksFound);
    135             if (captionTracksFound) {
    136                 for (EitItem item : items) {
    137                     item.setHasCaptionTrack();
    138                 }
    139             }
    140             if (tunerChannel != null && mEventListener != null) {
    141                 mEventListener.onEventDetected(tunerChannel, items);
    142             }
    143         }
    144 
    145         @Override
    146         public void onEttPidDetected(int pid) {
    147             startListening(pid);
    148         }
    149 
    150         @Override
    151         public void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems) {
    152             if (DEBUG) {
    153                 Log.d(TAG, "onVctItemParsed VCT " + channel);
    154                 Log.d(TAG, "                PMT " + pmtItems);
    155             }
    156 
    157             // Merges the audio and caption tracks located in PMT items into the tracks of the given
    158             // tuner channel.
    159             TunerChannel tunerChannel = TunerChannel.forFile(channel, pmtItems);
    160             List<AtscAudioTrack> audioTracks = new ArrayList<>();
    161             List<AtscCaptionTrack> captionTracks = new ArrayList<>();
    162             for (PmtItem pmtItem : pmtItems) {
    163                 if (pmtItem.getAudioTracks() != null) {
    164                     audioTracks.addAll(pmtItem.getAudioTracks());
    165                 }
    166                 if (pmtItem.getCaptionTracks() != null) {
    167                     captionTracks.addAll(pmtItem.getCaptionTracks());
    168                 }
    169             }
    170             int channelProgramNumber = channel.getProgramNumber();
    171 
    172             // If at least a one caption track have been found in VCT items for the given channel,
    173             // we starts to interpret the zero tracks as a clearance of the caption tracks.
    174             boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber)
    175                     || !captionTracks.isEmpty();
    176             mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound);
    177             if (captionTracksFound) {
    178                 tunerChannel.setHasCaptionTrack();
    179             }
    180             tunerChannel.setFilepath(mSource.getFilepath());
    181             tunerChannel.setAudioTracks(audioTracks);
    182             tunerChannel.setCaptionTracks(captionTracks);
    183 
    184             mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
    185             boolean found = mVctProgramNumberSet.contains(channelProgramNumber);
    186             if (!found) {
    187                 mVctProgramNumberSet.add(channelProgramNumber);
    188             }
    189             if (mEventListener != null) {
    190                 mEventListener.onChannelDetected(tunerChannel, !found);
    191             }
    192         }
    193     };
    194 }
    195