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