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