diff options
Diffstat (limited to 'tuner/src/com/android/tv/tuner/data/TunerChannel.java')
-rw-r--r-- | tuner/src/com/android/tv/tuner/data/TunerChannel.java | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/tuner/src/com/android/tv/tuner/data/TunerChannel.java b/tuner/src/com/android/tv/tuner/data/TunerChannel.java new file mode 100644 index 00000000..d20c343b --- /dev/null +++ b/tuner/src/com/android/tv/tuner/data/TunerChannel.java @@ -0,0 +1,552 @@ +/* + * 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.data; + +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.util.Log; +import com.android.tv.common.util.StringUtils; +import com.android.tv.tuner.data.nano.Channel; +import com.android.tv.tuner.data.nano.Channel.TunerChannelProto; +import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; +import com.android.tv.tuner.util.Ints; +import com.google.protobuf.nano.MessageNano; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** A class that represents a single channel accessible through a tuner. */ +public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracksInterface { + private static final String TAG = "TunerChannel"; + + /** Channel number separator between major number and minor number. */ + public static final char CHANNEL_NUMBER_SEPARATOR = '-'; + + // See ATSC Code Points Registry. + private static final String[] ATSC_SERVICE_TYPE_NAMES = + new String[] { + "ATSC Reserved", + "Analog television channels", + "ATSC_digital_television", + "ATSC_audio", + "ATSC_data_only_service", + "Software Download", + "Unassociated/Small Screen Service", + "Parameterized Service", + "ATSC NRT Service", + "Extended Parameterized Service" + }; + private static final String ATSC_SERVICE_TYPE_NAME_RESERVED = + ATSC_SERVICE_TYPE_NAMES[Channel.AtscServiceType.SERVICE_TYPE_ATSC_RESERVED]; + + public static final int INVALID_FREQUENCY = -1; + + // According to RFC4259, The number of available PIDs ranges from 0 to 8191. + public static final int INVALID_PID = -1; + + // According to ISO13818-1, Mpeg2 StreamType has a range from 0x00 to 0xff. + public static final int INVALID_STREAMTYPE = -1; + + // @GuardedBy(this) Writing operations and toByteArray will be guarded. b/34197766 + private final TunerChannelProto mProto; + + private TunerChannel( + PsipData.VctItem channel, int programNumber, List<PsiData.PmtItem> pmtItems, int type) { + mProto = new TunerChannelProto(); + if (channel == null) { + mProto.shortName = ""; + mProto.tsid = 0; + mProto.programNumber = programNumber; + mProto.virtualMajor = 0; + mProto.virtualMinor = 0; + } else { + mProto.shortName = channel.getShortName(); + if (channel.getLongName() != null) { + mProto.longName = channel.getLongName(); + } + mProto.tsid = channel.getChannelTsid(); + mProto.programNumber = channel.getProgramNumber(); + mProto.virtualMajor = channel.getMajorChannelNumber(); + mProto.virtualMinor = channel.getMinorChannelNumber(); + if (channel.getDescription() != null) { + mProto.description = channel.getDescription(); + } + mProto.serviceType = channel.getServiceType(); + } + initProto(pmtItems, type); + } + + private void initProto(List<PsiData.PmtItem> pmtItems, int type) { + mProto.type = type; + mProto.channelId = -1L; + mProto.frequency = INVALID_FREQUENCY; + mProto.videoPid = INVALID_PID; + mProto.videoStreamType = INVALID_STREAMTYPE; + List<Integer> audioPids = new ArrayList<>(); + List<Integer> audioStreamTypes = new ArrayList<>(); + for (PsiData.PmtItem pmt : pmtItems) { + switch (pmt.getStreamType()) { + // MPEG ES stream video types + case Channel.VideoStreamType.MPEG1: + case Channel.VideoStreamType.MPEG2: + case Channel.VideoStreamType.H263: + case Channel.VideoStreamType.H264: + case Channel.VideoStreamType.H265: + mProto.videoPid = pmt.getEsPid(); + mProto.videoStreamType = pmt.getStreamType(); + break; + + // MPEG ES stream audio types + case Channel.AudioStreamType.MPEG1AUDIO: + case Channel.AudioStreamType.MPEG2AUDIO: + case Channel.AudioStreamType.MPEG2AACAUDIO: + case Channel.AudioStreamType.MPEG4LATMAACAUDIO: + case Channel.AudioStreamType.A52AC3AUDIO: + case Channel.AudioStreamType.EAC3AUDIO: + audioPids.add(pmt.getEsPid()); + audioStreamTypes.add(pmt.getStreamType()); + break; + + // Non MPEG ES stream types + case 0x100: // PmtItem.ES_PID_PCR: + mProto.pcrPid = pmt.getEsPid(); + break; + default: + // fall out + } + } + mProto.audioPids = Ints.toArray(audioPids); + mProto.audioStreamTypes = Ints.toArray(audioStreamTypes); + mProto.audioTrackIndex = (audioPids.size() > 0) ? 0 : -1; + } + + private TunerChannel( + int programNumber, int type, PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) { + mProto = new TunerChannelProto(); + mProto.tsid = 0; + mProto.virtualMajor = 0; + mProto.virtualMinor = 0; + if (channel == null) { + mProto.shortName = ""; + mProto.programNumber = programNumber; + } else { + mProto.shortName = channel.getServiceName(); + mProto.programNumber = channel.getServiceId(); + mProto.serviceType = channel.getServiceType(); + } + initProto(pmtItems, type); + } + + /** Initialize tuner channel with VCT items and PMT items. */ + public TunerChannel(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) { + this(channel, 0, pmtItems, Channel.TunerType.TYPE_TUNER); + } + + /** Initialize tuner channel with program number and PMT items. */ + public TunerChannel(int programNumber, List<PsiData.PmtItem> pmtItems) { + this(null, programNumber, pmtItems, Channel.TunerType.TYPE_TUNER); + } + + /** Initialize tuner channel with SDT items and PMT items. */ + public TunerChannel(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) { + this(0, Channel.TunerType.TYPE_TUNER, channel, pmtItems); + } + + private TunerChannel(TunerChannelProto tunerChannelProto) { + mProto = tunerChannelProto; + } + + public static TunerChannel forFile(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) { + return new TunerChannel(channel, 0, pmtItems, Channel.TunerType.TYPE_FILE); + } + + public static TunerChannel forDvbFile( + PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) { + return new TunerChannel(0, Channel.TunerType.TYPE_FILE, channel, pmtItems); + } + + /** + * Create a TunerChannel object suitable for network tuners + * + * @param major Channel number major + * @param minor Channel number minor + * @param programNumber Program number + * @param shortName Short name + * @param recordingProhibited Recording prohibition info + * @param videoFormat Video format. Should be {@code null} or one of the followings: {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_240P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_360P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_480I}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_480P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_576I}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_576P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_720P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080I}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_2160P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_4320P} + * @return a TunerChannel object + */ + public static TunerChannel forNetwork( + int major, + int minor, + int programNumber, + String shortName, + boolean recordingProhibited, + String videoFormat) { + TunerChannel tunerChannel = + new TunerChannel( + null, + programNumber, + Collections.EMPTY_LIST, + Channel.TunerType.TYPE_NETWORK); + tunerChannel.setVirtualMajor(major); + tunerChannel.setVirtualMinor(minor); + tunerChannel.setShortName(shortName); + // Set audio and video pids in order to work around the audio-only channel check. + tunerChannel.setAudioPids(new ArrayList<>(Arrays.asList(0))); + tunerChannel.selectAudioTrack(0); + tunerChannel.setVideoPid(0); + tunerChannel.setRecordingProhibited(recordingProhibited); + if (videoFormat != null) { + tunerChannel.setVideoFormat(videoFormat); + } + return tunerChannel; + } + + public String getName() { + return (!mProto.shortName.isEmpty()) ? mProto.shortName : mProto.longName; + } + + public String getShortName() { + return mProto.shortName; + } + + public int getProgramNumber() { + return mProto.programNumber; + } + + public int getServiceType() { + return mProto.serviceType; + } + + public String getServiceTypeName() { + int serviceType = mProto.serviceType; + if (serviceType >= 0 && serviceType < ATSC_SERVICE_TYPE_NAMES.length) { + return ATSC_SERVICE_TYPE_NAMES[serviceType]; + } + return ATSC_SERVICE_TYPE_NAME_RESERVED; + } + + public int getVirtualMajor() { + return mProto.virtualMajor; + } + + public int getVirtualMinor() { + return mProto.virtualMinor; + } + + public int getFrequency() { + return mProto.frequency; + } + + public String getModulation() { + return mProto.modulation; + } + + public int getTsid() { + return mProto.tsid; + } + + public int getVideoPid() { + return mProto.videoPid; + } + + public synchronized void setVideoPid(int videoPid) { + mProto.videoPid = videoPid; + } + + public int getVideoStreamType() { + return mProto.videoStreamType; + } + + public int getAudioPid() { + if (mProto.audioTrackIndex == -1) { + return INVALID_PID; + } + return mProto.audioPids[mProto.audioTrackIndex]; + } + + public int getAudioStreamType() { + if (mProto.audioTrackIndex == -1) { + return INVALID_STREAMTYPE; + } + return mProto.audioStreamTypes[mProto.audioTrackIndex]; + } + + public List<Integer> getAudioPids() { + return Ints.asList(mProto.audioPids); + } + + public synchronized void setAudioPids(List<Integer> audioPids) { + mProto.audioPids = Ints.toArray(audioPids); + } + + public List<Integer> getAudioStreamTypes() { + return Ints.asList(mProto.audioStreamTypes); + } + + public synchronized void setAudioStreamTypes(List<Integer> audioStreamTypes) { + mProto.audioStreamTypes = Ints.toArray(audioStreamTypes); + } + + public int getPcrPid() { + return mProto.pcrPid; + } + + public int getType() { + return mProto.type; + } + + public synchronized void setFilepath(String filepath) { + mProto.filepath = filepath == null ? "" : filepath; + } + + public String getFilepath() { + return mProto.filepath; + } + + public synchronized void setVirtualMajor(int virtualMajor) { + mProto.virtualMajor = virtualMajor; + } + + public synchronized void setVirtualMinor(int virtualMinor) { + mProto.virtualMinor = virtualMinor; + } + + public synchronized void setShortName(String shortName) { + mProto.shortName = shortName == null ? "" : shortName; + } + + public synchronized void setFrequency(int frequency) { + mProto.frequency = frequency; + } + + public synchronized void setModulation(String modulation) { + mProto.modulation = modulation == null ? "" : modulation; + } + + public boolean hasVideo() { + return mProto.videoPid != INVALID_PID; + } + + public boolean hasAudio() { + return getAudioPid() != INVALID_PID; + } + + public long getChannelId() { + return mProto.channelId; + } + + public synchronized void setChannelId(long channelId) { + mProto.channelId = channelId; + } + + /** + * The flag indicating whether this TV channel is locked or not. This is primarily used for + * alternative parental control to prevent unauthorized users from watching the current channel + * regardless of the content rating + * + * @see <a + * href="https://developer.android.com/reference/android/media/tv/TvContract.Channels.html#COLUMN_LOCKED">link</a> + */ + public boolean isLocked() { + return mProto.locked; + } + + public synchronized void setLocked(boolean locked) { + mProto.locked = locked; + } + + public String getDisplayNumber() { + return getDisplayNumber(true); + } + + public String getDisplayNumber(boolean ignoreZeroMinorNumber) { + if (mProto.virtualMajor != 0 && (mProto.virtualMinor != 0 || !ignoreZeroMinorNumber)) { + return String.format( + "%d%c%d", mProto.virtualMajor, CHANNEL_NUMBER_SEPARATOR, mProto.virtualMinor); + } else if (mProto.virtualMajor != 0) { + return Integer.toString(mProto.virtualMajor); + } else { + return Integer.toString(mProto.programNumber); + } + } + + public String getDescription() { + return mProto.description; + } + + @Override + public synchronized void setHasCaptionTrack() { + mProto.hasCaptionTrack = true; + } + + @Override + public boolean hasCaptionTrack() { + return mProto.hasCaptionTrack; + } + + @Override + public List<AtscAudioTrack> getAudioTracks() { + return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks)); + } + + public synchronized void setAudioTracks(List<AtscAudioTrack> audioTracks) { + mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]); + } + + @Override + public List<AtscCaptionTrack> getCaptionTracks() { + return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks)); + } + + public synchronized void setCaptionTracks(List<AtscCaptionTrack> captionTracks) { + mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]); + } + + public synchronized void selectAudioTrack(int index) { + if (0 <= index && index < mProto.audioPids.length) { + mProto.audioTrackIndex = index; + } else { + mProto.audioTrackIndex = -1; + } + } + + public synchronized void setRecordingProhibited(boolean recordingProhibited) { + mProto.recordingProhibited = recordingProhibited; + } + + public boolean isRecordingProhibited() { + return mProto.recordingProhibited; + } + + public synchronized void setVideoFormat(String videoFormat) { + mProto.videoFormat = videoFormat == null ? "" : videoFormat; + } + + public String getVideoFormat() { + return mProto.videoFormat; + } + + @Override + public String toString() { + switch (mProto.type) { + case Channel.TunerType.TYPE_FILE: + return String.format( + "{%d-%d %s} Filepath: %s, ProgramNumber %d", + mProto.virtualMajor, + mProto.virtualMinor, + mProto.shortName, + mProto.filepath, + mProto.programNumber); + // case Channel.TunerType.TYPE_TUNER: + default: + return String.format( + "{%d-%d %s} Frequency: %d, ProgramNumber %d", + mProto.virtualMajor, + mProto.virtualMinor, + mProto.shortName, + mProto.frequency, + mProto.programNumber); + } + } + + @Override + public int compareTo(@NonNull TunerChannel channel) { + // In the same frequency, the program number acts as the sub-channel number. + int ret = getFrequency() - channel.getFrequency(); + if (ret != 0) { + return ret; + } + ret = getProgramNumber() - channel.getProgramNumber(); + if (ret != 0) { + return ret; + } + ret = StringUtils.compare(getName(), channel.getName()); + if (ret != 0) { + return ret; + } + // For FileTsStreamer, file paths should be compared. + return StringUtils.compare(getFilepath(), channel.getFilepath()); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TunerChannel)) { + return false; + } + return compareTo((TunerChannel) o) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(getFrequency(), getProgramNumber(), getName(), getFilepath()); + } + + // Serialization + public synchronized byte[] toByteArray() { + try { + return MessageNano.toByteArray(mProto); + } catch (Exception e) { + // Retry toByteArray. b/34197766 + Log.w( + TAG, + "TunerChannel or its variables are modified in multiple thread without lock", + e); + return MessageNano.toByteArray(mProto); + } + } + + public static TunerChannel parseFrom(byte[] data) { + if (data == null) { + return null; + } + try { + return new TunerChannel(TunerChannelProto.parseFrom(data)); + } catch (IOException e) { + Log.e(TAG, "Could not parse from byte array", e); + return null; + } + } + + public static TunerChannel fromCursor(Cursor cursor) { + long channelId = cursor.getLong(0); + boolean locked = cursor.getInt(1) > 0; + byte[] data = cursor.getBlob(2); + TunerChannel channel = TunerChannel.parseFrom(data); + if (channel != null) { + channel.setChannelId(channelId); + channel.setLocked(locked); + } + return channel; + } +} |