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