Home | History | Annotate | Download | only in ts
      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 {
     81         void onPatDetected(List<PatItem> items);
     82         void onEitPidDetected(int pid);
     83         void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems);
     84         void onEitItemParsed(VctItem channel, List<EitItem> items);
     85         void onEttPidDetected(int pid);
     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 
     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 
    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 
    111         public SectionStream(int pid) {
    112             mPid = pid;
    113             mSectionParser = new SectionParser(mSectionListener);
    114         }
    115 
    116         @Override
    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 
    286         public EventSourceEntry(int pid, int sourceId) {
    287             this.pid = pid;
    288             this.sourceId = sourceId;
    289         }
    290 
    291         @Override
    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
    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 
    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 
    332     private void handleEitItems(VctItem channel, List<EitItem> items) {
    333         if (mListener != null) {
    334             mListener.onEitItemParsed(channel, items);
    335         }
    336     }
    337 
    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      */
    378     public TsParser(TsOutputListener listener) {
    379         startListening(ATSC_SI_BASE_PID);
    380         startListening(PAT_PID);
    381         mListener = listener;
    382     }
    383 
    384     private void startListening(int pid) {
    385         mStreamMap.put(pid, new SectionStream(pid));
    386     }
    387 
    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      */
    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      */
    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