aboutsummaryrefslogtreecommitdiff
path: root/tuner/src/com/android/tv/tuner/data/TunerChannel.java
diff options
context:
space:
mode:
Diffstat (limited to 'tuner/src/com/android/tv/tuner/data/TunerChannel.java')
-rw-r--r--tuner/src/com/android/tv/tuner/data/TunerChannel.java552
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;
+ }
+}