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