diff options
Diffstat (limited to 'src/com/android/tv/tuner/data')
-rw-r--r-- | src/com/android/tv/tuner/data/Cea708Data.java | 329 | ||||
-rw-r--r-- | src/com/android/tv/tuner/data/PsiData.java | 93 | ||||
-rw-r--r-- | src/com/android/tv/tuner/data/PsipData.java | 871 | ||||
-rw-r--r-- | src/com/android/tv/tuner/data/TunerChannel.java | 517 |
4 files changed, 1810 insertions, 0 deletions
diff --git a/src/com/android/tv/tuner/data/Cea708Data.java b/src/com/android/tv/tuner/data/Cea708Data.java new file mode 100644 index 00000000..73a90181 --- /dev/null +++ b/src/com/android/tv/tuner/data/Cea708Data.java @@ -0,0 +1,329 @@ +/* + * 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.graphics.Color; +import android.support.annotation.NonNull; +import com.android.tv.tuner.cc.Cea708Parser; + +/** Collection of CEA-708 structures. */ +public class Cea708Data { + + private Cea708Data() {} + + // According to CEA-708B, the range of valid service number is between 1 and 63. + public static final int EMPTY_SERVICE_NUMBER = 0; + + // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6. + public static final int CODE_C0_RANGE_START = 0x00; + public static final int CODE_C0_RANGE_END = 0x1f; + public static final int CODE_C1_RANGE_START = 0x80; + public static final int CODE_C1_RANGE_END = 0x9f; + public static final int CODE_G0_RANGE_START = 0x20; + public static final int CODE_G0_RANGE_END = 0x7f; + public static final int CODE_G1_RANGE_START = 0xa0; + public static final int CODE_G1_RANGE_END = 0xff; + public static final int CODE_C2_RANGE_START = 0x00; + public static final int CODE_C2_RANGE_END = 0x1f; + public static final int CODE_C3_RANGE_START = 0x80; + public static final int CODE_C3_RANGE_END = 0x9f; + public static final int CODE_G2_RANGE_START = 0x20; + public static final int CODE_G2_RANGE_END = 0x7f; + public static final int CODE_G3_RANGE_START = 0xa0; + public static final int CODE_G3_RANGE_END = 0xff; + + // The following ranges are defined in CEA-708B Section 7.4.1. + public static final int CODE_C0_SKIP2_RANGE_START = 0x18; + public static final int CODE_C0_SKIP2_RANGE_END = 0x1f; + public static final int CODE_C0_SKIP1_RANGE_START = 0x10; + public static final int CODE_C0_SKIP1_RANGE_END = 0x17; + + // The following ranges are defined in CEA-708B Section 7.4.7. + public static final int CODE_C2_SKIP0_RANGE_START = 0x00; + public static final int CODE_C2_SKIP0_RANGE_END = 0x07; + public static final int CODE_C2_SKIP1_RANGE_START = 0x08; + public static final int CODE_C2_SKIP1_RANGE_END = 0x0f; + public static final int CODE_C2_SKIP2_RANGE_START = 0x10; + public static final int CODE_C2_SKIP2_RANGE_END = 0x17; + public static final int CODE_C2_SKIP3_RANGE_START = 0x18; + public static final int CODE_C2_SKIP3_RANGE_END = 0x1f; + + // The following ranges are defined in CEA-708B Section 7.4.8. + public static final int CODE_C3_SKIP4_RANGE_START = 0x80; + public static final int CODE_C3_SKIP4_RANGE_END = 0x87; + public static final int CODE_C3_SKIP5_RANGE_START = 0x88; + public static final int CODE_C3_SKIP5_RANGE_END = 0x8f; + + // The following values are the special characters of CEA-708 spec. + public static final int CODE_C0_NUL = 0x00; + public static final int CODE_C0_ETX = 0x03; + public static final int CODE_C0_BS = 0x08; + public static final int CODE_C0_FF = 0x0c; + public static final int CODE_C0_CR = 0x0d; + public static final int CODE_C0_HCR = 0x0e; + public static final int CODE_C0_EXT1 = 0x10; + public static final int CODE_C0_P16 = 0x18; + public static final int CODE_G0_MUSICNOTE = 0x7f; + public static final int CODE_G2_TSP = 0x20; + public static final int CODE_G2_NBTSP = 0x21; + public static final int CODE_G2_BLK = 0x30; + public static final int CODE_G3_CC = 0xa0; + + // The following values are the command bits of CEA-708 spec. + public static final int CODE_C1_CW0 = 0x80; + public static final int CODE_C1_CW1 = 0x81; + public static final int CODE_C1_CW2 = 0x82; + public static final int CODE_C1_CW3 = 0x83; + public static final int CODE_C1_CW4 = 0x84; + public static final int CODE_C1_CW5 = 0x85; + public static final int CODE_C1_CW6 = 0x86; + public static final int CODE_C1_CW7 = 0x87; + public static final int CODE_C1_CLW = 0x88; + public static final int CODE_C1_DSW = 0x89; + public static final int CODE_C1_HDW = 0x8a; + public static final int CODE_C1_TGW = 0x8b; + public static final int CODE_C1_DLW = 0x8c; + public static final int CODE_C1_DLY = 0x8d; + public static final int CODE_C1_DLC = 0x8e; + public static final int CODE_C1_RST = 0x8f; + public static final int CODE_C1_SPA = 0x90; + public static final int CODE_C1_SPC = 0x91; + public static final int CODE_C1_SPL = 0x92; + public static final int CODE_C1_SWA = 0x97; + public static final int CODE_C1_DF0 = 0x98; + public static final int CODE_C1_DF1 = 0x99; + public static final int CODE_C1_DF2 = 0x9a; + public static final int CODE_C1_DF3 = 0x9b; + public static final int CODE_C1_DF4 = 0x9c; + public static final int CODE_C1_DF5 = 0x9d; + public static final int CODE_C1_DF6 = 0x9e; + public static final int CODE_C1_DF7 = 0x9f; + + public static class CcPacket implements Comparable<CcPacket> { + public final byte[] bytes; + public final int ccCount; + public final long pts; + + public CcPacket(byte[] bytes, int ccCount, long pts) { + this.bytes = bytes; + this.ccCount = ccCount; + this.pts = pts; + } + + @Override + public int compareTo(@NonNull CcPacket another) { + return Long.compare(pts, another.pts); + } + } + + /** CEA-708B-specific color. */ + public static class CaptionColor { + public static final int OPACITY_SOLID = 0; + public static final int OPACITY_FLASH = 1; + public static final int OPACITY_TRANSLUCENT = 2; + public static final int OPACITY_TRANSPARENT = 3; + + private static final int[] COLOR_MAP = new int[] {0x00, 0x0f, 0xf0, 0xff}; + private static final int[] OPACITY_MAP = new int[] {0xff, 0xfe, 0x80, 0x00}; + + public final int opacity; + public final int red; + public final int green; + public final int blue; + + public CaptionColor(int opacity, int red, int green, int blue) { + this.opacity = opacity; + this.red = red; + this.green = green; + this.blue = blue; + } + + public int getArgbValue() { + return Color.argb( + OPACITY_MAP[opacity], COLOR_MAP[red], COLOR_MAP[green], COLOR_MAP[blue]); + } + } + + /** Caption event generated by {@link Cea708Parser}. */ + public static class CaptionEvent { + @Cea708Parser.CaptionEmitType public final int type; + public final Object obj; + + public CaptionEvent(int type, Object obj) { + this.type = type; + this.obj = obj; + } + } + + /** Pen style information. */ + public static class CaptionPenAttr { + // Pen sizes + public static final int PEN_SIZE_SMALL = 0; + public static final int PEN_SIZE_STANDARD = 1; + public static final int PEN_SIZE_LARGE = 2; + + // Offsets + public static final int OFFSET_SUBSCRIPT = 0; + public static final int OFFSET_NORMAL = 1; + public static final int OFFSET_SUPERSCRIPT = 2; + + public final int penSize; + public final int penOffset; + public final int textTag; + public final int fontTag; + public final int edgeType; + public final boolean underline; + public final boolean italic; + + public CaptionPenAttr( + int penSize, + int penOffset, + int textTag, + int fontTag, + int edgeType, + boolean underline, + boolean italic) { + this.penSize = penSize; + this.penOffset = penOffset; + this.textTag = textTag; + this.fontTag = fontTag; + this.edgeType = edgeType; + this.underline = underline; + this.italic = italic; + } + } + + /** + * {@link CaptionColor} objects that indicate the foreground, background, and edge color of a + * pen. + */ + public static class CaptionPenColor { + public final CaptionColor foregroundColor; + public final CaptionColor backgroundColor; + public final CaptionColor edgeColor; + + public CaptionPenColor( + CaptionColor foregroundColor, + CaptionColor backgroundColor, + CaptionColor edgeColor) { + this.foregroundColor = foregroundColor; + this.backgroundColor = backgroundColor; + this.edgeColor = edgeColor; + } + } + + /** Location information of a pen. */ + public static class CaptionPenLocation { + public final int row; + public final int column; + + public CaptionPenLocation(int row, int column) { + this.row = row; + this.column = column; + } + } + + /** Attributes of a caption window, which is defined in CEA-708B. */ + public static class CaptionWindowAttr { + public static final int JUSTIFY_LEFT = 0; + public static final int JUSTIFY_CENTER = 2; + public static final int PRINT_LEFT_TO_RIGHT = 0; + public static final int PRINT_RIGHT_TO_LEFT = 1; + public static final int PRINT_TOP_TO_BOTTOM = 2; + public static final int PRINT_BOTTOM_TO_TOP = 3; + + public final CaptionColor fillColor; + public final CaptionColor borderColor; + public final int borderType; + public final boolean wordWrap; + public final int printDirection; + public final int scrollDirection; + public final int justify; + public final int effectDirection; + public final int effectSpeed; + public final int displayEffect; + + public CaptionWindowAttr( + CaptionColor fillColor, + CaptionColor borderColor, + int borderType, + boolean wordWrap, + int printDirection, + int scrollDirection, + int justify, + int effectDirection, + int effectSpeed, + int displayEffect) { + this.fillColor = fillColor; + this.borderColor = borderColor; + this.borderType = borderType; + this.wordWrap = wordWrap; + this.printDirection = printDirection; + this.scrollDirection = scrollDirection; + this.justify = justify; + this.effectDirection = effectDirection; + this.effectSpeed = effectSpeed; + this.displayEffect = displayEffect; + } + } + + /** Construction information of the caption window of CEA-708B. */ + public static class CaptionWindow { + public final int id; + public final boolean visible; + public final boolean rowLock; + public final boolean columnLock; + public final int priority; + public final boolean relativePositioning; + public final int anchorVertical; + public final int anchorHorizontal; + public final int anchorId; + public final int rowCount; + public final int columnCount; + public final int penStyle; + public final int windowStyle; + + public CaptionWindow( + int id, + boolean visible, + boolean rowLock, + boolean columnLock, + int priority, + boolean relativePositioning, + int anchorVertical, + int anchorHorizontal, + int anchorId, + int rowCount, + int columnCount, + int penStyle, + int windowStyle) { + this.id = id; + this.visible = visible; + this.rowLock = rowLock; + this.columnLock = columnLock; + this.priority = priority; + this.relativePositioning = relativePositioning; + this.anchorVertical = anchorVertical; + this.anchorHorizontal = anchorHorizontal; + this.anchorId = anchorId; + this.rowCount = rowCount; + this.columnCount = columnCount; + this.penStyle = penStyle; + this.windowStyle = windowStyle; + } + } +} diff --git a/src/com/android/tv/tuner/data/PsiData.java b/src/com/android/tv/tuner/data/PsiData.java new file mode 100644 index 00000000..9b7c2e2c --- /dev/null +++ b/src/com/android/tv/tuner/data/PsiData.java @@ -0,0 +1,93 @@ +/* + * 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 com.android.tv.tuner.data.nano.Track.AtscAudioTrack; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; +import java.util.List; + +/** Collection of MPEG PSI table items. */ +public class PsiData { + + private PsiData() {} + + public static class PatItem { + private final int mProgramNo; + private final int mPmtPid; + + public PatItem(int programNo, int pmtPid) { + mProgramNo = programNo; + mPmtPid = pmtPid; + } + + public int getProgramNo() { + return mProgramNo; + } + + public int getPmtPid() { + return mPmtPid; + } + + @Override + public String toString() { + return String.format("Program No: %x PMT Pid: %x", mProgramNo, mPmtPid); + } + } + + public static class PmtItem { + public static final int ES_PID_PCR = 0x100; + + private final int mStreamType; + private final int mEsPid; + private final List<AtscAudioTrack> mAudioTracks; + private final List<AtscCaptionTrack> mCaptionTracks; + + public PmtItem( + int streamType, + int esPid, + List<AtscAudioTrack> audioTracks, + List<AtscCaptionTrack> captionTracks) { + mStreamType = streamType; + mEsPid = esPid; + mAudioTracks = audioTracks; + mCaptionTracks = captionTracks; + } + + public int getStreamType() { + return mStreamType; + } + + public int getEsPid() { + return mEsPid; + } + + public List<AtscAudioTrack> getAudioTracks() { + return mAudioTracks; + } + + public List<AtscCaptionTrack> getCaptionTracks() { + return mCaptionTracks; + } + + @Override + public String toString() { + return String.format( + "Stream Type: %x ES Pid: %x AudioTracks: %s CaptionTracks: %s", + mStreamType, mEsPid, mAudioTracks, mCaptionTracks); + } + } +} diff --git a/src/com/android/tv/tuner/data/PsipData.java b/src/com/android/tv/tuner/data/PsipData.java new file mode 100644 index 00000000..6459004c --- /dev/null +++ b/src/com/android/tv/tuner/data/PsipData.java @@ -0,0 +1,871 @@ +/* + * 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.support.annotation.NonNull; +import android.text.TextUtils; +import android.text.format.DateUtils; +import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; +import com.android.tv.tuner.ts.SectionParser; +import com.android.tv.tuner.util.ConvertUtils; +import com.android.tv.util.StringUtils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +/** Collection of ATSC PSIP table items. */ +public class PsipData { + + private PsipData() {} + + public static class PsipSection { + private final int mTableId; + private final int mTableIdExtension; + private final int mSectionNumber; + private final boolean mCurrentNextIndicator; + + public static PsipSection create(byte[] data) { + if (data.length < 9) { + return null; + } + int tableId = data[0] & 0xff; + int tableIdExtension = (data[3] & 0xff) << 8 | (data[4] & 0xff); + int sectionNumber = data[6] & 0xff; + boolean currentNextIndicator = (data[5] & 0x01) != 0; + return new PsipSection(tableId, tableIdExtension, sectionNumber, currentNextIndicator); + } + + private PsipSection( + int tableId, + int tableIdExtension, + int sectionNumber, + boolean currentNextIndicator) { + mTableId = tableId; + mTableIdExtension = tableIdExtension; + mSectionNumber = sectionNumber; + mCurrentNextIndicator = currentNextIndicator; + } + + public int getTableId() { + return mTableId; + } + + public int getTableIdExtension() { + return mTableIdExtension; + } + + public int getSectionNumber() { + return mSectionNumber; + } + + // This is for indicating that the section sent is applicable. + // We only consider a situation where currentNextIndicator is expected to have a true value. + // So, we are not going to compare this variable in hashCode() and equals() methods. + public boolean getCurrentNextIndicator() { + return mCurrentNextIndicator; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + mTableId; + result = 31 * result + mTableIdExtension; + result = 31 * result + mSectionNumber; + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PsipSection) { + PsipSection another = (PsipSection) obj; + return mTableId == another.getTableId() + && mTableIdExtension == another.getTableIdExtension() + && mSectionNumber == another.getSectionNumber(); + } + return false; + } + } + + /** {@link TvTracksInterface} for serving the audio and caption tracks. */ + public interface TvTracksInterface { + /** Set the flag that tells the caption tracks have been found in this section container. */ + void setHasCaptionTrack(); + + /** + * Returns whether or not the caption tracks have been found in this section container. If + * true, zero caption track will be interpreted as a clearance of the caption tracks. + */ + boolean hasCaptionTrack(); + + /** Returns the audio tracks received. */ + List<AtscAudioTrack> getAudioTracks(); + + /** Returns the caption tracks received. */ + List<AtscCaptionTrack> getCaptionTracks(); + } + + public static class MgtItem { + public static final int TABLE_TYPE_EIT_RANGE_START = 0x0100; + public static final int TABLE_TYPE_EIT_RANGE_END = 0x017f; + public static final int TABLE_TYPE_CHANNEL_ETT = 0x0004; + public static final int TABLE_TYPE_ETT_RANGE_START = 0x0200; + public static final int TABLE_TYPE_ETT_RANGE_END = 0x027f; + + private final int mTableType; + private final int mTableTypePid; + + public MgtItem(int tableType, int tableTypePid) { + mTableType = tableType; + mTableTypePid = tableTypePid; + } + + public int getTableType() { + return mTableType; + } + + public int getTableTypePid() { + return mTableTypePid; + } + } + + public static class VctItem { + private final String mShortName; + private final String mLongName; + private final int mServiceType; + private final int mChannelTsid; + private final int mProgramNumber; + private final int mMajorChannelNumber; + private final int mMinorChannelNumber; + private final int mSourceId; + private String mDescription; + + public VctItem( + String shortName, + String longName, + int serviceType, + int channelTsid, + int programNumber, + int majorChannelNumber, + int minorChannelNumber, + int sourceId) { + mShortName = shortName; + mLongName = longName; + mServiceType = serviceType; + mChannelTsid = channelTsid; + mProgramNumber = programNumber; + mMajorChannelNumber = majorChannelNumber; + mMinorChannelNumber = minorChannelNumber; + mSourceId = sourceId; + } + + public String getShortName() { + return mShortName; + } + + public String getLongName() { + return mLongName; + } + + public int getServiceType() { + return mServiceType; + } + + public int getChannelTsid() { + return mChannelTsid; + } + + public int getProgramNumber() { + return mProgramNumber; + } + + public int getMajorChannelNumber() { + return mMajorChannelNumber; + } + + public int getMinorChannelNumber() { + return mMinorChannelNumber; + } + + public int getSourceId() { + return mSourceId; + } + + @Override + public String toString() { + return String.format( + Locale.US, + "ShortName: %s LongName: %s ServiceType: %d ChannelTsid: %x " + + "ProgramNumber:%d %d-%d SourceId: %x", + mShortName, + mLongName, + mServiceType, + mChannelTsid, + mProgramNumber, + mMajorChannelNumber, + mMinorChannelNumber, + mSourceId); + } + + public void setDescription(String description) { + mDescription = description; + } + + public String getDescription() { + return mDescription; + } + } + + public static class SdtItem { + private final String mServiceName; + private final String mServiceProviderName; + private final int mServiceType; + private final int mServiceId; + private final int mOriginalNetWorkId; + + public SdtItem( + String serviceName, + String serviceProviderName, + int serviceType, + int serviceId, + int originalNetWorkId) { + mServiceName = serviceName; + mServiceProviderName = serviceProviderName; + mServiceType = serviceType; + mServiceId = serviceId; + mOriginalNetWorkId = originalNetWorkId; + } + + public String getServiceName() { + return mServiceName; + } + + public String getServiceProviderName() { + return mServiceProviderName; + } + + public int getServiceType() { + return mServiceType; + } + + public int getServiceId() { + return mServiceId; + } + + public int getOriginalNetworkId() { + return mOriginalNetWorkId; + } + + @Override + public String toString() { + return String.format( + "ServiceName: %s ServiceProviderName:%s ServiceType:%d " + + "OriginalNetworkId:%d", + mServiceName, mServiceProviderName, mServiceType, mOriginalNetWorkId); + } + } + + /** A base class for descriptors of Ts packets. */ + public abstract static class TsDescriptor { + public abstract int getTag(); + } + + public static class ContentAdvisoryDescriptor extends TsDescriptor { + private final List<RatingRegion> mRatingRegions; + + public ContentAdvisoryDescriptor(List<RatingRegion> ratingRegions) { + mRatingRegions = ratingRegions; + } + + @Override + public int getTag() { + return SectionParser.DESCRIPTOR_TAG_CONTENT_ADVISORY; + } + + public List<RatingRegion> getRatingRegions() { + return mRatingRegions; + } + } + + public static class CaptionServiceDescriptor extends TsDescriptor { + private final List<AtscCaptionTrack> mCaptionTracks; + + public CaptionServiceDescriptor(List<AtscCaptionTrack> captionTracks) { + mCaptionTracks = captionTracks; + } + + @Override + public int getTag() { + return SectionParser.DESCRIPTOR_TAG_CAPTION_SERVICE; + } + + public List<AtscCaptionTrack> getCaptionTracks() { + return mCaptionTracks; + } + } + + public static class ExtendedChannelNameDescriptor extends TsDescriptor { + private final String mLongChannelName; + + public ExtendedChannelNameDescriptor(String longChannelName) { + mLongChannelName = longChannelName; + } + + @Override + public int getTag() { + return SectionParser.DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME; + } + + public String getLongChannelName() { + return mLongChannelName; + } + } + + public static class GenreDescriptor extends TsDescriptor { + private final String[] mBroadcastGenres; + private final String[] mCanonicalGenres; + + public GenreDescriptor(String[] broadcastGenres, String[] canonicalGenres) { + mBroadcastGenres = broadcastGenres; + mCanonicalGenres = canonicalGenres; + } + + @Override + public int getTag() { + return SectionParser.DESCRIPTOR_TAG_GENRE; + } + + public String[] getBroadcastGenres() { + return mBroadcastGenres; + } + + public String[] getCanonicalGenres() { + return mCanonicalGenres; + } + } + + public static class Ac3AudioDescriptor extends TsDescriptor { + // See A/52 Annex A. Table A4.2 + private static final byte SAMPLE_RATE_CODE_48000HZ = 0; + private static final byte SAMPLE_RATE_CODE_44100HZ = 1; + private static final byte SAMPLE_RATE_CODE_32000HZ = 2; + + private final byte mSampleRateCode; + private final byte mBsid; + private final byte mBitRateCode; + private final byte mSurroundMode; + private final byte mBsmod; + private final int mNumChannels; + private final boolean mFullSvc; + private final byte mLangCod; + private final byte mLangCod2; + private final byte mMainId; + private final byte mPriority; + private final byte mAsvcflags; + private final String mText; + private final String mLanguage; + private final String mLanguage2; + + public Ac3AudioDescriptor( + byte sampleRateCode, + byte bsid, + byte bitRateCode, + byte surroundMode, + byte bsmod, + int numChannels, + boolean fullSvc, + byte langCod, + byte langCod2, + byte mainId, + byte priority, + byte asvcflags, + String text, + String language, + String language2) { + mSampleRateCode = sampleRateCode; + mBsid = bsid; + mBitRateCode = bitRateCode; + mSurroundMode = surroundMode; + mBsmod = bsmod; + mNumChannels = numChannels; + mFullSvc = fullSvc; + mLangCod = langCod; + mLangCod2 = langCod2; + mMainId = mainId; + mPriority = priority; + mAsvcflags = asvcflags; + mText = text; + mLanguage = language; + mLanguage2 = language2; + } + + @Override + public int getTag() { + return SectionParser.DESCRIPTOR_TAG_AC3_AUDIO_STREAM; + } + + public byte getSampleRateCode() { + return mSampleRateCode; + } + + public int getSampleRate() { + switch (mSampleRateCode) { + case SAMPLE_RATE_CODE_48000HZ: + return 48000; + case SAMPLE_RATE_CODE_44100HZ: + return 44100; + case SAMPLE_RATE_CODE_32000HZ: + return 32000; + default: + return 0; + } + } + + public byte getBsid() { + return mBsid; + } + + public byte getBitRateCode() { + return mBitRateCode; + } + + public byte getSurroundMode() { + return mSurroundMode; + } + + public byte getBsmod() { + return mBsmod; + } + + public int getNumChannels() { + return mNumChannels; + } + + public boolean isFullSvc() { + return mFullSvc; + } + + public byte getLangCod() { + return mLangCod; + } + + public byte getLangCod2() { + return mLangCod2; + } + + public byte getMainId() { + return mMainId; + } + + public byte getPriority() { + return mPriority; + } + + public byte getAsvcflags() { + return mAsvcflags; + } + + public String getText() { + return mText; + } + + public String getLanguage() { + return mLanguage; + } + + public String getLanguage2() { + return mLanguage2; + } + + @Override + public String toString() { + return String.format( + Locale.US, + "AC3 audio stream sampleRateCode: %d, bsid: %d, bitRateCode: %d, " + + "surroundMode: %d, bsmod: %d, numChannels: %d, fullSvc: %s, langCod: %d, " + + "langCod2: %d, mainId: %d, priority: %d, avcflags: %d, text: %s, language: %s" + + ", language2: %s", + mSampleRateCode, + mBsid, + mBitRateCode, + mSurroundMode, + mBsmod, + mNumChannels, + mFullSvc, + mLangCod, + mLangCod2, + mMainId, + mPriority, + mAsvcflags, + mText, + mLanguage, + mLanguage2); + } + } + + public static class Iso639LanguageDescriptor extends TsDescriptor { + private final List<AtscAudioTrack> mAudioTracks; + + public Iso639LanguageDescriptor(List<AtscAudioTrack> audioTracks) { + mAudioTracks = audioTracks; + } + + @Override + public int getTag() { + return SectionParser.DESCRIPTOR_TAG_ISO639LANGUAGE; + } + + public List<AtscAudioTrack> getAudioTracks() { + return mAudioTracks; + } + + @Override + public String toString() { + return String.format("%s %s", getClass().getName(), mAudioTracks); + } + } + + public static class ServiceDescriptor extends TsDescriptor { + private final int mServiceType; + private final String mServiceProviderName; + private final String mServiceName; + + public ServiceDescriptor(int serviceType, String serviceProviderName, String serviceName) { + mServiceType = serviceType; + mServiceProviderName = serviceProviderName; + mServiceName = serviceName; + } + + @Override + public int getTag() { + return SectionParser.DVB_DESCRIPTOR_TAG_SERVICE; + } + + public int getServiceType() { + return mServiceType; + } + + public String getServiceProviderName() { + return mServiceProviderName; + } + + public String getServiceName() { + return mServiceName; + } + + @Override + public String toString() { + return String.format( + "Service descriptor, service type: %d, " + + "service provider name: %s, " + + "service name: %s", + mServiceType, mServiceProviderName, mServiceName); + } + } + + public static class ShortEventDescriptor extends TsDescriptor { + private final String mLanguage; + private final String mEventName; + private final String mText; + + public ShortEventDescriptor(String language, String eventName, String text) { + mLanguage = language; + mEventName = eventName; + mText = text; + } + + public String getEventName() { + return mEventName; + } + + @Override + public int getTag() { + return SectionParser.DVB_DESCRIPTOR_TAG_SHORT_EVENT; + } + + @Override + public String toString() { + return String.format( + "ShortEvent Descriptor, language:%s, event name: %s, " + "text:%s", + mLanguage, mEventName, mText); + } + } + + public static class ParentalRatingDescriptor extends TsDescriptor { + private final HashMap<String, Integer> mRatings; + + public ParentalRatingDescriptor(HashMap<String, Integer> ratings) { + mRatings = ratings; + } + + @Override + public int getTag() { + return SectionParser.DVB_DESCRIPTOR_TAG_PARENTAL_RATING; + } + + public HashMap<String, Integer> getRatings() { + return mRatings; + } + + @Override + public String toString() { + return String.format("Parental rating descriptor, ratings:" + mRatings); + } + } + + public static class RatingRegion { + private final int mName; + private final String mDescription; + private final List<RegionalRating> mRegionalRatings; + + public RatingRegion(int name, String description, List<RegionalRating> regionalRatings) { + mName = name; + mDescription = description; + mRegionalRatings = regionalRatings; + } + + public int getName() { + return mName; + } + + public String getDescription() { + return mDescription; + } + + public List<RegionalRating> getRegionalRatings() { + return mRegionalRatings; + } + } + + public static class RegionalRating { + private final int mDimension; + private final int mRating; + + public RegionalRating(int dimension, int rating) { + mDimension = dimension; + mRating = rating; + } + + public int getDimension() { + return mDimension; + } + + public int getRating() { + return mRating; + } + } + + public static class EitItem implements Comparable<EitItem>, TvTracksInterface { + public static final long INVALID_PROGRAM_ID = -1; + + // A program id is a primary key of TvContract.Programs table. So it must be positive. + private final long mProgramId; + private final int mEventId; + private final String mTitleText; + private String mDescription; + private final long mStartTime; + private final int mLengthInSecond; + private final String mContentRating; + private final List<AtscAudioTrack> mAudioTracks; + private final List<AtscCaptionTrack> mCaptionTracks; + private boolean mHasCaptionTrack; + private final String mBroadcastGenre; + private final String mCanonicalGenre; + + public EitItem( + long programId, + int eventId, + String titleText, + long startTime, + int lengthInSecond, + String contentRating, + List<AtscAudioTrack> audioTracks, + List<AtscCaptionTrack> captionTracks, + String broadcastGenre, + String canonicalGenre, + String description) { + mProgramId = programId; + mEventId = eventId; + mTitleText = titleText; + mStartTime = startTime; + mLengthInSecond = lengthInSecond; + mContentRating = contentRating; + mAudioTracks = audioTracks; + mCaptionTracks = captionTracks; + mBroadcastGenre = broadcastGenre; + mCanonicalGenre = canonicalGenre; + mDescription = description; + } + + public long getProgramId() { + return mProgramId; + } + + public int getEventId() { + return mEventId; + } + + public String getTitleText() { + return mTitleText; + } + + public void setDescription(String description) { + mDescription = description; + } + + public String getDescription() { + return mDescription; + } + + public long getStartTime() { + return mStartTime; + } + + public int getLengthInSecond() { + return mLengthInSecond; + } + + public long getStartTimeUtcMillis() { + return ConvertUtils.convertGPSTimeToUnixEpoch(mStartTime) * DateUtils.SECOND_IN_MILLIS; + } + + public long getEndTimeUtcMillis() { + return ConvertUtils.convertGPSTimeToUnixEpoch(mStartTime + mLengthInSecond) + * DateUtils.SECOND_IN_MILLIS; + } + + public String getContentRating() { + return mContentRating; + } + + @Override + public List<AtscAudioTrack> getAudioTracks() { + return mAudioTracks; + } + + @Override + public List<AtscCaptionTrack> getCaptionTracks() { + return mCaptionTracks; + } + + public String getBroadcastGenre() { + return mBroadcastGenre; + } + + public String getCanonicalGenre() { + return mCanonicalGenre; + } + + @Override + public void setHasCaptionTrack() { + mHasCaptionTrack = true; + } + + @Override + public boolean hasCaptionTrack() { + return mHasCaptionTrack; + } + + @Override + public int compareTo(@NonNull EitItem item) { + // The list of caption tracks and the program ids are not compared in here because the + // channels in TIF have the concept of the caption and audio tracks while the programs + // do not and the programs in TIF only have a program id since they are the rows of + // Content Provider. + int ret = mEventId - item.getEventId(); + if (ret != 0) { + return ret; + } + ret = StringUtils.compare(mTitleText, item.getTitleText()); + if (ret != 0) { + return ret; + } + if (mStartTime > item.getStartTime()) { + return 1; + } else if (mStartTime < item.getStartTime()) { + return -1; + } + if (mLengthInSecond > item.getLengthInSecond()) { + return 1; + } else if (mLengthInSecond < item.getLengthInSecond()) { + return -1; + } + + // Compares content ratings + ret = StringUtils.compare(mContentRating, item.getContentRating()); + if (ret != 0) { + return ret; + } + + // Compares broadcast genres + ret = StringUtils.compare(mBroadcastGenre, item.getBroadcastGenre()); + if (ret != 0) { + return ret; + } + // Compares canonical genres + ret = StringUtils.compare(mCanonicalGenre, item.getCanonicalGenre()); + if (ret != 0) { + return ret; + } + + // Compares descriptions + return StringUtils.compare(mDescription, item.getDescription()); + } + + public String getAudioLanguage() { + if (mAudioTracks == null) { + return ""; + } + ArrayList<String> languages = new ArrayList<>(); + for (AtscAudioTrack audioTrack : mAudioTracks) { + languages.add(audioTrack.language); + } + return TextUtils.join(",", languages); + } + + @Override + public String toString() { + return String.format( + Locale.US, + "EitItem programId: %d, eventId: %d, title: %s, startTime: %10d, " + + "length: %6d, rating: %s, audio tracks: %d, caption tracks: %d, " + + "genres (broadcast: %s, canonical: %s), description: %s", + mProgramId, + mEventId, + mTitleText, + mStartTime, + mLengthInSecond, + mContentRating, + mAudioTracks != null ? mAudioTracks.size() : 0, + mCaptionTracks != null ? mCaptionTracks.size() : 0, + mBroadcastGenre, + mCanonicalGenre, + mDescription); + } + } + + public static class EttItem { + public final int eventId; + public final String text; + + public EttItem(int eventId, String text) { + this.eventId = eventId; + this.text = text; + } + } +} diff --git a/src/com/android/tv/tuner/data/TunerChannel.java b/src/com/android/tv/tuner/data/TunerChannel.java new file mode 100644 index 00000000..52356c2e --- /dev/null +++ b/src/com/android/tv/tuner/data/TunerChannel.java @@ -0,0 +1,517 @@ +/* + * 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.support.annotation.NonNull; +import android.util.Log; +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.android.tv.util.StringUtils; +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.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.MPEG1: + case Channel.MPEG2: + case Channel.H263: + case Channel.H264: + case Channel.H265: + mProto.videoPid = pmt.getEsPid(); + mProto.videoStreamType = pmt.getStreamType(); + break; + + // MPEG ES stream audio types + case Channel.MPEG1AUDIO: + case Channel.MPEG2AUDIO: + case Channel.MPEG2AACAUDIO: + case Channel.MPEG4LATMAACAUDIO: + case Channel.A52AC3AUDIO: + case Channel.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; + } + } + 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.TYPE_TUNER); + } + + /** Initialize tuner channel with program number and PMT items. */ + public TunerChannel(int programNumber, List<PsiData.PmtItem> pmtItems) { + this(null, programNumber, pmtItems, Channel.TYPE_TUNER); + } + + /** Initialize tuner channel with SDT items and PMT items. */ + public TunerChannel(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) { + this(0, Channel.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.TYPE_FILE); + } + + public static TunerChannel forDvbFile( + PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) { + return new TunerChannel(0, Channel.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.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; + } + + 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.TYPE_FILE: + return String.format( + "{%d-%d %s} Filepath: %s, ProgramNumber %d", + mProto.virtualMajor, + mProto.virtualMinor, + mProto.shortName, + mProto.filepath, + mProto.programNumber); + // case Channel.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; + } + } +} |