• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ts;
18 
19 import android.util.Log;
20 import android.util.SparseArray;
21 import android.util.SparseBooleanArray;
22 
23 import com.android.usbtuner.data.PsiData.PatItem;
24 import com.android.usbtuner.data.PsiData.PmtItem;
25 import com.android.usbtuner.data.PsipData.EitItem;
26 import com.android.usbtuner.data.PsipData.EttItem;
27 import com.android.usbtuner.data.PsipData.MgtItem;
28 import com.android.usbtuner.data.PsipData.VctItem;
29 import com.android.usbtuner.data.TunerChannel;
30 import com.android.usbtuner.ts.SectionParser.OutputListener;
31 import com.android.usbtuner.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 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 TsOutputListener mListener;
74 
75     private int mPartialTSPacketSize;
76     private byte[] mPartialTSPacketBuf = new byte[TS_PACKET_SIZE];
77 
78     public interface TsOutputListener {
onPatDetected(List<PatItem> items)79         void onPatDetected(List<PatItem> items);
onEitPidDetected(int pid)80         void onEitPidDetected(int pid);
onVctItemParsed(VctItem channel, List<PmtItem> pmtItems)81         void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems);
onEitItemParsed(VctItem channel, List<EitItem> items)82         void onEitItemParsed(VctItem channel, List<EitItem> items);
onEttPidDetected(int pid)83         void onEttPidDetected(int pid);
84     }
85 
86     private abstract class Stream {
87         private static final int INVALID_CONTINUITY_COUNTER = -1;
88         private static final int NUM_CONTINUITY_COUNTER = 16;
89 
90         protected int mContinuityCounter = INVALID_CONTINUITY_COUNTER;
91         protected final ByteArrayBuffer mPacket = new ByteArrayBuffer(TS_PACKET_SIZE);
92 
feedData(byte[] data, int continuityCounter, boolean startIndicator)93         public void feedData(byte[] data, int continuityCounter, boolean startIndicator) {
94             if ((mContinuityCounter + 1) % NUM_CONTINUITY_COUNTER != continuityCounter) {
95                 mPacket.setLength(0);
96             }
97             mContinuityCounter = continuityCounter;
98             handleData(data, startIndicator);
99         }
100 
handleData(byte[] data, boolean startIndicator)101         protected abstract void handleData(byte[] data, boolean startIndicator);
102     }
103 
104     private class SectionStream extends Stream {
105         private final SectionParser mSectionParser;
106         private final int mPid;
107 
SectionStream(int pid)108         public SectionStream(int pid) {
109             mPid = pid;
110             mSectionParser = new SectionParser(mSectionListener);
111         }
112 
113         @Override
handleData(byte[] data, boolean startIndicator)114         protected void handleData(byte[] data, boolean startIndicator) {
115             int startPos = 0;
116             if (mPacket.length() == 0) {
117                 if (startIndicator) {
118                     startPos = (data[0] & 0xff) + 1;
119                 } else {
120                     // Don't know where the section starts yet. Wait until start indicator is on.
121                     return;
122                 }
123             } else {
124                 if (startIndicator) {
125                     startPos = 1;
126                 }
127             }
128 
129             // When a broken packet is encountered, parsing will stop and return right away.
130             if (startPos >= data.length) {
131                 mPacket.setLength(0);
132                 return;
133             }
134             mPacket.append(data, startPos, data.length - startPos);
135             mSectionParser.parseSections(mPacket);
136         }
137 
138         private OutputListener mSectionListener = new OutputListener() {
139             @Override
140             public void onPatParsed(List<PatItem> items) {
141                 for (PatItem i : items) {
142                     startListening(i.getPmtPid());
143                 }
144                 if (mListener != null) {
145                     mListener.onPatDetected(items);
146                 }
147             }
148 
149             @Override
150             public void onPmtParsed(int programNumber, List<PmtItem> items) {
151                 mProgramNumberToPMTMap.put(programNumber, items);
152                 if (DEBUG) {
153                     Log.d(TAG, "onPMTParsed, programNo " + programNumber + " handledStatus is "
154                             + mProgramNumberHandledStatus.get(programNumber, false));
155                 }
156                 int statusIndex = mProgramNumberHandledStatus.indexOfKey(programNumber);
157                 if (statusIndex < 0) {
158                     mProgramNumberHandledStatus.put(programNumber, false);
159                     return;
160                 }
161                 if (!mProgramNumberHandledStatus.valueAt(statusIndex)) {
162                     VctItem vctItem = mProgramNumberToVctItemMap.get(programNumber);
163                     if (vctItem != null) {
164                         // When PMT is parsed later than VCT.
165                         mProgramNumberHandledStatus.put(programNumber, true);
166                         handleVctItem(vctItem, items);
167                     }
168                 }
169             }
170 
171             @Override
172             public void onMgtParsed(List<MgtItem> items) {
173                 for (MgtItem i : items) {
174                     if (mStreamMap.get(i.getTableTypePid()) != null) {
175                         continue;
176                     }
177                     if (i.getTableType() >= MgtItem.TABLE_TYPE_EIT_RANGE_START
178                             && i.getTableType() <= MgtItem.TABLE_TYPE_EIT_RANGE_END) {
179                         startListening(i.getTableTypePid());
180                         mEITPids.add(i.getTableTypePid());
181                         if (mListener != null) {
182                             mListener.onEitPidDetected(i.getTableTypePid());
183                         }
184                     } else if (i.getTableType() == MgtItem.TABLE_TYPE_CHANNEL_ETT ||
185                             (i.getTableType() >= MgtItem.TABLE_TYPE_ETT_RANGE_START
186                                     && i.getTableType() <= MgtItem.TABLE_TYPE_ETT_RANGE_END)) {
187                         startListening(i.getTableTypePid());
188                         mETTPids.add(i.getTableTypePid());
189                         if (mListener != null) {
190                             mListener.onEttPidDetected(i.getTableTypePid());
191                         }
192                     }
193                 }
194             }
195 
196             @Override
197             public void onVctParsed(List<VctItem> items) {
198                 for (VctItem i : items) {
199                     if (DEBUG) Log.d(TAG, "onVCTParsed " + i);
200                     if (i.getSourceId() != 0) {
201                         mSourceIdToVctItemMap.put(i.getSourceId(), i);
202                         i.setDescription(mSourceIdToVctItemDescriptionMap.get(i.getSourceId()));
203                     }
204                     int programNumber = i.getProgramNumber();
205                     mProgramNumberToVctItemMap.put(programNumber, i);
206                     List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber);
207                     if (pmtList != null) {
208                         mProgramNumberHandledStatus.put(programNumber, true);
209                         handleVctItem(i, pmtList);
210                     } else {
211                         mProgramNumberHandledStatus.put(programNumber, false);
212                         Log.i(TAG, "onVCTParsed, but PMT for programNo " + programNumber
213                                 + " is not found yet.");
214                     }
215                 }
216             }
217 
218             @Override
219             public void onEitParsed(int sourceId, List<EitItem> items) {
220                 if (DEBUG) Log.d(TAG, "onEITParsed " + sourceId);
221                 EventSourceEntry entry = new EventSourceEntry(mPid, sourceId);
222                 mEitMap.put(entry, items);
223                 handleEvents(sourceId);
224             }
225 
226             @Override
227             public void onEttParsed(int sourceId, List<EttItem> descriptions) {
228                 if (DEBUG) {
229                     Log.d(TAG, String.format("onETTParsed sourceId: %d, descriptions.size(): %d",
230                             sourceId, descriptions.size()));
231                 }
232                 for (EttItem item : descriptions) {
233                     if (item.eventId == 0) {
234                         // Channel description
235                         mSourceIdToVctItemDescriptionMap.put(sourceId, item.text);
236                         VctItem vctItem = mSourceIdToVctItemMap.get(sourceId);
237                         if (vctItem != null) {
238                             vctItem.setDescription(item.text);
239                             List<PmtItem> pmtItems =
240                                     mProgramNumberToPMTMap.get(vctItem.getProgramNumber());
241                             if (pmtItems != null) {
242                                 handleVctItem(vctItem, pmtItems);
243                             }
244                         }
245                     }
246                 }
247 
248                 // Event Information description
249                 EventSourceEntry entry = new EventSourceEntry(mPid, sourceId);
250                 mETTMap.put(entry, descriptions);
251                 handleEvents(sourceId);
252             }
253         };
254     }
255 
256     private static class EventSourceEntry {
257         public final int pid;
258         public final int sourceId;
259 
EventSourceEntry(int pid, int sourceId)260         public EventSourceEntry(int pid, int sourceId) {
261             this.pid = pid;
262             this.sourceId = sourceId;
263         }
264 
265         @Override
hashCode()266         public int hashCode() {
267             int result = 17;
268             result = 31 * result + pid;
269             result = 31 * result + sourceId;
270             return result;
271         }
272 
273         @Override
equals(Object obj)274         public boolean equals(Object obj) {
275             if (obj instanceof EventSourceEntry) {
276                 EventSourceEntry another = (EventSourceEntry) obj;
277                 return pid == another.pid && sourceId == another.sourceId;
278             }
279             return false;
280         }
281     }
282 
handleVctItem(VctItem channel, List<PmtItem> pmtItems)283     private void handleVctItem(VctItem channel, List<PmtItem> pmtItems) {
284         if (mListener != null) {
285             mListener.onVctItemParsed(channel, pmtItems);
286         }
287         int sourceId = channel.getSourceId();
288         int statusIndex = mVctItemHandledStatus.indexOfKey(sourceId);
289         if (statusIndex < 0) {
290             mVctItemHandledStatus.put(sourceId, false);
291             return;
292         }
293         if (!mVctItemHandledStatus.valueAt(statusIndex)) {
294             List<EitItem> eitItems = mSourceIdToEitMap.get(sourceId);
295             if (eitItems != null) {
296                 // When VCT is parsed later than EIT.
297                 mVctItemHandledStatus.put(sourceId, true);
298                 handleEitItems(channel, eitItems);
299             }
300         }
301     }
302 
handleEitItems(VctItem channel, List<EitItem> items)303     private void handleEitItems(VctItem channel, List<EitItem> items) {
304         if (mListener != null) {
305             mListener.onEitItemParsed(channel, items);
306         }
307     }
308 
handleEvents(int sourceId)309     private void handleEvents(int sourceId) {
310         Map<Integer, EitItem> itemSet = new HashMap<>();
311         for (int pid : mEITPids) {
312             List<EitItem> eitItems = mEitMap.get(new EventSourceEntry(pid, sourceId));
313             if (eitItems != null) {
314                 for (EitItem item : eitItems) {
315                     item.setDescription(null);
316                     itemSet.put(item.getEventId(), item);
317                 }
318             }
319         }
320         for (int pid : mETTPids) {
321             List<EttItem> ettItems = mETTMap.get(new EventSourceEntry(pid, sourceId));
322             if (ettItems != null) {
323                 for (EttItem ettItem : ettItems) {
324                     if (ettItem.eventId != 0) {
325                         EitItem item = itemSet.get(ettItem.eventId);
326                         if (item != null) {
327                             item.setDescription(ettItem.text);
328                         }
329                     }
330                 }
331             }
332         }
333         List<EitItem> items = new ArrayList<>(itemSet.values());
334         mSourceIdToEitMap.put(sourceId, items);
335         VctItem channel = mSourceIdToVctItemMap.get(sourceId);
336         if (channel != null && mProgramNumberHandledStatus.get(channel.getProgramNumber())) {
337             mVctItemHandledStatus.put(sourceId, true);
338             handleEitItems(channel, items);
339         } else {
340             mVctItemHandledStatus.put(sourceId, false);
341             Log.i(TAG, "onEITParsed, but VCT for sourceId " + sourceId + " is not found yet.");
342         }
343     }
344 
TsParser(TsOutputListener listener)345     public TsParser(TsOutputListener listener) {
346         startListening(ATSC_SI_BASE_PID);
347         startListening(PAT_PID);
348         mListener = listener;
349     }
350 
startListening(int pid)351     private void startListening(int pid) {
352         mStreamMap.put(pid, new SectionStream(pid));
353     }
354 
feedTSPacket(byte[] tsData, int pos)355     private boolean feedTSPacket(byte[] tsData, int pos) {
356         if (tsData.length < pos + TS_PACKET_SIZE) {
357             if (DEBUG) Log.d(TAG, "Data should include a single TS packet.");
358             return false;
359         }
360         if (tsData[pos] != TS_PACKET_START_CODE) {
361             if (DEBUG) Log.d(TAG, "Invalid ts packet.");
362             return false;
363         }
364         if ((tsData[pos + 1] & TS_PACKET_TEI_MASK) != 0) {
365             if (DEBUG) Log.d(TAG, "Erroneous ts packet.");
366             return false;
367         }
368 
369         // For details for the structure of TS packet, see H.222.0 Table 2-2.
370         int pid = ((tsData[pos + 1] & 0x1f) << 8) | (tsData[pos + 2] & 0xff);
371         boolean hasAdaptation = (tsData[pos + 3] & 0x20) != 0;
372         boolean hasPayload = (tsData[pos + 3] & 0x10) != 0;
373         boolean payloadStartIndicator = (tsData[pos + 1] & 0x40) != 0;
374         int continuityCounter = tsData[pos + 3] & 0x0f;
375         Stream stream = mStreamMap.get(pid);
376         int payloadPos = pos;
377         payloadPos += hasAdaptation ? 5 + (tsData[pos + 4] & 0xff) : 4;
378         if (!hasPayload || stream == null) {
379             // We are not interested in this packet.
380             return false;
381         }
382         if (payloadPos > pos + TS_PACKET_SIZE) {
383             if (DEBUG) Log.d(TAG, "Payload should be included in a single TS packet.");
384             return false;
385         }
386         stream.feedData(Arrays.copyOfRange(tsData, payloadPos, pos + TS_PACKET_SIZE),
387                 continuityCounter, payloadStartIndicator);
388         return true;
389     }
390 
feedTSData(byte[] tsData, int pos, int length)391     public void feedTSData(byte[] tsData, int pos, int length) {
392         int origPos = pos;
393         if (mPartialTSPacketSize != 0
394                 && (mPartialTSPacketSize + length) > TS_PACKET_SIZE) {
395             System.arraycopy(tsData, pos, mPartialTSPacketBuf, mPartialTSPacketSize,
396                     TS_PACKET_SIZE - mPartialTSPacketSize);
397             feedTSPacket(mPartialTSPacketBuf, 0);
398             pos += TS_PACKET_SIZE - mPartialTSPacketSize;
399             mPartialTSPacketSize = 0;
400         }
401         for (; pos <= length - TS_PACKET_SIZE; pos += TS_PACKET_SIZE) {
402             feedTSPacket(tsData, pos);
403         }
404         int remaining = origPos + length - pos;
405         if (remaining > 0) {
406             System.arraycopy(tsData, pos, mPartialTSPacketBuf, mPartialTSPacketSize, remaining);
407         }
408     }
409 
getIncompleteChannels()410     public List<TunerChannel> getIncompleteChannels() {
411         List<TunerChannel> incompleteChannels = new ArrayList<>();
412         for (int i = 0; i < mProgramNumberHandledStatus.size(); i++) {
413             if (!mProgramNumberHandledStatus.valueAt(i)) {
414                 int programNumber = mProgramNumberHandledStatus.keyAt(i);
415                 List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber);
416                 if (pmtList != null) {
417                     TunerChannel tunerChannel = new TunerChannel(programNumber, pmtList);
418                     incompleteChannels.add(tunerChannel);
419                 }
420             }
421         }
422         return incompleteChannels;
423     }
424 }
425