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.usbtuner.tvinput; 18 19 import android.util.Log; 20 import android.util.SparseArray; 21 import android.util.SparseBooleanArray; 22 23 import com.android.usbtuner.FileDataSource; 24 import com.android.usbtuner.data.PsiData.PatItem; 25 import com.android.usbtuner.data.PsiData.PmtItem; 26 import com.android.usbtuner.data.PsipData.EitItem; 27 import com.android.usbtuner.data.PsipData.VctItem; 28 import com.android.usbtuner.data.Track.AtscAudioTrack; 29 import com.android.usbtuner.data.Track.AtscCaptionTrack; 30 import com.android.usbtuner.data.TunerChannel; 31 import com.android.usbtuner.ts.TsParser; 32 import com.android.usbtuner.ts.TsParser.TsOutputListener; 33 import com.android.usbtuner.tvinput.EventDetector.EventListener; 34 35 import java.util.ArrayList; 36 import java.util.HashSet; 37 import java.util.List; 38 import java.util.Set; 39 40 /** 41 * PSIP event detector for a file source. 42 * 43 * <p>Uses {@link TsParser} to analyze input MPEG-2 transport stream, detects and reports 44 * various PSIP-related events via {@link TsOutputListener}. 45 */ 46 public class FileSourceEventDetector { 47 private static final String TAG = "FileSourceEventDetector"; 48 private static final boolean DEBUG = true; 49 50 private TsParser mTsParser; 51 private final Set<Integer> mVctProgramNumberSet = new HashSet<>(); 52 private final SparseArray<TunerChannel> mChannelMap = new SparseArray<>(); 53 private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray(); 54 private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray(); 55 private final EventListener mEventListener; 56 private FileDataSource.StreamProvider mSource; 57 58 public FileSourceEventDetector(EventDetector.EventListener listener) { 59 mEventListener = listener; 60 } 61 62 public void start(FileDataSource.StreamProvider source) { 63 mSource = source; 64 reset(); 65 } 66 67 private void reset() { 68 mTsParser = new TsParser(mTsOutputListener); // TODO: Use TsParser.reset() 69 mSource.clearPidFilter(); 70 mVctProgramNumberSet.clear(); 71 mVctCaptionTracksFound.clear(); 72 mEitCaptionTracksFound.clear(); 73 mChannelMap.clear(); 74 } 75 76 public void feedTSStream(byte[] data, int startOffset, int length) { 77 if (mSource.isFilterEmpty()) { 78 startListening(TsParser.ATSC_SI_BASE_PID); 79 startListening(TsParser.PAT_PID); 80 } 81 if (mTsParser != null) { 82 mTsParser.feedTSData(data, startOffset, length); 83 } 84 } 85 86 private void startListening(int pid) { 87 if (mSource.isInFilter(pid)) { 88 return; 89 } 90 mSource.addPidFilter(pid); 91 } 92 93 private TsOutputListener mTsOutputListener = new TsOutputListener() { 94 @Override 95 public void onPatDetected(List<PatItem> items) { 96 for (PatItem i : items) { 97 mSource.addPidFilter(i.getPmtPid()); 98 } 99 } 100 101 @Override 102 public void onEitPidDetected(int pid) { 103 startListening(pid); 104 } 105 106 @Override 107 public void onEitItemParsed(VctItem channel, List<EitItem> items) { 108 TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber()); 109 if (DEBUG) { 110 Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " " 111 + channel.getProgramNumber()); 112 } 113 int channelSourceId = channel.getSourceId(); 114 115 // Source id 0 is useful for cases where a cable operator wishes to define a channel for 116 // which no EPG data is currently available. 117 // We don't handle such a case. 118 if (channelSourceId == 0) { 119 return; 120 } 121 122 // If at least a one caption track have been found in EIT items for the given channel, 123 // we starts to interpret the zero tracks as a clearance of the caption tracks. 124 boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId); 125 for (EitItem item : items) { 126 if (captionTracksFound) { 127 break; 128 } 129 List<AtscCaptionTrack> captionTracks = item.getCaptionTracks(); 130 if (captionTracks != null && !captionTracks.isEmpty()) { 131 captionTracksFound = true; 132 } 133 } 134 mEitCaptionTracksFound.put(channelSourceId, captionTracksFound); 135 if (captionTracksFound) { 136 for (EitItem item : items) { 137 item.setHasCaptionTrack(); 138 } 139 } 140 if (tunerChannel != null && mEventListener != null) { 141 mEventListener.onEventDetected(tunerChannel, items); 142 } 143 } 144 145 @Override 146 public void onEttPidDetected(int pid) { 147 startListening(pid); 148 } 149 150 @Override 151 public void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems) { 152 if (DEBUG) { 153 Log.d(TAG, "onVctItemParsed VCT " + channel); 154 Log.d(TAG, " PMT " + pmtItems); 155 } 156 157 // Merges the audio and caption tracks located in PMT items into the tracks of the given 158 // tuner channel. 159 TunerChannel tunerChannel = TunerChannel.forFile(channel, pmtItems); 160 List<AtscAudioTrack> audioTracks = new ArrayList<>(); 161 List<AtscCaptionTrack> captionTracks = new ArrayList<>(); 162 for (PmtItem pmtItem : pmtItems) { 163 if (pmtItem.getAudioTracks() != null) { 164 audioTracks.addAll(pmtItem.getAudioTracks()); 165 } 166 if (pmtItem.getCaptionTracks() != null) { 167 captionTracks.addAll(pmtItem.getCaptionTracks()); 168 } 169 } 170 int channelProgramNumber = channel.getProgramNumber(); 171 172 // If at least a one caption track have been found in VCT items for the given channel, 173 // we starts to interpret the zero tracks as a clearance of the caption tracks. 174 boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber) 175 || !captionTracks.isEmpty(); 176 mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound); 177 if (captionTracksFound) { 178 tunerChannel.setHasCaptionTrack(); 179 } 180 tunerChannel.setFilepath(mSource.getFilepath()); 181 tunerChannel.setAudioTracks(audioTracks); 182 tunerChannel.setCaptionTracks(captionTracks); 183 184 mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); 185 boolean found = mVctProgramNumberSet.contains(channelProgramNumber); 186 if (!found) { 187 mVctProgramNumberSet.add(channelProgramNumber); 188 } 189 if (mEventListener != null) { 190 mEventListener.onChannelDetected(tunerChannel, !found); 191 } 192 } 193 }; 194 } 195