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