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