blob: dc99118a3be0b6e4418cc44c27cad5539ce96c48 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tv.tuner.tvinput;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.tv.tuner.TunerHal;
import com.android.tv.tuner.ts.TsParser;
import com.android.tv.tuner.data.PsiData;
import com.android.tv.tuner.data.PsipData;
import com.android.tv.tuner.data.TunerChannel;
import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Detects channels and programs that are emerged or changed while parsing ATSC PSIP information.
*/
public class EventDetector {
private static final String TAG = "EventDetector";
private static final boolean DEBUG = false;
public static final int ALL_PROGRAM_NUMBERS = -1;
private final TunerHal mTunerHal;
private TsParser mTsParser;
private final Set<Integer> mPidSet = new HashSet<>();
// To prevent channel duplication
private final Set<Integer> mVctProgramNumberSet = new HashSet<>();
private final Set<Integer> mSdtProgramNumberSet = new HashSet<>();
private final SparseArray<TunerChannel> mChannelMap = new SparseArray<>();
private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray();
private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray();
private final List<EventListener> mEventListeners = new ArrayList<>();
private int mFrequency;
private String mModulation;
private int mProgramNumber = ALL_PROGRAM_NUMBERS;
private final TsParser.TsOutputListener mTsOutputListener = new TsParser.TsOutputListener() {
@Override
public void onPatDetected(List<PsiData.PatItem> items) {
for (PsiData.PatItem i : items) {
if (mProgramNumber == ALL_PROGRAM_NUMBERS || mProgramNumber == i.getProgramNo()) {
mTunerHal.addPidFilter(i.getPmtPid(), TunerHal.FILTER_TYPE_OTHER);
}
}
}
@Override
public void onEitPidDetected(int pid) {
startListening(pid);
}
@Override
public void onEitItemParsed(PsipData.VctItem channel, List<PsipData.EitItem> items) {
TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber());
if (DEBUG) {
Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " "
+ channel.getProgramNumber());
}
int channelSourceId = channel.getSourceId();
// Source id 0 is useful for cases where a cable operator wishes to define a channel for
// which no EPG data is currently available.
// We don't handle such a case.
if (channelSourceId == 0) {
return;
}
// If at least a one caption track have been found in EIT items for the given channel,
// we starts to interpret the zero tracks as a clearance of the caption tracks.
boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId);
for (PsipData.EitItem item : items) {
if (captionTracksFound) {
break;
}
List<AtscCaptionTrack> captionTracks = item.getCaptionTracks();
if (captionTracks != null && !captionTracks.isEmpty()) {
captionTracksFound = true;
}
}
mEitCaptionTracksFound.put(channelSourceId, captionTracksFound);
if (captionTracksFound) {
for (PsipData.EitItem item : items) {
item.setHasCaptionTrack();
}
}
if (tunerChannel != null && !mEventListeners.isEmpty()) {
for (EventListener eventListener : mEventListeners) {
eventListener.onEventDetected(tunerChannel, items);
}
}
}
@Override
public void onEttPidDetected(int pid) {
startListening(pid);
}
@Override
public void onAllVctItemsParsed() {
if (!mEventListeners.isEmpty()) {
for (EventListener eventListener : mEventListeners) {
eventListener.onChannelScanDone();
}
}
}
@Override
public void onVctItemParsed(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
if (DEBUG) {
Log.d(TAG, "onVctItemParsed VCT " + channel);
Log.d(TAG, " PMT " + pmtItems);
}
// Merges the audio and caption tracks located in PMT items into the tracks of the given
// tuner channel.
TunerChannel tunerChannel = new TunerChannel(channel, pmtItems);
List<AtscAudioTrack> audioTracks = new ArrayList<>();
List<AtscCaptionTrack> captionTracks = new ArrayList<>();
for (PsiData.PmtItem pmtItem : pmtItems) {
if (pmtItem.getAudioTracks() != null) {
audioTracks.addAll(pmtItem.getAudioTracks());
}
if (pmtItem.getCaptionTracks() != null) {
captionTracks.addAll(pmtItem.getCaptionTracks());
}
}
int channelProgramNumber = channel.getProgramNumber();
// If at least a one caption track have been found in VCT items for the given channel,
// we starts to interpret the zero tracks as a clearance of the caption tracks.
boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber)
|| !captionTracks.isEmpty();
mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound);
if (captionTracksFound) {
tunerChannel.setHasCaptionTrack();
}
tunerChannel.setAudioTracks(audioTracks);
tunerChannel.setCaptionTracks(captionTracks);
tunerChannel.setFrequency(mFrequency);
tunerChannel.setModulation(mModulation);
mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
boolean found = mVctProgramNumberSet.contains(channelProgramNumber);
if (!found) {
mVctProgramNumberSet.add(channelProgramNumber);
}
if (!mEventListeners.isEmpty()) {
for (EventListener eventListener : mEventListeners) {
eventListener.onChannelDetected(tunerChannel, !found);
}
}
}
@Override
public void onSdtItemParsed(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
if (DEBUG) {
Log.d(TAG, "onSdtItemParsed SDT " + channel);
Log.d(TAG, " PMT " + pmtItems);
}
// Merges the audio and caption tracks located in PMT items into the tracks of the given
// tuner channel.
TunerChannel tunerChannel = new TunerChannel(channel, pmtItems);
List<AtscAudioTrack> audioTracks = new ArrayList<>();
List<AtscCaptionTrack> captionTracks = new ArrayList<>();
for (PsiData.PmtItem pmtItem : pmtItems) {
if (pmtItem.getAudioTracks() != null) {
audioTracks.addAll(pmtItem.getAudioTracks());
}
if (pmtItem.getCaptionTracks() != null) {
captionTracks.addAll(pmtItem.getCaptionTracks());
}
}
int channelProgramNumber = channel.getServiceId();
tunerChannel.setAudioTracks(audioTracks);
tunerChannel.setCaptionTracks(captionTracks);
tunerChannel.setFrequency(mFrequency);
tunerChannel.setModulation(mModulation);
mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
boolean found = mSdtProgramNumberSet.contains(channelProgramNumber);
if (!found) {
mSdtProgramNumberSet.add(channelProgramNumber);
}
if (!mEventListeners.isEmpty()) {
for (EventListener eventListener : mEventListeners) {
eventListener.onChannelDetected(tunerChannel, !found);
}
}
}
};
/**
* Listener for detecting ATSC TV channels and receiving EPG data.
*/
public interface EventListener {
/**
* Fired when new information of an ATSC TV channel arrived.
*
* @param channel an ATSC TV channel
* @param channelArrivedAtFirstTime tells whether this channel arrived at first time
*/
void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime);
/**
* Fired when new program events of an ATSC TV channel arrived.
*
* @param channel an ATSC TV channel
* @param items a list of EIT items that were received
*/
void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items);
/**
* Fired when information of all detectable ATSC TV channels in current frequency arrived.
*/
void onChannelScanDone();
}
/**
* Creates a detector for ATSC TV channles and program information.
*
* @param usbTunerInteface {@link TunerHal}
*/
public EventDetector(TunerHal usbTunerInteface) {
mTunerHal = usbTunerInteface;
}
private void reset() {
// TODO: Use TsParser.reset()
int deliverySystemType = mTunerHal.getDeliverySystemType();
mTsParser =
new TsParser(
mTsOutputListener,
TunerHal.isDvbDeliverySystem(mTunerHal.getDeliverySystemType()));
mPidSet.clear();
mVctProgramNumberSet.clear();
mSdtProgramNumberSet.clear();
mVctCaptionTracksFound.clear();
mEitCaptionTracksFound.clear();
mChannelMap.clear();
}
/**
* Starts detecting channel and program information.
*
* @param frequency The frequency to listen to.
* @param modulation The modulation type.
* @param programNumber The program number if this is for handling tune request. For scanning
* purpose, supply {@link #ALL_PROGRAM_NUMBERS}.
*/
public void startDetecting(int frequency, String modulation, int programNumber) {
reset();
mFrequency = frequency;
mModulation = modulation;
mProgramNumber = programNumber;
}
private void startListening(int pid) {
if (mPidSet.contains(pid)) {
return;
}
mPidSet.add(pid);
mTunerHal.addPidFilter(pid, TunerHal.FILTER_TYPE_OTHER);
}
/**
* Feeds ATSC TS stream to detect channel and program information.
* @param data buffer for ATSC TS stream
* @param startOffset the offset where buffer starts
* @param length The length of available data
*/
public void feedTSStream(byte[] data, int startOffset, int length) {
if (mPidSet.isEmpty()) {
startListening(TsParser.ATSC_SI_BASE_PID);
}
if (mTsParser != null) {
mTsParser.feedTSData(data, startOffset, length);
}
}
/**
* Retrieves the channel information regardless of being well-formed.
* @return {@link List} of {@link TunerChannel}
*/
public List<TunerChannel> getMalFormedChannels() {
return mTsParser.getMalFormedChannels();
}
/**
* Registers an EventListener.
* @param eventListener the listener to be registered
*/
public void registerListener(EventListener eventListener) {
if (mTsParser != null) {
// Resets the version numbers so that the new listener can receive the EIT items.
// Otherwise, each EIT session is handled only once unless there is a new version.
mTsParser.resetDataVersions();
}
mEventListeners.add(eventListener);
}
/**
* Unregisters an EventListener.
* @param eventListener the listener to be unregistered
*/
public void unregisterListener(EventListener eventListener) {
boolean removed = mEventListeners.remove(eventListener);
if (!removed && DEBUG) {
Log.d(TAG, "Cannot unregister a non-registered listener!");
}
}
}