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.usbtuner.ts; 18 19import android.util.Log; 20import android.util.SparseArray; 21import android.util.SparseBooleanArray; 22 23import com.android.usbtuner.data.PsiData.PatItem; 24import com.android.usbtuner.data.PsiData.PmtItem; 25import com.android.usbtuner.data.PsipData.EitItem; 26import com.android.usbtuner.data.PsipData.EttItem; 27import com.android.usbtuner.data.PsipData.MgtItem; 28import com.android.usbtuner.data.PsipData.VctItem; 29import com.android.usbtuner.data.TunerChannel; 30import com.android.usbtuner.ts.SectionParser.OutputListener; 31import com.android.usbtuner.util.ByteArrayBuffer; 32 33import java.util.ArrayList; 34import java.util.Arrays; 35import java.util.HashMap; 36import java.util.List; 37import java.util.Map; 38import java.util.TreeSet; 39 40/** 41 * Parses MPEG-2 TS packets. 42 */ 43public class TsParser { 44 private static final String TAG = "TsParser"; 45 private static 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 TsOutputListener mListener; 74 75 private int mPartialTSPacketSize; 76 private byte[] mPartialTSPacketBuf = new byte[TS_PACKET_SIZE]; 77 78 public interface TsOutputListener { 79 void onPatDetected(List<PatItem> items); 80 void onEitPidDetected(int pid); 81 void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems); 82 void onEitItemParsed(VctItem channel, List<EitItem> items); 83 void onEttPidDetected(int pid); 84 } 85 86 private abstract class Stream { 87 private static final int INVALID_CONTINUITY_COUNTER = -1; 88 private static final int NUM_CONTINUITY_COUNTER = 16; 89 90 protected int mContinuityCounter = INVALID_CONTINUITY_COUNTER; 91 protected final ByteArrayBuffer mPacket = new ByteArrayBuffer(TS_PACKET_SIZE); 92 93 public void feedData(byte[] data, int continuityCounter, boolean startIndicator) { 94 if ((mContinuityCounter + 1) % NUM_CONTINUITY_COUNTER != continuityCounter) { 95 mPacket.setLength(0); 96 } 97 mContinuityCounter = continuityCounter; 98 handleData(data, startIndicator); 99 } 100 101 protected abstract void handleData(byte[] data, boolean startIndicator); 102 } 103 104 private class SectionStream extends Stream { 105 private final SectionParser mSectionParser; 106 private final int mPid; 107 108 public SectionStream(int pid) { 109 mPid = pid; 110 mSectionParser = new SectionParser(mSectionListener); 111 } 112 113 @Override 114 protected void handleData(byte[] data, boolean startIndicator) { 115 int startPos = 0; 116 if (mPacket.length() == 0) { 117 if (startIndicator) { 118 startPos = (data[0] & 0xff) + 1; 119 } else { 120 // Don't know where the section starts yet. Wait until start indicator is on. 121 return; 122 } 123 } else { 124 if (startIndicator) { 125 startPos = 1; 126 } 127 } 128 129 // When a broken packet is encountered, parsing will stop and return right away. 130 if (startPos >= data.length) { 131 mPacket.setLength(0); 132 return; 133 } 134 mPacket.append(data, startPos, data.length - startPos); 135 mSectionParser.parseSections(mPacket); 136 } 137 138 private OutputListener mSectionListener = new OutputListener() { 139 @Override 140 public void onPatParsed(List<PatItem> items) { 141 for (PatItem i : items) { 142 startListening(i.getPmtPid()); 143 } 144 if (mListener != null) { 145 mListener.onPatDetected(items); 146 } 147 } 148 149 @Override 150 public void onPmtParsed(int programNumber, List<PmtItem> items) { 151 mProgramNumberToPMTMap.put(programNumber, items); 152 if (DEBUG) { 153 Log.d(TAG, "onPMTParsed, programNo " + programNumber + " handledStatus is " 154 + mProgramNumberHandledStatus.get(programNumber, false)); 155 } 156 int statusIndex = mProgramNumberHandledStatus.indexOfKey(programNumber); 157 if (statusIndex < 0) { 158 mProgramNumberHandledStatus.put(programNumber, false); 159 return; 160 } 161 if (!mProgramNumberHandledStatus.valueAt(statusIndex)) { 162 VctItem vctItem = mProgramNumberToVctItemMap.get(programNumber); 163 if (vctItem != null) { 164 // When PMT is parsed later than VCT. 165 mProgramNumberHandledStatus.put(programNumber, true); 166 handleVctItem(vctItem, items); 167 } 168 } 169 } 170 171 @Override 172 public void onMgtParsed(List<MgtItem> items) { 173 for (MgtItem i : items) { 174 if (mStreamMap.get(i.getTableTypePid()) != null) { 175 continue; 176 } 177 if (i.getTableType() >= MgtItem.TABLE_TYPE_EIT_RANGE_START 178 && i.getTableType() <= MgtItem.TABLE_TYPE_EIT_RANGE_END) { 179 startListening(i.getTableTypePid()); 180 mEITPids.add(i.getTableTypePid()); 181 if (mListener != null) { 182 mListener.onEitPidDetected(i.getTableTypePid()); 183 } 184 } else if (i.getTableType() == MgtItem.TABLE_TYPE_CHANNEL_ETT || 185 (i.getTableType() >= MgtItem.TABLE_TYPE_ETT_RANGE_START 186 && i.getTableType() <= MgtItem.TABLE_TYPE_ETT_RANGE_END)) { 187 startListening(i.getTableTypePid()); 188 mETTPids.add(i.getTableTypePid()); 189 if (mListener != null) { 190 mListener.onEttPidDetected(i.getTableTypePid()); 191 } 192 } 193 } 194 } 195 196 @Override 197 public void onVctParsed(List<VctItem> items) { 198 for (VctItem i : items) { 199 if (DEBUG) Log.d(TAG, "onVCTParsed " + i); 200 if (i.getSourceId() != 0) { 201 mSourceIdToVctItemMap.put(i.getSourceId(), i); 202 i.setDescription(mSourceIdToVctItemDescriptionMap.get(i.getSourceId())); 203 } 204 int programNumber = i.getProgramNumber(); 205 mProgramNumberToVctItemMap.put(programNumber, i); 206 List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber); 207 if (pmtList != null) { 208 mProgramNumberHandledStatus.put(programNumber, true); 209 handleVctItem(i, pmtList); 210 } else { 211 mProgramNumberHandledStatus.put(programNumber, false); 212 Log.i(TAG, "onVCTParsed, but PMT for programNo " + programNumber 213 + " is not found yet."); 214 } 215 } 216 } 217 218 @Override 219 public void onEitParsed(int sourceId, List<EitItem> items) { 220 if (DEBUG) Log.d(TAG, "onEITParsed " + sourceId); 221 EventSourceEntry entry = new EventSourceEntry(mPid, sourceId); 222 mEitMap.put(entry, items); 223 handleEvents(sourceId); 224 } 225 226 @Override 227 public void onEttParsed(int sourceId, List<EttItem> descriptions) { 228 if (DEBUG) { 229 Log.d(TAG, String.format("onETTParsed sourceId: %d, descriptions.size(): %d", 230 sourceId, descriptions.size())); 231 } 232 for (EttItem item : descriptions) { 233 if (item.eventId == 0) { 234 // Channel description 235 mSourceIdToVctItemDescriptionMap.put(sourceId, item.text); 236 VctItem vctItem = mSourceIdToVctItemMap.get(sourceId); 237 if (vctItem != null) { 238 vctItem.setDescription(item.text); 239 List<PmtItem> pmtItems = 240 mProgramNumberToPMTMap.get(vctItem.getProgramNumber()); 241 if (pmtItems != null) { 242 handleVctItem(vctItem, pmtItems); 243 } 244 } 245 } 246 } 247 248 // Event Information description 249 EventSourceEntry entry = new EventSourceEntry(mPid, sourceId); 250 mETTMap.put(entry, descriptions); 251 handleEvents(sourceId); 252 } 253 }; 254 } 255 256 private static class EventSourceEntry { 257 public final int pid; 258 public final int sourceId; 259 260 public EventSourceEntry(int pid, int sourceId) { 261 this.pid = pid; 262 this.sourceId = sourceId; 263 } 264 265 @Override 266 public int hashCode() { 267 int result = 17; 268 result = 31 * result + pid; 269 result = 31 * result + sourceId; 270 return result; 271 } 272 273 @Override 274 public boolean equals(Object obj) { 275 if (obj instanceof EventSourceEntry) { 276 EventSourceEntry another = (EventSourceEntry) obj; 277 return pid == another.pid && sourceId == another.sourceId; 278 } 279 return false; 280 } 281 } 282 283 private void handleVctItem(VctItem channel, List<PmtItem> pmtItems) { 284 if (mListener != null) { 285 mListener.onVctItemParsed(channel, pmtItems); 286 } 287 int sourceId = channel.getSourceId(); 288 int statusIndex = mVctItemHandledStatus.indexOfKey(sourceId); 289 if (statusIndex < 0) { 290 mVctItemHandledStatus.put(sourceId, false); 291 return; 292 } 293 if (!mVctItemHandledStatus.valueAt(statusIndex)) { 294 List<EitItem> eitItems = mSourceIdToEitMap.get(sourceId); 295 if (eitItems != null) { 296 // When VCT is parsed later than EIT. 297 mVctItemHandledStatus.put(sourceId, true); 298 handleEitItems(channel, eitItems); 299 } 300 } 301 } 302 303 private void handleEitItems(VctItem channel, List<EitItem> items) { 304 if (mListener != null) { 305 mListener.onEitItemParsed(channel, items); 306 } 307 } 308 309 private void handleEvents(int sourceId) { 310 Map<Integer, EitItem> itemSet = new HashMap<>(); 311 for (int pid : mEITPids) { 312 List<EitItem> eitItems = mEitMap.get(new EventSourceEntry(pid, sourceId)); 313 if (eitItems != null) { 314 for (EitItem item : eitItems) { 315 item.setDescription(null); 316 itemSet.put(item.getEventId(), item); 317 } 318 } 319 } 320 for (int pid : mETTPids) { 321 List<EttItem> ettItems = mETTMap.get(new EventSourceEntry(pid, sourceId)); 322 if (ettItems != null) { 323 for (EttItem ettItem : ettItems) { 324 if (ettItem.eventId != 0) { 325 EitItem item = itemSet.get(ettItem.eventId); 326 if (item != null) { 327 item.setDescription(ettItem.text); 328 } 329 } 330 } 331 } 332 } 333 List<EitItem> items = new ArrayList<>(itemSet.values()); 334 mSourceIdToEitMap.put(sourceId, items); 335 VctItem channel = mSourceIdToVctItemMap.get(sourceId); 336 if (channel != null && mProgramNumberHandledStatus.get(channel.getProgramNumber())) { 337 mVctItemHandledStatus.put(sourceId, true); 338 handleEitItems(channel, items); 339 } else { 340 mVctItemHandledStatus.put(sourceId, false); 341 Log.i(TAG, "onEITParsed, but VCT for sourceId " + sourceId + " is not found yet."); 342 } 343 } 344 345 public TsParser(TsOutputListener listener) { 346 startListening(ATSC_SI_BASE_PID); 347 startListening(PAT_PID); 348 mListener = listener; 349 } 350 351 private void startListening(int pid) { 352 mStreamMap.put(pid, new SectionStream(pid)); 353 } 354 355 private boolean feedTSPacket(byte[] tsData, int pos) { 356 if (tsData.length < pos + TS_PACKET_SIZE) { 357 if (DEBUG) Log.d(TAG, "Data should include a single TS packet."); 358 return false; 359 } 360 if (tsData[pos] != TS_PACKET_START_CODE) { 361 if (DEBUG) Log.d(TAG, "Invalid ts packet."); 362 return false; 363 } 364 if ((tsData[pos + 1] & TS_PACKET_TEI_MASK) != 0) { 365 if (DEBUG) Log.d(TAG, "Erroneous ts packet."); 366 return false; 367 } 368 369 // For details for the structure of TS packet, see H.222.0 Table 2-2. 370 int pid = ((tsData[pos + 1] & 0x1f) << 8) | (tsData[pos + 2] & 0xff); 371 boolean hasAdaptation = (tsData[pos + 3] & 0x20) != 0; 372 boolean hasPayload = (tsData[pos + 3] & 0x10) != 0; 373 boolean payloadStartIndicator = (tsData[pos + 1] & 0x40) != 0; 374 int continuityCounter = tsData[pos + 3] & 0x0f; 375 Stream stream = mStreamMap.get(pid); 376 int payloadPos = pos; 377 payloadPos += hasAdaptation ? 5 + (tsData[pos + 4] & 0xff) : 4; 378 if (!hasPayload || stream == null) { 379 // We are not interested in this packet. 380 return false; 381 } 382 if (payloadPos > pos + TS_PACKET_SIZE) { 383 if (DEBUG) Log.d(TAG, "Payload should be included in a single TS packet."); 384 return false; 385 } 386 stream.feedData(Arrays.copyOfRange(tsData, payloadPos, pos + TS_PACKET_SIZE), 387 continuityCounter, payloadStartIndicator); 388 return true; 389 } 390 391 public void feedTSData(byte[] tsData, int pos, int length) { 392 int origPos = pos; 393 if (mPartialTSPacketSize != 0 394 && (mPartialTSPacketSize + length) > TS_PACKET_SIZE) { 395 System.arraycopy(tsData, pos, mPartialTSPacketBuf, mPartialTSPacketSize, 396 TS_PACKET_SIZE - mPartialTSPacketSize); 397 feedTSPacket(mPartialTSPacketBuf, 0); 398 pos += TS_PACKET_SIZE - mPartialTSPacketSize; 399 mPartialTSPacketSize = 0; 400 } 401 for (; pos <= length - TS_PACKET_SIZE; pos += TS_PACKET_SIZE) { 402 feedTSPacket(tsData, pos); 403 } 404 int remaining = origPos + length - pos; 405 if (remaining > 0) { 406 System.arraycopy(tsData, pos, mPartialTSPacketBuf, mPartialTSPacketSize, remaining); 407 } 408 } 409 410 public List<TunerChannel> getIncompleteChannels() { 411 List<TunerChannel> incompleteChannels = new ArrayList<>(); 412 for (int i = 0; i < mProgramNumberHandledStatus.size(); i++) { 413 if (!mProgramNumberHandledStatus.valueAt(i)) { 414 int programNumber = mProgramNumberHandledStatus.keyAt(i); 415 List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber); 416 if (pmtList != null) { 417 TunerChannel tunerChannel = new TunerChannel(programNumber, pmtList); 418 incompleteChannels.add(tunerChannel); 419 } 420 } 421 } 422 return incompleteChannels; 423 } 424} 425