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.ts;
18 
19 import android.util.Log;
20 import android.util.SparseArray;
21 import android.util.SparseBooleanArray;
22 
23 import com.android.tv.tuner.data.PsiData.PatItem;
24 import com.android.tv.tuner.data.PsiData.PmtItem;
25 import com.android.tv.tuner.data.PsipData.EitItem;
26 import com.android.tv.tuner.data.PsipData.EttItem;
27 import com.android.tv.tuner.data.PsipData.MgtItem;
28 import com.android.tv.tuner.data.PsipData.VctItem;
29 import com.android.tv.tuner.data.TunerChannel;
30 import com.android.tv.tuner.ts.SectionParser.OutputListener;
31 import com.android.tv.tuner.util.ByteArrayBuffer;
32 
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.TreeSet;
39 
40 /**
41  * Parses MPEG-2 TS packets.
42  */
43 public class TsParser {
44     private static final String TAG = "TsParser";
45     private static final boolean DEBUG = false;
46 
47     public static final int ATSC_SI_BASE_PID = 0x1ffb;
48     public static final int PAT_PID = 0x0000;
49     private static final int TS_PACKET_START_CODE = 0x47;
50     private static final int TS_PACKET_TEI_MASK = 0x80;
51     private static final int TS_PACKET_SIZE = 188;
52 
53     /*
54      * Using a SparseArray removes the need to auto box the int key for mStreamMap
55      * in feedTdPacket which is called 100 times a second. This greatly reduces the
56      * number of objects created and the frequency of garbage collection.
57      * Other maps might be suitable for a SparseArray, but the performance
58      * trade offs must be considered carefully.
59      * mStreamMap is the only one called at such a high rate.
60      */
61     private final SparseArray<Stream> mStreamMap = new SparseArray<>();
62     private final Map<Integer, VctItem> mSourceIdToVctItemMap = new HashMap<>();
63     private final Map<Integer, String> mSourceIdToVctItemDescriptionMap = new HashMap<>();
64     private final Map<Integer, VctItem> mProgramNumberToVctItemMap = new HashMap<>();
65     private final Map<Integer, List<PmtItem>> mProgramNumberToPMTMap = new HashMap<>();
66     private final Map<Integer, List<EitItem>> mSourceIdToEitMap = new HashMap<>();
67     private final Map<EventSourceEntry, List<EitItem>> mEitMap = new HashMap<>();
68     private final Map<EventSourceEntry, List<EttItem>> mETTMap = new HashMap<>();
69     private final TreeSet<Integer> mEITPids = new TreeSet<>();
70     private final TreeSet<Integer> mETTPids = new TreeSet<>();
71     private final SparseBooleanArray mProgramNumberHandledStatus = new SparseBooleanArray();
72     private final SparseBooleanArray mVctItemHandledStatus = new SparseBooleanArray();
73     private final TsOutputListener mListener;
74 
75     private int mVctItemCount;
76     private int mHandledVctItemCount;
77     private int mVctSectionParsedCount;
78     private boolean[] mVctSectionParsed;
79 
80     public interface TsOutputListener {
onPatDetected(List<PatItem> items)81         void onPatDetected(List<PatItem> items);
onEitPidDetected(int pid)82         void onEitPidDetected(int pid);
onVctItemParsed(VctItem channel, List<PmtItem> pmtItems)83         void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems);
onEitItemParsed(VctItem channel, List<EitItem> items)84         void onEitItemParsed(VctItem channel, List<EitItem> items);
onEttPidDetected(int pid)85         void onEttPidDetected(int pid);
onAllVctItemsParsed()86         void onAllVctItemsParsed();
87     }
88 
89     private abstract class Stream {
90         private static final int INVALID_CONTINUITY_COUNTER = -1;
91         private static final int NUM_CONTINUITY_COUNTER = 16;
92 
93         protected int mContinuityCounter = INVALID_CONTINUITY_COUNTER;
94         protected final ByteArrayBuffer mPacket = new ByteArrayBuffer(TS_PACKET_SIZE);
95 
feedData(byte[] data, int continuityCounter, boolean startIndicator)96         public void feedData(byte[] data, int continuityCounter, boolean startIndicator) {
97             if ((mContinuityCounter + 1) % NUM_CONTINUITY_COUNTER != continuityCounter) {
98                 mPacket.setLength(0);
99             }
100             mContinuityCounter = continuityCounter;
101             handleData(data, startIndicator);
102         }
103 
handleData(byte[] data, boolean startIndicator)104         protected abstract void handleData(byte[] data, boolean startIndicator);
105     }
106 
107     private class SectionStream extends Stream {
108         private final SectionParser mSectionParser;
109         private final int mPid;
110 
SectionStream(int pid)111         public SectionStream(int pid) {
112             mPid = pid;
113             mSectionParser = new SectionParser(mSectionListener);
114         }
115 
116         @Override
handleData(byte[] data, boolean startIndicator)117         protected void handleData(byte[] data, boolean startIndicator) {
118             int startPos = 0;
119             if (mPacket.length() == 0) {
120                 if (startIndicator) {
121                     startPos = (data[0] & 0xff) + 1;
122                 } else {
123                     // Don't know where the section starts yet. Wait until start indicator is on.
124                     return;
125                 }
126             } else {
127                 if (startIndicator) {
128                     startPos = 1;
129                 }
130             }
131 
132             // When a broken packet is encountered, parsing will stop and return right away.
133             if (startPos >= data.length) {
134                 mPacket.setLength(0);
135                 return;
136             }
137             mPacket.append(data, startPos, data.length - startPos);
138             mSectionParser.parseSections(mPacket);
139         }
140 
141         private final OutputListener mSectionListener = new OutputListener() {
142             @Override
143             public void onPatParsed(List<PatItem> items) {
144                 for (PatItem i : items) {
145                     startListening(i.getPmtPid());
146                 }
147                 if (mListener != null) {
148                     mListener.onPatDetected(items);
149                 }
150             }
151 
152             @Override
153             public void onPmtParsed(int programNumber, List<PmtItem> items) {
154                 mProgramNumberToPMTMap.put(programNumber, items);
155                 if (DEBUG) {
156                     Log.d(TAG, "onPMTParsed, programNo " + programNumber + " handledStatus is "
157                             + mProgramNumberHandledStatus.get(programNumber, false));
158                 }
159                 int statusIndex = mProgramNumberHandledStatus.indexOfKey(programNumber);
160                 if (statusIndex < 0) {
161                     mProgramNumberHandledStatus.put(programNumber, false);
162                 }
163                 if (!mProgramNumberHandledStatus.get(programNumber)) {
164                     VctItem vctItem = mProgramNumberToVctItemMap.get(programNumber);
165                     if (vctItem != null) {
166                         // When PMT is parsed later than VCT.
167                         mProgramNumberHandledStatus.put(programNumber, true);
168                         handleVctItem(vctItem, items);
169                         mHandledVctItemCount++;
170                         if (mHandledVctItemCount >= mVctItemCount
171                                 && mVctSectionParsedCount >= mVctSectionParsed.length
172                                 && mListener != null) {
173                             mListener.onAllVctItemsParsed();
174                         }
175                     }
176                 }
177             }
178 
179             @Override
180             public void onMgtParsed(List<MgtItem> items) {
181                 for (MgtItem i : items) {
182                     if (mStreamMap.get(i.getTableTypePid()) != null) {
183                         continue;
184                     }
185                     if (i.getTableType() >= MgtItem.TABLE_TYPE_EIT_RANGE_START
186                             && i.getTableType() <= MgtItem.TABLE_TYPE_EIT_RANGE_END) {
187                         startListening(i.getTableTypePid());
188                         mEITPids.add(i.getTableTypePid());
189                         if (mListener != null) {
190                             mListener.onEitPidDetected(i.getTableTypePid());
191                         }
192                     } else if (i.getTableType() == MgtItem.TABLE_TYPE_CHANNEL_ETT ||
193                             (i.getTableType() >= MgtItem.TABLE_TYPE_ETT_RANGE_START
194                                     && i.getTableType() <= MgtItem.TABLE_TYPE_ETT_RANGE_END)) {
195                         startListening(i.getTableTypePid());
196                         mETTPids.add(i.getTableTypePid());
197                         if (mListener != null) {
198                             mListener.onEttPidDetected(i.getTableTypePid());
199                         }
200                     }
201                 }
202             }
203 
204             @Override
205             public void onVctParsed(List<VctItem> items, int sectionNumber, int lastSectionNumber) {
206                 if (mVctSectionParsed == null) {
207                     mVctSectionParsed = new boolean[lastSectionNumber + 1];
208                 } else if (mVctSectionParsed[sectionNumber]) {
209                     // The current section was handled before.
210                     if (DEBUG) {
211                         Log.d(TAG, "Duplicate VCT section found.");
212                     }
213                     return;
214                 }
215                 mVctSectionParsed[sectionNumber] = true;
216                 mVctSectionParsedCount++;
217                 mVctItemCount += items.size();
218                 for (VctItem i : items) {
219                     if (DEBUG) Log.d(TAG, "onVCTParsed " + i);
220                     if (i.getSourceId() != 0) {
221                         mSourceIdToVctItemMap.put(i.getSourceId(), i);
222                         i.setDescription(mSourceIdToVctItemDescriptionMap.get(i.getSourceId()));
223                     }
224                     int programNumber = i.getProgramNumber();
225                     mProgramNumberToVctItemMap.put(programNumber, i);
226                     List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber);
227                     if (pmtList != null) {
228                         mProgramNumberHandledStatus.put(programNumber, true);
229                         handleVctItem(i, pmtList);
230                         mHandledVctItemCount++;
231                         if (mHandledVctItemCount >= mVctItemCount
232                                 && mVctSectionParsedCount >= mVctSectionParsed.length
233                                 && mListener != null) {
234                             mListener.onAllVctItemsParsed();
235                         }
236                     } else {
237                         mProgramNumberHandledStatus.put(programNumber, false);
238                         Log.i(TAG, "onVCTParsed, but PMT for programNo " + programNumber
239                                 + " is not found yet.");
240                     }
241                 }
242             }
243 
244             @Override
245             public void onEitParsed(int sourceId, List<EitItem> items) {
246                 if (DEBUG) Log.d(TAG, "onEITParsed " + sourceId);
247                 EventSourceEntry entry = new EventSourceEntry(mPid, sourceId);
248                 mEitMap.put(entry, items);
249                 handleEvents(sourceId);
250             }
251 
252             @Override
253             public void onEttParsed(int sourceId, List<EttItem> descriptions) {
254                 if (DEBUG) {
255                     Log.d(TAG, String.format("onETTParsed sourceId: %d, descriptions.size(): %d",
256                             sourceId, descriptions.size()));
257                 }
258                 for (EttItem item : descriptions) {
259                     if (item.eventId == 0) {
260                         // Channel description
261                         mSourceIdToVctItemDescriptionMap.put(sourceId, item.text);
262                         VctItem vctItem = mSourceIdToVctItemMap.get(sourceId);
263                         if (vctItem != null) {
264                             vctItem.setDescription(item.text);
265                             List<PmtItem> pmtItems =
266                                     mProgramNumberToPMTMap.get(vctItem.getProgramNumber());
267                             if (pmtItems != null) {
268                                 handleVctItem(vctItem, pmtItems);
269                             }
270                         }
271                     }
272                 }
273 
274                 // Event Information description
275                 EventSourceEntry entry = new EventSourceEntry(mPid, sourceId);
276                 mETTMap.put(entry, descriptions);
277                 handleEvents(sourceId);
278             }
279         };
280     }
281 
282     private static class EventSourceEntry {
283         public final int pid;
284         public final int sourceId;
285 
EventSourceEntry(int pid, int sourceId)286         public EventSourceEntry(int pid, int sourceId) {
287             this.pid = pid;
288             this.sourceId = sourceId;
289         }
290 
291         @Override
hashCode()292         public int hashCode() {
293             int result = 17;
294             result = 31 * result + pid;
295             result = 31 * result + sourceId;
296             return result;
297         }
298 
299         @Override
equals(Object obj)300         public boolean equals(Object obj) {
301             if (obj instanceof EventSourceEntry) {
302                 EventSourceEntry another = (EventSourceEntry) obj;
303                 return pid == another.pid && sourceId == another.sourceId;
304             }
305             return false;
306         }
307     }
308 
handleVctItem(VctItem channel, List<PmtItem> pmtItems)309     private void handleVctItem(VctItem channel, List<PmtItem> pmtItems) {
310         if (DEBUG) {
311             Log.d(TAG, "handleVctItem " + channel);
312         }
313         if (mListener != null) {
314             mListener.onVctItemParsed(channel, pmtItems);
315         }
316         int sourceId = channel.getSourceId();
317         int statusIndex = mVctItemHandledStatus.indexOfKey(sourceId);
318         if (statusIndex < 0) {
319             mVctItemHandledStatus.put(sourceId, false);
320             return;
321         }
322         if (!mVctItemHandledStatus.valueAt(statusIndex)) {
323             List<EitItem> eitItems = mSourceIdToEitMap.get(sourceId);
324             if (eitItems != null) {
325                 // When VCT is parsed later than EIT.
326                 mVctItemHandledStatus.put(sourceId, true);
327                 handleEitItems(channel, eitItems);
328             }
329         }
330     }
331 
handleEitItems(VctItem channel, List<EitItem> items)332     private void handleEitItems(VctItem channel, List<EitItem> items) {
333         if (mListener != null) {
334             mListener.onEitItemParsed(channel, items);
335         }
336     }
337 
handleEvents(int sourceId)338     private void handleEvents(int sourceId) {
339         Map<Integer, EitItem> itemSet = new HashMap<>();
340         for (int pid : mEITPids) {
341             List<EitItem> eitItems = mEitMap.get(new EventSourceEntry(pid, sourceId));
342             if (eitItems != null) {
343                 for (EitItem item : eitItems) {
344                     item.setDescription(null);
345                     itemSet.put(item.getEventId(), item);
346                 }
347             }
348         }
349         for (int pid : mETTPids) {
350             List<EttItem> ettItems = mETTMap.get(new EventSourceEntry(pid, sourceId));
351             if (ettItems != null) {
352                 for (EttItem ettItem : ettItems) {
353                     if (ettItem.eventId != 0) {
354                         EitItem item = itemSet.get(ettItem.eventId);
355                         if (item != null) {
356                             item.setDescription(ettItem.text);
357                         }
358                     }
359                 }
360             }
361         }
362         List<EitItem> items = new ArrayList<>(itemSet.values());
363         mSourceIdToEitMap.put(sourceId, items);
364         VctItem channel = mSourceIdToVctItemMap.get(sourceId);
365         if (channel != null && mProgramNumberHandledStatus.get(channel.getProgramNumber())) {
366             mVctItemHandledStatus.put(sourceId, true);
367             handleEitItems(channel, items);
368         } else {
369             mVctItemHandledStatus.put(sourceId, false);
370             Log.i(TAG, "onEITParsed, but VCT for sourceId " + sourceId + " is not found yet.");
371         }
372     }
373 
374     /**
375      * Creates MPEG-2 TS parser.
376      * @param listener TsOutputListener
377      */
TsParser(TsOutputListener listener)378     public TsParser(TsOutputListener listener) {
379         startListening(ATSC_SI_BASE_PID);
380         startListening(PAT_PID);
381         mListener = listener;
382     }
383 
startListening(int pid)384     private void startListening(int pid) {
385         mStreamMap.put(pid, new SectionStream(pid));
386     }
387 
feedTSPacket(byte[] tsData, int pos)388     private boolean feedTSPacket(byte[] tsData, int pos) {
389         if (tsData.length < pos + TS_PACKET_SIZE) {
390             if (DEBUG) Log.d(TAG, "Data should include a single TS packet.");
391             return false;
392         }
393         if (tsData[pos] != TS_PACKET_START_CODE) {
394             if (DEBUG) Log.d(TAG, "Invalid ts packet.");
395             return false;
396         }
397         if ((tsData[pos + 1] & TS_PACKET_TEI_MASK) != 0) {
398             if (DEBUG) Log.d(TAG, "Erroneous ts packet.");
399             return false;
400         }
401 
402         // For details for the structure of TS packet, see H.222.0 Table 2-2.
403         int pid = ((tsData[pos + 1] & 0x1f) << 8) | (tsData[pos + 2] & 0xff);
404         boolean hasAdaptation = (tsData[pos + 3] & 0x20) != 0;
405         boolean hasPayload = (tsData[pos + 3] & 0x10) != 0;
406         boolean payloadStartIndicator = (tsData[pos + 1] & 0x40) != 0;
407         int continuityCounter = tsData[pos + 3] & 0x0f;
408         Stream stream = mStreamMap.get(pid);
409         int payloadPos = pos;
410         payloadPos += hasAdaptation ? 5 + (tsData[pos + 4] & 0xff) : 4;
411         if (!hasPayload || stream == null) {
412             // We are not interested in this packet.
413             return false;
414         }
415         if (payloadPos > pos + TS_PACKET_SIZE) {
416             if (DEBUG) Log.d(TAG, "Payload should be included in a single TS packet.");
417             return false;
418         }
419         stream.feedData(Arrays.copyOfRange(tsData, payloadPos, pos + TS_PACKET_SIZE),
420                 continuityCounter, payloadStartIndicator);
421         return true;
422     }
423 
424     /**
425      * Feeds MPEG-2 TS data to parse.
426      * @param tsData buffer for ATSC TS stream
427      * @param pos the offset where buffer starts
428      * @param length The length of available data
429      */
feedTSData(byte[] tsData, int pos, int length)430     public void feedTSData(byte[] tsData, int pos, int length) {
431         for (; pos <= length - TS_PACKET_SIZE; pos += TS_PACKET_SIZE) {
432             feedTSPacket(tsData, pos);
433         }
434     }
435 
436     /**
437      * Retrieves the channel information regardless of being well-formed.
438      * @return {@link List} of {@link TunerChannel}
439      */
getMalFormedChannels()440     public List<TunerChannel> getMalFormedChannels() {
441         List<TunerChannel> incompleteChannels = new ArrayList<>();
442         for (int i = 0; i < mProgramNumberHandledStatus.size(); i++) {
443             if (!mProgramNumberHandledStatus.valueAt(i)) {
444                 int programNumber = mProgramNumberHandledStatus.keyAt(i);
445                 List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber);
446                 if (pmtList != null) {
447                     TunerChannel tunerChannel = new TunerChannel(programNumber, pmtList);
448                     incompleteChannels.add(tunerChannel);
449                 }
450             }
451         }
452         return incompleteChannels;
453     }
454 }
455