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