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 17package com.android.tv.tuner.ts; 18 19import android.util.Log; 20import android.util.SparseArray; 21import android.util.SparseBooleanArray; 22 23import com.android.tv.tuner.data.PsiData.PatItem; 24import com.android.tv.tuner.data.PsiData.PmtItem; 25import com.android.tv.tuner.data.PsipData.EitItem; 26import com.android.tv.tuner.data.PsipData.EttItem; 27import com.android.tv.tuner.data.PsipData.MgtItem; 28import com.android.tv.tuner.data.PsipData.SdtItem; 29import com.android.tv.tuner.data.PsipData.VctItem; 30import com.android.tv.tuner.data.TunerChannel; 31import com.android.tv.tuner.ts.SectionParser.OutputListener; 32import com.android.tv.tuner.util.ByteArrayBuffer; 33 34import java.util.ArrayList; 35import java.util.Arrays; 36import java.util.HashMap; 37import java.util.List; 38import java.util.Map; 39import java.util.TreeSet; 40 41/** 42 * Parses MPEG-2 TS packets. 43 */ 44public 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