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 
23 import com.android.tv.tuner.TunerHal;
24 import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
25 import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
26 import com.android.tv.tuner.data.TunerChannel;
27 import com.android.tv.tuner.ts.TsParser;
28 import com.android.tv.tuner.data.PsiData;
29 import com.android.tv.tuner.data.PsipData;
30 
31 import java.util.ArrayList;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Set;
35 
36 /**
37  * Detects channels and programs that are emerged or changed while parsing ATSC PSIP information.
38  */
39 public class EventDetector {
40     private static final String TAG = "EventDetector";
41     private static final boolean DEBUG = false;
42     public static final int ALL_PROGRAM_NUMBERS = -1;
43 
44     private final TunerHal mTunerHal;
45 
46     private TsParser mTsParser;
47     private final Set<Integer> mPidSet = new HashSet<>();
48 
49     // To prevent channel duplication
50     private final Set<Integer> mVctProgramNumberSet = new HashSet<>();
51     private final SparseArray<TunerChannel> mChannelMap = new SparseArray<>();
52     private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray();
53     private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray();
54     private final EventListener mEventListener;
55     private int mFrequency;
56     private String mModulation;
57     private int mProgramNumber = ALL_PROGRAM_NUMBERS;
58 
59     private final TsParser.TsOutputListener mTsOutputListener = 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 || mProgramNumber == i.getProgramNo()) {
64                     mTunerHal.addPidFilter(i.getPmtPid(), TunerHal.FILTER_TYPE_OTHER);
65                 }
66             }
67         }
68 
69         @Override
70         public void onEitPidDetected(int pid) {
71             startListening(pid);
72         }
73 
74         @Override
75         public void onEitItemParsed(PsipData.VctItem channel, List<PsipData.EitItem> items) {
76             TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber());
77             if (DEBUG) {
78                 Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " "
79                         + channel.getProgramNumber());
80             }
81             int channelSourceId = channel.getSourceId();
82 
83             // Source id 0 is useful for cases where a cable operator wishes to define a channel for
84             // which no EPG data is currently available.
85             // We don't handle such a case.
86             if (channelSourceId == 0) {
87                 return;
88             }
89 
90             // If at least a one caption track have been found in EIT items for the given channel,
91             // we starts to interpret the zero tracks as a clearance of the caption tracks.
92             boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId);
93             for (PsipData.EitItem item : items) {
94                 if (captionTracksFound) {
95                     break;
96                 }
97                 List<AtscCaptionTrack> captionTracks = item.getCaptionTracks();
98                 if (captionTracks != null && !captionTracks.isEmpty()) {
99                     captionTracksFound = true;
100                 }
101             }
102             mEitCaptionTracksFound.put(channelSourceId, captionTracksFound);
103             if (captionTracksFound) {
104                 for (PsipData.EitItem item : items) {
105                     item.setHasCaptionTrack();
106                 }
107             }
108             if (tunerChannel != null && mEventListener != null) {
109                 mEventListener.onEventDetected(tunerChannel, items);
110             }
111         }
112 
113         @Override
114         public void onEttPidDetected(int pid) {
115             startListening(pid);
116         }
117 
118         @Override
119         public void onAllVctItemsParsed() {
120             if (mEventListener != null) {
121                 mEventListener.onChannelScanDone();
122             }
123         }
124 
125         @Override
126         public void onVctItemParsed(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
127             if (DEBUG) {
128                 Log.d(TAG, "onVctItemParsed VCT " + channel);
129                 Log.d(TAG, "                PMT " + pmtItems);
130             }
131 
132             // Merges the audio and caption tracks located in PMT items into the tracks of the given
133             // tuner channel.
134             TunerChannel tunerChannel = new TunerChannel(channel, pmtItems);
135             List<AtscAudioTrack> audioTracks = new ArrayList<>();
136             List<AtscCaptionTrack> captionTracks = new ArrayList<>();
137             for (PsiData.PmtItem pmtItem : pmtItems) {
138                 if (pmtItem.getAudioTracks() != null) {
139                     audioTracks.addAll(pmtItem.getAudioTracks());
140                 }
141                 if (pmtItem.getCaptionTracks() != null) {
142                     captionTracks.addAll(pmtItem.getCaptionTracks());
143                 }
144             }
145             int channelProgramNumber = channel.getProgramNumber();
146 
147             // If at least a one caption track have been found in VCT items for the given channel,
148             // we starts to interpret the zero tracks as a clearance of the caption tracks.
149             boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber)
150                     || !captionTracks.isEmpty();
151             mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound);
152             if (captionTracksFound) {
153                 tunerChannel.setHasCaptionTrack();
154             }
155             tunerChannel.setAudioTracks(audioTracks);
156             tunerChannel.setCaptionTracks(captionTracks);
157             tunerChannel.setFrequency(mFrequency);
158             tunerChannel.setModulation(mModulation);
159             mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
160             boolean found = mVctProgramNumberSet.contains(channelProgramNumber);
161             if (!found) {
162                 mVctProgramNumberSet.add(channelProgramNumber);
163             }
164             if (mEventListener != null) {
165                 mEventListener.onChannelDetected(tunerChannel, !found);
166             }
167         }
168     };
169 
170     /**
171      * Listener for detecting ATSC TV channels and receiving EPG data.
172      */
173     public interface EventListener {
174 
175         /**
176          * Fired when new information of an ATSC TV channel arrived.
177          *
178          * @param channel an ATSC TV channel
179          * @param channelArrivedAtFirstTime tells whether this channel arrived at first time
180          */
onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime)181         void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime);
182 
183         /**
184          * Fired when new program events of an ATSC TV channel arrived.
185          *
186          * @param channel an ATSC TV channel
187          * @param items a list of EIT items that were received
188          */
onEventDetected(TunerChannel channel, List<PsipData.EitItem> items)189         void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items);
190 
191         /**
192          * Fired when information of all detectable ATSC TV channels in current frequency arrived.
193          */
onChannelScanDone()194         void onChannelScanDone();
195     }
196 
197     /**
198      * Creates a detector for ATSC TV channles and program information.
199      * @param usbTunerInteface {@link TunerHal}
200      * @param listener for ATSC TV channels and program information
201      */
EventDetector(TunerHal usbTunerInteface, EventListener listener)202     public EventDetector(TunerHal usbTunerInteface, EventListener listener) {
203         mTunerHal = usbTunerInteface;
204         mEventListener = listener;
205     }
206 
reset()207     private void reset() {
208         mTsParser = new TsParser(mTsOutputListener); // TODO: Use TsParser.reset()
209         mPidSet.clear();
210         mVctProgramNumberSet.clear();
211         mVctCaptionTracksFound.clear();
212         mEitCaptionTracksFound.clear();
213         mChannelMap.clear();
214     }
215 
216     /**
217      * Starts detecting channel and program information.
218      *
219      * @param frequency The frequency to listen to.
220      * @param modulation The modulation type.
221      * @param programNumber The program number if this is for handling tune request. For scanning
222      *            purpose, supply {@link #ALL_PROGRAM_NUMBERS}.
223      */
startDetecting(int frequency, String modulation, int programNumber)224     public void startDetecting(int frequency, String modulation, int programNumber) {
225         reset();
226         mFrequency = frequency;
227         mModulation = modulation;
228         mProgramNumber = programNumber;
229     }
230 
startListening(int pid)231     private void startListening(int pid) {
232         if (mPidSet.contains(pid)) {
233             return;
234         }
235         mPidSet.add(pid);
236         mTunerHal.addPidFilter(pid, TunerHal.FILTER_TYPE_OTHER);
237     }
238 
239     /**
240      * Feeds ATSC TS stream to detect channel and program information.
241      * @param data buffer for ATSC TS stream
242      * @param startOffset the offset where buffer starts
243      * @param length The length of available data
244      */
feedTSStream(byte[] data, int startOffset, int length)245     public void feedTSStream(byte[] data, int startOffset, int length) {
246         if (mPidSet.isEmpty()) {
247             startListening(TsParser.ATSC_SI_BASE_PID);
248         }
249         if (mTsParser != null) {
250             mTsParser.feedTSData(data, startOffset, length);
251         }
252     }
253 
254     /**
255      * Retrieves the channel information regardless of being well-formed.
256      * @return {@link List} of {@link TunerChannel}
257      */
getMalFormedChannels()258     public List<TunerChannel> getMalFormedChannels() {
259         return mTsParser.getMalFormedChannels();
260     }
261 }
262