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.tvinput; 18 19import android.util.Log; 20import android.util.SparseArray; 21import android.util.SparseBooleanArray; 22 23import com.android.usbtuner.TunerHal; 24import com.android.usbtuner.data.PsiData.PatItem; 25import com.android.usbtuner.data.PsiData.PmtItem; 26import com.android.usbtuner.data.PsipData.EitItem; 27import com.android.usbtuner.data.PsipData.VctItem; 28import com.android.usbtuner.data.Track.AtscAudioTrack; 29import com.android.usbtuner.data.Track.AtscCaptionTrack; 30import com.android.usbtuner.data.TunerChannel; 31import com.android.usbtuner.ts.TsParser; 32import com.android.usbtuner.ts.TsParser.TsOutputListener; 33 34import java.util.ArrayList; 35import java.util.HashSet; 36import java.util.List; 37import java.util.Set; 38 39/** 40 * Detects channels and programs that are emerged or changed while parsing ATSC PSIP information. 41 */ 42public class EventDetector { 43 private static final String TAG = "EventDetector"; 44 private static final boolean DEBUG = false; 45 46 private final TunerHal mTunerHal; 47 48 private TsParser mTsParser; 49 private final Set<Integer> mPidSet = new HashSet<>(); 50 51 // To prevent channel duplication 52 private final Set<Integer> mVctProgramNumberSet = new HashSet<>(); 53 private final SparseArray<TunerChannel> mChannelMap = new SparseArray<>(); 54 private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray(); 55 private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray(); 56 private EventListener mEventListener; 57 private int mFrequency; 58 private String mModulation; 59 60 private TsOutputListener mTsOutputListener = new TsOutputListener() { 61 @Override 62 public void onPatDetected(List<PatItem> items) { 63 for (PatItem i : items) { 64 mTunerHal.addPidFilter(i.getPmtPid(), TunerHal.FILTER_TYPE_OTHER); 65 } 66 } 67 68 @Override 69 public void onEitPidDetected(int pid) { 70 startListening(pid); 71 } 72 73 @Override 74 public void onEitItemParsed(VctItem channel, List<EitItem> items) { 75 TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber()); 76 if (DEBUG) { 77 Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " " 78 + channel.getProgramNumber()); 79 } 80 int channelSourceId = channel.getSourceId(); 81 82 // Source id 0 is useful for cases where a cable operator wishes to define a channel for 83 // which no EPG data is currently available. 84 // We don't handle such a case. 85 if (channelSourceId == 0) { 86 return; 87 } 88 89 // If at least a one caption track have been found in EIT items for the given channel, 90 // we starts to interpret the zero tracks as a clearance of the caption tracks. 91 boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId); 92 for (EitItem item : items) { 93 if (captionTracksFound) { 94 break; 95 } 96 List<AtscCaptionTrack> captionTracks = item.getCaptionTracks(); 97 if (captionTracks != null && !captionTracks.isEmpty()) { 98 captionTracksFound = true; 99 } 100 } 101 mEitCaptionTracksFound.put(channelSourceId, captionTracksFound); 102 if (captionTracksFound) { 103 for (EitItem item : items) { 104 item.setHasCaptionTrack(); 105 } 106 } 107 if (tunerChannel != null && mEventListener != null) { 108 mEventListener.onEventDetected(tunerChannel, items); 109 } 110 } 111 112 @Override 113 public void onEttPidDetected(int pid) { 114 startListening(pid); 115 } 116 117 @Override 118 public void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems) { 119 if (DEBUG) { 120 Log.d(TAG, "onVctItemParsed VCT " + channel); 121 Log.d(TAG, " PMT " + pmtItems); 122 } 123 124 // Merges the audio and caption tracks located in PMT items into the tracks of the given 125 // tuner channel. 126 TunerChannel tunerChannel = new TunerChannel(channel, pmtItems); 127 List<AtscAudioTrack> audioTracks = new ArrayList<>(); 128 List<AtscCaptionTrack> captionTracks = new ArrayList<>(); 129 for (PmtItem pmtItem : pmtItems) { 130 if (pmtItem.getAudioTracks() != null) { 131 audioTracks.addAll(pmtItem.getAudioTracks()); 132 } 133 if (pmtItem.getCaptionTracks() != null) { 134 captionTracks.addAll(pmtItem.getCaptionTracks()); 135 } 136 } 137 int channelProgramNumber = channel.getProgramNumber(); 138 139 // If at least a one caption track have been found in VCT items for the given channel, 140 // we starts to interpret the zero tracks as a clearance of the caption tracks. 141 boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber) 142 || !captionTracks.isEmpty(); 143 mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound); 144 if (captionTracksFound) { 145 tunerChannel.setHasCaptionTrack(); 146 } 147 tunerChannel.setAudioTracks(audioTracks); 148 tunerChannel.setCaptionTracks(captionTracks); 149 tunerChannel.setFrequency(mFrequency); 150 tunerChannel.setModulation(mModulation); 151 mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); 152 boolean found = mVctProgramNumberSet.contains(channelProgramNumber); 153 if (!found) { 154 mVctProgramNumberSet.add(channelProgramNumber); 155 } 156 if (mEventListener != null) { 157 mEventListener.onChannelDetected(tunerChannel, !found); 158 } 159 } 160 }; 161 162 /** 163 * Listener for detecting ATSC TV channels and receiving EPG data. 164 */ 165 public interface EventListener { 166 167 /** 168 * Fired when new information of an ATSC TV channel arrived. 169 * 170 * @param channel an ATSC TV channel 171 * @param channelArrivedAtFirstTime tells whether this channel arrived at first time 172 */ 173 void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime); 174 175 /** 176 * Fired when new program events of an ATSC TV channel arrived. 177 * 178 * @param channel an ATSC TV channel 179 * @param items a list of EIT items that were received 180 */ 181 void onEventDetected(TunerChannel channel, List<EitItem> items); 182 } 183 184 public EventDetector(TunerHal usbTunerInteface, EventListener listener) { 185 mTunerHal = usbTunerInteface; 186 mEventListener = listener; 187 } 188 189 private void reset() { 190 mTsParser = new TsParser(mTsOutputListener); // TODO: Use TsParser.reset() 191 mPidSet.clear(); 192 mVctProgramNumberSet.clear(); 193 mVctCaptionTracksFound.clear(); 194 mEitCaptionTracksFound.clear(); 195 mChannelMap.clear(); 196 } 197 198 public void startDetecting(int frequency, String modulation) { 199 reset(); 200 mFrequency = frequency; 201 mModulation = modulation; 202 } 203 204 private void startListening(int pid) { 205 if (mPidSet.contains(pid)) { 206 return; 207 } 208 mPidSet.add(pid); 209 mTunerHal.addPidFilter(pid, TunerHal.FILTER_TYPE_OTHER); 210 } 211 212 public void feedTSStream(byte[] data, int startOffset, int length) { 213 if (mPidSet.isEmpty()) { 214 startListening(TsParser.ATSC_SI_BASE_PID); 215 } 216 if (mTsParser != null) { 217 mTsParser.feedTSData(data, startOffset, length); 218 } 219 } 220 221 public List<TunerChannel> getIncompleteChannels() { 222 return mTsParser.getIncompleteChannels(); 223 } 224} 225