11abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/*
21abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Copyright (C) 2015 The Android Open Source Project
31abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
41abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
51abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * you may not use this file except in compliance with the License.
61abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * You may obtain a copy of the License at
71abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
81abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
91abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Unless required by applicable law or agreed to in writing, software
111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * See the License for the specific language governing permissions and
141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * limitations under the License.
151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */
161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopackage com.android.tv.tuner.ts;
181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
191abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.media.tv.TvContentRating;
201abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.media.tv.TvContract.Programs.Genres;
216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.support.annotation.VisibleForTesting;
221abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.text.TextUtils;
236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.util.ArraySet;
241abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.util.Log;
251abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.util.SparseArray;
261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsiData.PatItem;
2865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsiData.PmtItem;
2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.Ac3AudioDescriptor;
3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.CaptionServiceDescriptor;
3165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.ContentAdvisoryDescriptor;
3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.EitItem;
3365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.EttItem;
3465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.ExtendedChannelNameDescriptor;
3565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.GenreDescriptor;
3665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.Iso639LanguageDescriptor;
3765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.MgtItem;
386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.tuner.data.PsipData.ParentalRatingDescriptor;
3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.PsipSection;
4065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.RatingRegion;
4165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.RegionalRating;
426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.tuner.data.PsipData.SdtItem;
436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.tuner.data.PsipData.ServiceDescriptor;
446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.tuner.data.PsipData.ShortEventDescriptor;
4565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.TsDescriptor;
4665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData.VctItem;
476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.tuner.data.nano.Channel;
48721bd0da688cd552737fbb753a00597f95103b95Adrian Roosimport com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
49721bd0da688cd552737fbb753a00597f95103b95Adrian Roosimport com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
5065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.util.ByteArrayBuffer;
511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.tuner.util.ConvertUtils;
531abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport com.ibm.icu.text.UnicodeDecompressor;
541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
551abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.io.UnsupportedEncodingException;
561abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.nio.charset.Charset;
576ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport java.util.Calendar;
581abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.ArrayList;
591abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.Arrays;
601abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.HashMap;
611abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.HashSet;
621abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.List;
636ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport java.util.Set;
641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/**
661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Parses ATSC PSIP sections.
671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */
681abddd9f6225298066094e20a6c29061b6af4590Nick Chalkopublic class SectionParser {
691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final String TAG = "SectionParser";
701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final boolean DEBUG = false;
711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte TABLE_ID_PAT = (byte) 0x00;
731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte TABLE_ID_PMT = (byte) 0x02;
741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte TABLE_ID_MGT = (byte) 0xc7;
751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte TABLE_ID_TVCT = (byte) 0xc8;
761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte TABLE_ID_CVCT = (byte) 0xc9;
771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte TABLE_ID_EIT = (byte) 0xcb;
781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte TABLE_ID_ETT = (byte) 0xcc;
791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
806ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    // Table id for DVB
816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final byte TABLE_ID_SDT = (byte) 0x42;
826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final byte TABLE_ID_DVB_ACTUAL_P_F_EIT = (byte) 0x4e;
836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final byte TABLE_ID_DVB_OTHER_P_F_EIT = (byte) 0x4f;
846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final byte TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT = (byte) 0x50;
856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final byte TABLE_ID_DVB_OTHER_SCHEDULE_EIT = (byte) 0x60;
866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // For details of the structure for the tags of descriptors, see ATSC A/65 Table 6.25.
881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int DESCRIPTOR_TAG_ISO639LANGUAGE = 0x0a;
891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86;
901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int DESCRIPTOR_TAG_CONTENT_ADVISORY = 0x87;
911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int DESCRIPTOR_TAG_AC3_AUDIO_STREAM = 0x81;
921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME = 0xa0;
931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int DESCRIPTOR_TAG_GENRE = 0xab;
941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    // For details of the structure for the tags of DVB descriptors, see DVB Document A038 Table 12.
966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public static final int DVB_DESCRIPTOR_TAG_SERVICE = 0x48;
976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public static final int DVB_DESCRIPTOR_TAG_SHORT_EVENT = 0X4d;
986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public static final int DVB_DESCRIPTOR_TAG_CONTENT = 0x54;
996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public static final int DVB_DESCRIPTOR_TAG_PARENTAL_RATING = 0x55;
1006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte COMPRESSION_TYPE_NO_COMPRESSION = (byte) 0x00;
1021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte MODE_SELECTED_UNICODE_RANGE_1 = (byte) 0x00;  // 0x0000 - 0x00ff
1031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte MODE_UTF16 = (byte) 0x3f;
1041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final byte MODE_SCSU = (byte) 0x3e;
1051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int MAX_SHORT_NAME_BYTES = 14;
1061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // See ANSI/CEA-766-C.
1081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int RATING_REGION_US_TV = 1;
1091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int RATING_REGION_KR_TV = 4;
1101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // The following values are defined in the live channels app.
1121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // See https://developer.android.com/reference/android/media/tv/TvContentRating.html.
1136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final String RATING_DOMAIN = "com.android.tv";
1141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final String RATING_REGION_RATING_SYSTEM_US_TV = "US_TV";
1156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final String RATING_REGION_RATING_SYSTEM_US_MV = "US_MV";
1161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final String RATING_REGION_RATING_SYSTEM_KR_TV = "KR_TV";
1171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final String[] RATING_REGION_TABLE_US_TV = {
1191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        "US_TV_Y", "US_TV_Y7", "US_TV_G", "US_TV_PG", "US_TV_14", "US_TV_MA"
1201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    };
1211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final String[] RATING_REGION_TABLE_US_MV = {
1236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        "US_MV_G", "US_MV_PG", "US_MV_PG13", "US_MV_R", "US_MV_NC17"
1246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    };
1256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final String[] RATING_REGION_TABLE_KR_TV = {
1271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        "KR_TV_ALL", "KR_TV_7", "KR_TV_12", "KR_TV_15", "KR_TV_19"
1281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    };
1291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final String[] RATING_REGION_TABLE_US_TV_SUBRATING = {
1316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        "US_TV_D", "US_TV_L", "US_TV_S", "US_TV_V", "US_TV_FV"
1326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    };
1336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    // According to ANSI-CEA-766-D
1356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_TV_Y = 1;
1366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_TV_Y7 = 2;
1376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_TV_NONE = 1;
1386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_TV_G = 2;
1396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_TV_PG = 3;
1406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_TV_14 = 4;
1416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_TV_MA = 5;
1426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int DIMENSION_US_TV_RATING = 0;
1446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int DIMENSION_US_TV_D = 1;
1456ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int DIMENSION_US_TV_L = 2;
1466ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int DIMENSION_US_TV_S = 3;
1476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int DIMENSION_US_TV_V = 4;
1486ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int DIMENSION_US_TV_Y = 5;
1496ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int DIMENSION_US_TV_FV = 6;
1506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int DIMENSION_US_MV_RATING = 7;
1516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_MV_G = 2;
1536ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_MV_PG = 3;
1546ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_MV_PG13 = 4;
1556ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_MV_R = 5;
1566ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_MV_NC17 = 6;
1576ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int VALUE_US_MV_X = 7;
1586ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1596ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final String STRING_US_TV_Y = "US_TV_Y";
1606ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final String STRING_US_TV_Y7 = "US_TV_Y7";
1616ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final String STRING_US_TV_FV = "US_TV_FV";
1626ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1636ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    /*
1651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * The following CRC table is from the code generated by the following command.
1661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * $ python pycrc.py --model crc-32-mpeg --algorithm table-driven --generate c
1671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * To see the details of pycrc, visit http://www.tty1.net/pycrc/index_en.html
1681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     */
1691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int[] CRC_TABLE = {
1701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
1711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
1721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
1731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
1741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
1751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
1761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
1771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
1781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
1791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
1801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
1811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
1821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
1831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
1841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
1851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
1861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
1871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
1881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
1891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
1901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
1911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
1921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
1931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
1941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
1951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
1961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
1971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
1981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
1991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
2001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
2011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
2021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
2031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
2041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
2051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
2061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
2071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
2081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
2091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
2101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
2111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
2121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
2131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
2141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
2151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
2161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
2171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
2181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
2191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
2201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
2211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
2221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
2231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
2241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
2251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
2261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
2271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
2281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
2291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
2301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
2311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
2321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
2331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
2341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    };
2351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // A table which maps ATSC genres to TIF genres.
2371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // See ATSC/65 Table 6.20.
2381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final String[] CANONICAL_GENRES_TABLE = {
2391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null, null,
2401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null, null,
2411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null, null,
2421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null, null,
2431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null, null,
2441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null, null,
2451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null, null,
2461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null, null,
2471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.EDUCATION, Genres.ENTERTAINMENT, Genres.MOVIES, Genres.NEWS,
2481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.LIFE_STYLE, Genres.SPORTS, null, Genres.MOVIES,
2491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.FAMILY_KIDS, Genres.DRAMA, null, Genres.ENTERTAINMENT, Genres.SPORTS,
2511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.SPORTS,
2521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null,
2531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.MUSIC, Genres.EDUCATION,
2541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.COMEDY,
2561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.MUSIC,
2581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null,
2591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.MOVIES, Genres.ENTERTAINMENT, Genres.NEWS, Genres.DRAMA,
2601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.EDUCATION, Genres.MOVIES, Genres.SPORTS, Genres.MOVIES,
2611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.LIFE_STYLE, Genres.ARTS, Genres.LIFE_STYLE, Genres.SPORTS,
2631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null,
2641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.GAMING, Genres.LIFE_STYLE, Genres.SPORTS,
2651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.LIFE_STYLE, Genres.EDUCATION, Genres.EDUCATION, Genres.LIFE_STYLE,
2671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.SPORTS, Genres.LIFE_STYLE, Genres.MOVIES, Genres.NEWS,
2681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null,
2691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.EDUCATION,
2701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null,
2711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.EDUCATION,
2721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null,
2731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.DRAMA, Genres.MUSIC, Genres.MOVIES,
2741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.ANIMAL_WILDLIFE,
2761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null,
2771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.PREMIER,
2781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null, null,
2791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.SPORTS, Genres.ARTS,
2801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null, null, null,
2811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.MOVIES, Genres.TECH_SCIENCE, Genres.DRAMA,
2821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.SHOPPING, Genres.DRAMA,
2841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.MOVIES, Genres.ENTERTAINMENT, Genres.TECH_SCIENCE, Genres.SPORTS,
2861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.TRAVEL, Genres.ENTERTAINMENT, Genres.ARTS, Genres.NEWS,
2871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.ARTS, Genres.SPORTS, Genres.SPORTS, Genres.NEWS,
2891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.SPORTS, Genres.SPORTS, Genres.SPORTS, Genres.FAMILY_KIDS,
2901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.FAMILY_KIDS, Genres.MOVIES,
2911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.TECH_SCIENCE, Genres.MUSIC,
2931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.SPORTS, Genres.FAMILY_KIDS, Genres.NEWS, Genres.SPORTS,
2951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.NEWS, Genres.SPORTS, Genres.ANIMAL_WILDLIFE,
2961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.MUSIC, Genres.NEWS, Genres.SPORTS,
2981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
2991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.NEWS, Genres.NEWS, Genres.NEWS, Genres.NEWS,
3001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.SPORTS, Genres.MOVIES, Genres.ARTS, Genres.ANIMAL_WILDLIFE,
3011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.MUSIC, Genres.MUSIC, Genres.MOVIES, Genres.EDUCATION,
3021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.DRAMA, Genres.SPORTS, Genres.SPORTS, Genres.SPORTS,
3031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.SPORTS,
3041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        null,
3051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Genres.SPORTS, Genres.SPORTS,
3061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    };
3071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // A table which contains ATSC categorical genre code assignments.
3091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // See ATSC/65 Table 6.20.
3101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final String[] BROADCAST_GENRES_TABLE = new String[] {
3111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            null, null, null, null,
3121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            null, null, null, null,
3131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            null, null, null, null,
3141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            null, null, null, null,
3151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            null, null, null, null,
3161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            null, null, null, null,
3171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            null, null, null, null,
3181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            null, null, null, null,
3191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Education", "Entertainment", "Movie", "News",
3201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Religious", "Sports", "Other", "Action",
3211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Advertisement", "Animated", "Anthology", "Automobile",
3221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Awards", "Baseball", "Basketball", "Bulletin",
3231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Business", "Classical", "College", "Combat",
3241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Comedy", "Commentary", "Concert", "Consumer",
3251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Contemporary", "Crime", "Dance", "Documentary",
3261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Drama", "Elementary", "Erotica", "Exercise",
3271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Fantasy", "Farm", "Fashion", "Fiction",
3281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Food", "Football", "Foreign", "Fund Raiser",
3291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Game/Quiz", "Garden", "Golf", "Government",
3301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Health", "High School", "History", "Hobby",
3311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Hockey", "Home", "Horror", "Information",
3321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Instruction", "International", "Interview", "Language",
3331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Legal", "Live", "Local", "Math",
3341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Medical", "Meeting", "Military", "Miniseries",
3351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Music", "Mystery", "National", "Nature",
3361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Police", "Politics", "Premier", "Prerecorded",
3371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Product", "Professional", "Public", "Racing",
3381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Reading", "Repair", "Repeat", "Review",
3391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Romance", "Science", "Series", "Service",
3401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Shopping", "Soap Opera", "Special", "Suspense",
3411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Talk", "Technical", "Tennis", "Travel",
3421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Variety", "Video", "Weather", "Western",
3431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Art", "Auto Racing", "Aviation", "Biography",
3441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Boating", "Bowling", "Boxing", "Cartoon",
3451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Children", "Classic Film", "Community", "Computers",
3461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Country Music", "Court", "Extreme Sports", "Family",
3471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Financial", "Gymnastics", "Headlines", "Horse Racing",
3481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Hunting/Fishing/Outdoors", "Independent", "Jazz", "Magazine",
3491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Motorcycle Racing", "Music/Film/Books", "News-International", "News-Local",
3501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "News-National", "News-Regional", "Olympics", "Original",
3511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Performing Arts", "Pets/Animals", "Pop", "Rock & Roll",
3521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Sci-Fi", "Self Improvement", "Sitcom", "Skating",
3531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Skiing", "Soccer", "Track/Field", "True",
3541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "Volleyball", "Wrestling",
3551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    };
3561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
35765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    // Audio language code map from ISO 639-2/B to 639-2/T, in order to show correct audio language.
35865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final HashMap<String, String> ISO_LANGUAGE_CODE_MAP;
35965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    static {
36065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP = new HashMap<>();
36165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("alb", "sqi");
36265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("arm", "hye");
36365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("baq", "eus");
36465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("bur", "mya");
36565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("chi", "zho");
36665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("cze", "ces");
36765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("dut", "nld");
36865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("fre", "fra");
36965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("geo", "kat");
37065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("ger", "deu");
37165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("gre", "ell");
37265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("ice", "isl");
37365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("mac", "mkd");
37465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("mao", "mri");
37565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("may", "msa");
37665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("per", "fas");
37765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("rum", "ron");
37865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("slo", "slk");
37965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("tib", "bod");
38065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("wel", "cym");
38165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ISO_LANGUAGE_CODE_MAP.put("esl", "spa"); // Special entry for channel 9-1 KQED in bay area.
38265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
38365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
3841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // Containers to store the last version numbers of the PSIP sections.
3851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private final HashMap<PsipSection, Integer> mSectionVersionMap = new HashMap<>();
3861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private final SparseArray<List<EttItem>> mParsedEttItems = new SparseArray<>();
3871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public interface OutputListener {
3891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        void onPatParsed(List<PatItem> items);
3901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        void onPmtParsed(int programNumber, List<PmtItem> items);
3911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        void onMgtParsed(List<MgtItem> items);
39265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        void onVctParsed(List<VctItem> items, int sectionNumber, int lastSectionNumber);
3931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        void onEitParsed(int sourceId, List<EitItem> items);
3941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        void onEttParsed(int sourceId, List<EttItem> descriptions);
3956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        void onSdtParsed(List<SdtItem> items);
3961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
3971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
39865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final OutputListener mListener;
3991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public SectionParser(OutputListener listener) {
4011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mListener = listener;
4021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
4031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public void parseSections(ByteArrayBuffer data) {
4051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int pos = 0;
4061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        while (pos + 3 <= data.length()) {
4071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if ((data.byteAt(pos) & 0xff) == 0xff) {
4081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // Clear stuffing bytes according to H222.0 section 2.4.4.
4091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                data.setLength(0);
4101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
4121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int sectionLength =
4131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    (((data.byteAt(pos + 1) & 0x0f) << 8) | (data.byteAt(pos + 2) & 0xff)) + 3;
4141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (pos + sectionLength > data.length()) {
4151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
4171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (DEBUG) {
4181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.d(TAG, "parseSections 0x" + Integer.toHexString(data.byteAt(pos) & 0xff));
4191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
4201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            parseSection(Arrays.copyOfRange(data.buffer(), pos, pos + sectionLength));
4211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += sectionLength;
4221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
4231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mListener != null) {
4241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            for (int i = 0; i < mParsedEttItems.size(); ++i) {
4251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int sourceId = mParsedEttItems.keyAt(i);
4261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                List<EttItem> descriptions = mParsedEttItems.valueAt(i);
4271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                mListener.onEttParsed(sourceId, descriptions);
4281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
4291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
4301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mParsedEttItems.clear();
4311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
4321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public void resetVersionNumbers() {
4346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        mSectionVersionMap.clear();
4356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
4366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
4371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private void parseSection(byte[] data) {
4381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (!checkSanity(data)) {
4391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.d(TAG, "Bad CRC!");
4401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return;
4411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
4421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        PsipSection section = PsipSection.create(data);
4431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (section == null) {
4441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return;
4451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
4461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // The currentNextIndicator indicates that the section sent is currently applicable.
4481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (!section.getCurrentNextIndicator()) {
4491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return;
4501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
4511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int versionNumber = (data[5] & 0x3e) >> 1;
4521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        Integer oldVersionNumber = mSectionVersionMap.get(section);
4531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // The versionNumber shall be incremented when a change in the information carried within
4551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // the section occurs.
4561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (oldVersionNumber != null && versionNumber == oldVersionNumber) {
4571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return;
4581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
4591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        boolean result = false;
4601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        switch (data[0]) {
4611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case TABLE_ID_PAT:
4621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                result = parsePAT(data);
4631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case TABLE_ID_PMT:
4651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                result = parsePMT(data);
4661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case TABLE_ID_MGT:
4681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                result = parseMGT(data);
4691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case TABLE_ID_TVCT:
4711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case TABLE_ID_CVCT:
4721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                result = parseVCT(data);
4731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case TABLE_ID_EIT:
4751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                result = parseEIT(data);
4761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case TABLE_ID_ETT:
4781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                result = parseETT(data);
4791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4806ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            case TABLE_ID_SDT:
4816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                result = parseSDT(data);
4826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                break;
4836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            case TABLE_ID_DVB_ACTUAL_P_F_EIT:
4846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            case TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT:
4856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                result = parseDVBEIT(data);
4866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                break;
4871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            default:
4881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
4901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (result) {
4911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mSectionVersionMap.put(section, versionNumber);
4921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
4931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
4941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private boolean parsePAT(byte[] data) {
4961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (DEBUG) {
4971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.d(TAG, "PAT is discovered.");
4981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
4991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int pos = 8;
5001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<PatItem> results = new ArrayList<>();
5021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (; pos < data.length - 4; pos = pos + 4) {
5031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (pos > data.length - 4 - 4) {
5041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken PAT.");
5051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
5061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
5071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int programNo = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff);
5081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int pmtPid = ((data[pos + 2] & 0x1f) << 8) | (data[pos + 3] & 0xff);
5091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            results.add(new PatItem(programNo, pmtPid));
5101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mListener != null) {
5121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mListener.onPatParsed(results);
5131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return true;
5151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
5161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private boolean parsePMT(byte[] data) {
5181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int table_id_ext = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
5191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (DEBUG) {
5201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.d(TAG, "PMT is discovered. programNo = " + table_id_ext);
5211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (data.length <= 11) {
5231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken PMT.");
5241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return false;
5251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int pcrPid = (data[8] & 0x1f) << 8 | data[9];
5271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int programInfoLen = (data[10] & 0x0f) << 8 | data[11];
5281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int pos = 12;
5291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<TsDescriptor> descriptors = parseDescriptors(data, pos, pos + programInfoLen);
5301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        pos += programInfoLen;
5311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (DEBUG) {
5321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.d(TAG, "PMT descriptors size: " + descriptors.size());
5331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<PmtItem> results = new ArrayList<>();
5351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (; pos < data.length - 4;) {
53665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (pos < 0) {
53765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                Log.e(TAG, "Broken PMT.");
53865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return false;
53965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
5401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int streamType = data[pos] & 0xff;
5411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int esPid = (data[pos + 1] & 0x1f) << 8 | (data[pos + 2] & 0xff);
5421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int esInfoLen = (data[pos + 3] & 0xf) << 8 | (data[pos + 4] & 0xff);
5431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (data.length < pos + esInfoLen + 5) {
5441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken PMT.");
5451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
5461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
5471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            descriptors = parseDescriptors(data, pos + 5, pos + 5 + esInfoLen);
5481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            List<AtscAudioTrack> audioTracks = generateAudioTracks(descriptors);
5491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            List<AtscCaptionTrack> captionTracks = generateCaptionTracks(descriptors);
5501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            PmtItem pmtItem = new PmtItem(streamType, esPid, audioTracks, captionTracks);
5511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (DEBUG) {
5521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.d(TAG, "PMT " + pmtItem + " descriptors size: " + descriptors.size());
5531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
5541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            results.add(pmtItem);
5551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = pos + esInfoLen + 5;
5561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        results.add(new PmtItem(PmtItem.ES_PID_PCR, pcrPid, null, null));
5581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mListener != null) {
5591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mListener.onPmtParsed(table_id_ext, results);
5601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return true;
5621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
5631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private boolean parseMGT(byte[] data) {
5651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For details of the structure for MGT, see ATSC A/65 Table 6.2.
5661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (DEBUG) {
5671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.d(TAG, "MGT is discovered.");
5681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (data.length <= 10) {
5701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken MGT.");
5711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return false;
5721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int tablesDefined = ((data[9] & 0xff) << 8) | (data[10] & 0xff);
5741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int pos = 11;
5751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<MgtItem> results = new ArrayList<>();
5761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < tablesDefined; ++i) {
5771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (data.length <= pos + 10) {
5781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken MGT.");
5791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
5801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
5811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int tableType = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff);
5821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int tableTypePid = ((data[pos + 2] & 0x1f) << 8) | (data[pos + 3] & 0xff);
5831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int descriptorsLength = ((data[pos + 9] & 0x0f) << 8) | (data[pos + 10] & 0xff);
5841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 11 + descriptorsLength;
5851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            results.add(new MgtItem(tableType, tableTypePid));
5861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // Skip the remaining descriptor part which we don't use.
5886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
5891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mListener != null) {
5901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mListener.onMgtParsed(results);
5911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
5921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return true;
5931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
5941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private boolean parseVCT(byte[] data) {
5961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For details of the structure for VCT, see ATSC A/65 Table 6.4 and 6.8.
5971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (DEBUG) {
5981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.d(TAG, "VCT is discovered.");
5991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
6001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (data.length <= 9) {
6011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken VCT.");
6021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return false;
6031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
6041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int numChannelsInSection = (data[9] & 0xff);
60565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        int sectionNumber = (data[6] & 0xff);
60665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        int lastSectionNumber = (data[7] & 0xff);
607d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        if (sectionNumber > lastSectionNumber) {
608d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            // According to section 6.3.1 of the spec ATSC A/65,
609d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            // last section number is the largest section number.
610d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            Log.w(TAG, "Invalid VCT. Section Number " + sectionNumber + " > Last Section Number "
611d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    + lastSectionNumber);
612d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            return false;
613d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
6141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int pos = 10;
6151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<VctItem> results = new ArrayList<>();
6161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < numChannelsInSection; ++i) {
6171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (data.length <= pos + 31) {
6181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken VCT.");
6191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
6201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            String shortName = "";
6221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int shortNameSize = getShortNameSize(data, pos);
6231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            try {
6241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                shortName = new String(
6251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        Arrays.copyOfRange(data, pos, pos + shortNameSize), "UTF-16");
6261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            } catch (UnsupportedEncodingException e) {
6271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken VCT.", e);
6281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
6291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if ((data[pos + 14] & 0xf0) != 0xf0) {
6311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken VCT.");
6321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
6331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int majorNumber = ((data[pos + 14] & 0x0f) << 6) | ((data[pos + 15] & 0xff) >> 2);
6351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int minorNumber = ((data[pos + 15] & 0x03) << 8) | (data[pos + 16] & 0xff);
6361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if ((majorNumber & 0x3f0) == 0x3f0) {
6371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // If the six MSBs are 111111, these indicate that there is only one-part channel
6381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // number. To see details, refer A/65 Section 6.3.2.
6391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                majorNumber = ((majorNumber & 0xf) << 10) + minorNumber;
6401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                minorNumber = 0;
6411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int channelTsid = ((data[pos + 22] & 0xff) << 8) | (data[pos + 23] & 0xff);
6431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int programNumber = ((data[pos + 24] & 0xff) << 8) | (data[pos + 25] & 0xff);
6441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            boolean accessControlled = (data[pos + 26] & 0x20) != 0;
6451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            boolean hidden = (data[pos + 26] & 0x10) != 0;
6461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int serviceType = (data[pos + 27] & 0x3f);
6471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int sourceId = ((data[pos + 28] & 0xff) << 8) | (data[pos + 29] & 0xff);
6481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int descriptorsPos = pos + 32;
6491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int descriptorsLength = ((data[pos + 30] & 0x03) << 8) | (data[pos + 31] & 0xff);
6501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 32 + descriptorsLength;
6511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (data.length < pos) {
6521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken VCT.");
6531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
6541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            List<TsDescriptor> descriptors = parseDescriptors(
6561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    data, descriptorsPos, descriptorsPos + descriptorsLength);
6571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            String longName = null;
6581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            for (TsDescriptor descriptor : descriptors) {
6591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (descriptor instanceof ExtendedChannelNameDescriptor) {
6601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    ExtendedChannelNameDescriptor extendedChannelNameDescriptor =
6611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            (ExtendedChannelNameDescriptor) descriptor;
6621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    longName = extendedChannelNameDescriptor.getLongChannelName();
6631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
6641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
6651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (DEBUG) {
6671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.d(TAG, String.format(
6681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        "Found channel [%s] %s - serviceType: %d tsid: 0x%x program: %d "
6691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                + "channel: %d-%d encrypted: %b hidden: %b, descriptors: %d",
6701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        shortName, longName, serviceType, channelTsid, programNumber, majorNumber,
6711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        minorNumber, accessControlled, hidden, descriptors.size()));
6721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (!accessControlled && !hidden && (serviceType == Channel.SERVICE_TYPE_ATSC_AUDIO ||
6741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    serviceType == Channel.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION ||
6751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    serviceType == Channel.SERVICE_TYPE_UNASSOCIATED_SMALL_SCREEN_SERVICE)) {
6761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // Hide hidden, encrypted, or unsupported ATSC service type channels
6771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                results.add(new VctItem(shortName, longName, serviceType, channelTsid,
6781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        programNumber, majorNumber, minorNumber, sourceId));
6791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
68165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Skip the remaining descriptor part which we don't use.
68265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
6831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mListener != null) {
68465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mListener.onVctParsed(results, sectionNumber, lastSectionNumber);
6851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
6861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return true;
6871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
6881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
6891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private boolean parseEIT(byte[] data) {
6901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For details of the structure for EIT, see ATSC A/65 Table 6.11.
6911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (DEBUG) {
6921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.d(TAG, "EIT is discovered.");
6931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
6941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (data.length <= 9) {
6951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken EIT.");
6961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return false;
6971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
6981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int sourceId = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
6991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int numEventsInSection = (data[9] & 0xff);
7001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
7011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int pos = 10;
7021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<EitItem> results = new ArrayList<>();
7031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < numEventsInSection; ++i) {
7041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (data.length <= pos + 9) {
7051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken EIT.");
7061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
7071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
7081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if ((data[pos] & 0xc0) != 0xc0) {
7091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken EIT.");
7101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
7111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
7121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int eventId = ((data[pos] & 0x3f) << 8) + (data[pos + 1] & 0xff);
7131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            long startTime = ((data[pos + 2] & (long) 0xff) << 24) | ((data[pos + 3] & 0xff) << 16)
7141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    | ((data[pos + 4] & 0xff) << 8) | (data[pos + 5] & 0xff);
7151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int lengthInSecond = ((data[pos + 6] & 0x0f) << 16)
7161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    | ((data[pos + 7] & 0xff) << 8) | (data[pos + 8] & 0xff);
7171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int titleLength = (data[pos + 9] & 0xff);
7181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (data.length <= pos + 10 + titleLength + 1) {
7191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken EIT.");
7201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
7211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
7221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            String titleText = "";
7231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (titleLength > 0) {
7241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                titleText = extractText(data, pos + 10);
7251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
7261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if ((data[pos + 10 + titleLength] & 0xf0) != 0xf0) {
7271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken EIT.");
7281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
7291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
7301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int descriptorsLength = ((data[pos + 10 + titleLength] & 0x0f) << 8)
7311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    | (data[pos + 10 + titleLength + 1] & 0xff);
7321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int descriptorsPos = pos + 10 + titleLength + 2;
7331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (data.length < descriptorsPos + descriptorsLength) {
7341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken EIT.");
7351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
7361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
7371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            List<TsDescriptor> descriptors = parseDescriptors(
7381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    data, descriptorsPos, descriptorsPos + descriptorsLength);
7391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (DEBUG) {
7401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.d(TAG, String.format("EIT descriptors size: %d", descriptors.size()));
7411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
7421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            String contentRating = generateContentRating(descriptors);
7431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            String broadcastGenre = generateBroadcastGenre(descriptors);
7441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            String canonicalGenre = generateCanonicalGenre(descriptors);
7451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            List<AtscAudioTrack> audioTracks = generateAudioTracks(descriptors);
7461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            List<AtscCaptionTrack> captionTracks = generateCaptionTracks(descriptors);
7471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 10 + titleLength + 2 + descriptorsLength;
7481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            results.add(new EitItem(EitItem.INVALID_PROGRAM_ID, eventId, titleText,
7491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    startTime, lengthInSecond, contentRating, audioTracks, captionTracks,
7501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    broadcastGenre, canonicalGenre, null));
7511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
7521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mListener != null) {
7531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mListener.onEitParsed(sourceId, results);
7541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
7551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return true;
7561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
7571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
7581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private boolean parseETT(byte[] data) {
7591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For details of the structure for ETT, see ATSC A/65 Table 6.13.
7601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (DEBUG) {
7611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.d(TAG, "ETT is discovered.");
7621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
7631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (data.length <= 12) {
7641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken ETT.");
7651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return false;
7661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
7671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int sourceId = ((data[9] & 0xff) << 8) | (data[10] & 0xff);
7681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int eventId = (((data[11] & 0xff) << 8) | (data[12] & 0xff)) >> 2;
7691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        String text = extractText(data, 13);
7701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<EttItem> ettItems = mParsedEttItems.get(sourceId);
7711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (ettItems == null) {
7721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ettItems = new ArrayList<>();
7731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mParsedEttItems.put(sourceId, ettItems);
7741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
7751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ettItems.add(new EttItem(eventId, text));
7761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return true;
7771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
7781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
7796ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private boolean parseSDT(byte[] data) {
7806ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // For details of the structure for SDT, see DVB Document A038 Table 5.
7816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (DEBUG) {
7826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.d(TAG, "SDT id discovered");
7836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
7846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (data.length <= 11) {
7856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.e(TAG, "Broken SDT.");
7866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return false;
7876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
7886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if ((data[1] & 0x80) >> 7 != 1) {
7896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.e(TAG, "Broken SDT, section syntax indicator error.");
7906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return false;
7916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
7926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff);
7936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int transportStreamId = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
7946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int originalNetworkId = ((data[8] & 0xff) << 8) | (data[9] & 0xff);
7956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int pos = 11;
7966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (sectionLength + 3 > data.length) {
7976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.e(TAG, "Broken SDT.");
7986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
7996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<SdtItem> sdtItems = new ArrayList<>();
8006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        while (pos + 9 < data.length) {
8016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int serviceId = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff);
8026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int descriptorsLength = ((data[pos + 3] & 0x0f) << 8) | (data[pos + 4] & 0xff);
8036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            pos += 5;
8046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            List<TsDescriptor> descriptors = parseDescriptors(data, pos, pos + descriptorsLength);
8056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            List<ServiceDescriptor> serviceDescriptors = generateServiceDescriptors(descriptors);
8066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            String serviceName = "";
8076ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            String serviceProviderName = "";
8086ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int serviceType = 0;
8096ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            for (ServiceDescriptor serviceDescriptor : serviceDescriptors) {
8106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                serviceName = serviceDescriptor.getServiceName();
8116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                serviceProviderName = serviceDescriptor.getServiceProviderName();
8126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                serviceType = serviceDescriptor.getServiceType();
8136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
8146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (serviceDescriptors.size() > 0) {
8156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                sdtItems.add(new SdtItem(serviceName, serviceProviderName, serviceType, serviceId,
8166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        originalNetworkId));
8176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
8186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            pos += descriptorsLength;
8196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (mListener != null) {
8216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            mListener.onSdtParsed(sdtItems);
8226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return true;
8246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
8256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
8266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private boolean parseDVBEIT(byte[] data) {
8276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // For details of the structure for DVB ETT, see DVB Document A038 Table 7.
8286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (DEBUG) {
8296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.d(TAG, "DVB EIT is discovered.");
8306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (data.length < 18) {
8326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.e(TAG, "Broken DVB EIT.");
8336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return false;
8346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff);
8366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int sourceId = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
8376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int transportStreamId = ((data[8] & 0xff) << 8) | (data[9] & 0xff);
8386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int originalNetworkId = ((data[10] & 0xff) << 8) | (data[11] & 0xff);
8396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
8406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int pos = 14;
8416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<EitItem> results = new ArrayList<>();
8426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        while (pos + 12 < data.length) {
8436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int eventId = ((data[pos] & 0xff) << 8) + (data[pos + 1] & 0xff);
8446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            float modifiedJulianDate = ((data[pos + 2] &  0xff) << 8) | (data[pos + 3] & 0xff);
8456ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int startYear = (int) ((modifiedJulianDate - 15078.2f) / 365.25f);
8466ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int mjdMonth = (int) ((modifiedJulianDate - 14956.1f
8476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    - (int) (startYear * 365.25f)) / 30.6001f);
8486ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int startDay = (int) modifiedJulianDate - 14956 - (int) (startYear * 365.25f)
8496ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    - (int) (mjdMonth * 30.6001f);
8506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int startMonth = mjdMonth - 1;
8516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (mjdMonth == 14 || mjdMonth == 15) {
8526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                startYear += 1;
8536ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                startMonth -= 12;
8546ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
8556ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int startHour = ((data[pos + 4] & 0xf0) >> 4) * 10 + (data[pos + 4] & 0x0f);
8566ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int startMinute = ((data[pos + 5] & 0xf0) >> 4) * 10 + (data[pos + 5] & 0x0f);
8576ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int startSecond = ((data[pos + 6] & 0xf0) >> 4) * 10 + (data[pos + 6] & 0x0f);
8586ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Calendar calendar = Calendar.getInstance();
8596ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            startYear += 1900;
8606ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            calendar.set(startYear, startMonth, startDay, startHour, startMinute, startSecond);
8616ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            long startTime = ConvertUtils.convertUnixEpochToGPSTime(
8626ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    calendar.getTimeInMillis() / 1000);
8636ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int durationInSecond = (((data[pos + 7] & 0xf0) >> 4) * 10
8646ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    + (data[pos + 7] & 0x0f)) * 3600
8656ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    + (((data[pos + 8] & 0xf0) >> 4) * 10 + (data[pos + 8] & 0x0f)) * 60
8666ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    + (((data[pos + 9] & 0xf0) >> 4) * 10 + (data[pos + 9] & 0x0f));
8676ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int descriptorsLength = ((data[pos + 10] & 0x0f) << 8)
8686ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    | (data[pos + 10 + 1] & 0xff);
8696ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int descriptorsPos = pos + 10 + 2;
8706ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (data.length < descriptorsPos + descriptorsLength) {
8716ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                Log.e(TAG, "Broken EIT.");
8726ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                return false;
8736ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
8746ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            List<TsDescriptor> descriptors = parseDescriptors(
8756ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    data, descriptorsPos, descriptorsPos + descriptorsLength);
8766ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (DEBUG) {
8776ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                Log.d(TAG, String.format("DVB EIT descriptors size: %d", descriptors.size()));
8786ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
8796ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            // TODO: Add logic to generating content rating for dvb. See DVB document 6.2.28 for
8806ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            // details. Content rating here will be null
8816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            String contentRating = generateContentRating(descriptors);
8826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            // TODO: Add logic for generating genre for dvb. See DVB document 6.2.9 for details.
8836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            // Genre here will be null here.
8846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            String broadcastGenre = generateBroadcastGenre(descriptors);
8856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            String canonicalGenre = generateCanonicalGenre(descriptors);
8866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            String titleText = generateShortEventName(descriptors);
8876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            List<AtscAudioTrack> audioTracks = generateAudioTracks(descriptors);
8886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            List<AtscCaptionTrack> captionTracks = generateCaptionTracks(descriptors);
8896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            pos += 12 + descriptorsLength;
8906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            results.add(new EitItem(EitItem.INVALID_PROGRAM_ID, eventId, titleText,
8916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    startTime, durationInSecond, contentRating, audioTracks, captionTracks,
8926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    broadcastGenre, canonicalGenre, null));
8936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (mListener != null) {
8956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            mListener.onEitParsed(sourceId, results);
8966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return true;
8986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
8996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
9001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static List<AtscAudioTrack> generateAudioTracks(List<TsDescriptor> descriptors) {
9011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // The list of audio tracks sent is located at both AC3 Audio descriptor and ISO 639
9021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Language descriptor.
9031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<AtscAudioTrack> ac3Tracks = new ArrayList<>();
9041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<AtscAudioTrack> iso639LanguageTracks = new ArrayList<>();
90565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (TsDescriptor descriptor : descriptors) {
9061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (descriptor instanceof Ac3AudioDescriptor) {
9071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Ac3AudioDescriptor audioDescriptor =
9081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        (Ac3AudioDescriptor) descriptor;
9091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                AtscAudioTrack audioTrack = new AtscAudioTrack();
9101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (audioDescriptor.getLanguage() != null) {
9111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    audioTrack.language = audioDescriptor.getLanguage();
9121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
9136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                if (audioTrack.language == null) {
9146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    audioTrack.language = "";
9156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                }
9161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                audioTrack.audioType = AtscAudioTrack.AUDIOTYPE_UNDEFINED;
9171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                audioTrack.channelCount = audioDescriptor.getNumChannels();
9181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                audioTrack.sampleRate = audioDescriptor.getSampleRate();
9191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ac3Tracks.add(audioTrack);
9201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
9211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
9221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (TsDescriptor descriptor : descriptors) {
9231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (descriptor instanceof Iso639LanguageDescriptor) {
9241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Iso639LanguageDescriptor iso639LanguageDescriptor =
9251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        (Iso639LanguageDescriptor) descriptor;
9261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                iso639LanguageTracks.addAll(iso639LanguageDescriptor.getAudioTracks());
9271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
9281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
9291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
9301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // An AC3 audio stream descriptor only has a audio channel count and a audio sample rate
9311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // while a ISO 639 Language descriptor only has a audio type, which describes a main use
9321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // case of its audio track.
9331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Some channels contain only AC3 audio stream descriptors with valid language values.
9341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Other channels contain both an AC3 audio stream descriptor and a ISO 639 Language
9351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // descriptor per audio track, and those AC3 audio stream descriptors often have a null
9361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // value of language field.
9371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Combines two descriptors into one in order to gather more audio track specific
9381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // information as much as possible.
9391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<AtscAudioTrack> tracks = new ArrayList<>();
9401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (!ac3Tracks.isEmpty() && !iso639LanguageTracks.isEmpty()
9411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && ac3Tracks.size() != iso639LanguageTracks.size()) {
9421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // This shouldn't be happen. In here, it handles two cases. The first case is that the
9431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // only one type of descriptors arrives. The second case is that the two types of
9441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // descriptors have the same number of tracks.
9451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "AC3 audio stream descriptors size != ISO 639 Language descriptors size");
9461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return tracks;
9471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
9481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int size = Math.max(ac3Tracks.size(), iso639LanguageTracks.size());
9491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < size; ++i) {
9501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            AtscAudioTrack audioTrack = null;
9511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (i < ac3Tracks.size()) {
9521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                audioTrack = ac3Tracks.get(i);
9531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
9541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (i < iso639LanguageTracks.size()) {
9551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (audioTrack == null) {
9561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    audioTrack = iso639LanguageTracks.get(i);
9571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                } else {
9581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    AtscAudioTrack iso639LanguageTrack = iso639LanguageTracks.get(i);
95965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    if (audioTrack.language == null || TextUtils.equals(audioTrack.language, "")) {
9601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        audioTrack.language = iso639LanguageTrack.language;
9611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    }
9621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    audioTrack.audioType = iso639LanguageTrack.audioType;
9631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
9641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
96565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            String language = ISO_LANGUAGE_CODE_MAP.get(audioTrack.language);
96665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (language != null) {
96765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                audioTrack.language = language;
96865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
9691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            tracks.add(audioTrack);
9701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
9711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return tracks;
9721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
9731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
9741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static List<AtscCaptionTrack> generateCaptionTracks(List<TsDescriptor> descriptors) {
9751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<AtscCaptionTrack> services = new ArrayList<>();
9761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (TsDescriptor descriptor : descriptors) {
9771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (descriptor instanceof CaptionServiceDescriptor) {
9781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                CaptionServiceDescriptor captionServiceDescriptor =
9791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        (CaptionServiceDescriptor) descriptor;
9801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                services.addAll(captionServiceDescriptor.getCaptionTracks());
9811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
9821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
9831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return services;
9841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
9851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
9866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @VisibleForTesting
9876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    static String generateContentRating(List<TsDescriptor> descriptors) {
9886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        Set<String> contentRatings = new ArraySet<>();
9896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<RatingRegion> usRatingRegions = getRatingRegions(descriptors, RATING_REGION_US_TV);
9906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<RatingRegion> krRatingRegions = getRatingRegions(descriptors, RATING_REGION_KR_TV);
9916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (RatingRegion region : usRatingRegions) {
9926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            String contentRating = getUsRating(region);
9936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (contentRating != null) {
9946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                contentRatings.add(contentRating);
9956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
9966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
9976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (RatingRegion region : krRatingRegions) {
9986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            String contentRating = getKrRating(region);
9996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (contentRating != null) {
10006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                contentRatings.add(contentRating);
10016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
10026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
10036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return TextUtils.join(",", contentRatings);
10046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
10056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
10066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
10076ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Gets a list of {@link RatingRegion} in the specific region.
10086ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     *
10096ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * @param descriptors {@link TsDescriptor} list which may contains rating information
10106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * @param region the specific region
10116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * @return a list of {@link RatingRegion} in the specific region
10126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
10136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static List<RatingRegion> getRatingRegions(List<TsDescriptor> descriptors, int region) {
10146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<RatingRegion> ratingRegions = new ArrayList<>();
10151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (TsDescriptor descriptor : descriptors) {
10166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (!(descriptor instanceof ContentAdvisoryDescriptor)) {
10176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                continue;
10186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
10196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            ContentAdvisoryDescriptor contentAdvisoryDescriptor =
10206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    (ContentAdvisoryDescriptor) descriptor;
10216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            for (RatingRegion ratingRegion : contentAdvisoryDescriptor.getRatingRegions()) {
10226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                if (ratingRegion.getName() == region) {
10236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    ratingRegions.add(ratingRegion);
10246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                }
10256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
10266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
10276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return ratingRegions;
10286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
10296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
10306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
10316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Gets US content rating and subratings (if any).
10326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     *
10336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * @param ratingRegion a {@link RatingRegion} instance which may contain rating information.
10346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * @return A string representing the US content rating and subratings. The format of the string
10356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     *     is defined in {@link TvContentRating}. null, if no such a string exists.
10366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
10376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static String getUsRating(RatingRegion ratingRegion) {
10386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (ratingRegion.getName() != RATING_REGION_US_TV) {
10396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return null;
10406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
10416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<RegionalRating> regionalRatings = ratingRegion.getRegionalRatings();
10426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        String rating = null;
10436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int ratingIndex = VALUE_US_TV_NONE;
10446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<String> subratings = new ArrayList<>();
10456ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (RegionalRating index : regionalRatings) {
10466ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            // See Table 3 of ANSI-CEA-766-D
10476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int dimension = index.getDimension();
10486ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int value = index.getRating();
10496ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            switch (dimension) {
10506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // According to Table 6.27 of ATSC A65,
10516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // the dimensions shall be in increasing order.
10526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // Therefore, rating and ratingIndex are assigned before any corresponding
10536ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // subrating.
10546ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DIMENSION_US_TV_RATING:
10556ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    if (value >= VALUE_US_TV_G && value < RATING_REGION_TABLE_US_TV.length) {
10566ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        rating = RATING_REGION_TABLE_US_TV[value];
10576ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        ratingIndex = value;
10586ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    }
10596ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
10606ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DIMENSION_US_TV_D:
10616ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    if (value == 1
10626ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            && (ratingIndex == VALUE_US_TV_PG || ratingIndex == VALUE_US_TV_14)) {
10636ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        // US_TV_D is applicable to US_TV_PG and US_TV_14
10646ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]);
10656ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    }
10666ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
10676ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DIMENSION_US_TV_L:
10686ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DIMENSION_US_TV_S:
10696ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DIMENSION_US_TV_V:
10706ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    if (value == 1
10716ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            && ratingIndex >= VALUE_US_TV_PG
10726ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            && ratingIndex <= VALUE_US_TV_MA) {
10736ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        // US_TV_L, US_TV_S, and US_TV_V are applicable to
10746ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        // US_TV_PG, US_TV_14 and US_TV_MA
10756ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]);
10766ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    }
10776ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
10786ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DIMENSION_US_TV_Y:
10796ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    if (rating == null) {
10806ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        if (value == VALUE_US_TV_Y) {
10816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            rating = STRING_US_TV_Y;
10826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        } else if (value == VALUE_US_TV_Y7) {
10836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            rating = STRING_US_TV_Y7;
10841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        }
10856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    }
10866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
10876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DIMENSION_US_TV_FV:
10886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    if (STRING_US_TV_Y7.equals(rating) && value == 1) {
10896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        // US_TV_FV is applicable to US_TV_Y7
10906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        subratings.add(STRING_US_TV_FV);
10916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    }
10926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
10936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DIMENSION_US_MV_RATING:
10946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    if (value >= VALUE_US_MV_G && value <= VALUE_US_MV_X) {
10956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        if (value == VALUE_US_MV_X) {
10966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            // US_MV_X was replaced by US_MV_NC17 in 1990,
10976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            // and it's not supported by TvContentRating
10986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            value = VALUE_US_MV_NC17;
10996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        }
11006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        if (rating != null) {
11016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            // According to Table 3 of ANSI-CEA-766-D,
11026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            // DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING shall not be
11036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            // present in the same descriptor.
11046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            Log.w(
11056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                    TAG,
11066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                    "DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING are "
11076ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                            + "present in the same descriptor");
11086ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        } else {
11096ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            return TvContentRating.createRating(
11106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                            RATING_DOMAIN,
11116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                            RATING_REGION_RATING_SYSTEM_US_MV,
11126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                            RATING_REGION_TABLE_US_MV[value - 2])
11136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                    .flattenToString();
11141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        }
11151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    }
11166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
11176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
11186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                default:
11196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
11201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
11211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
11226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (rating == null) {
11236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return null;
11246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
11256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
11266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        String[] subratingArray = subratings.toArray(new String[subratings.size()]);
11276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return TvContentRating.createRating(
11286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_US_TV, rating, subratingArray)
11296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                .flattenToString();
11306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
11316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
11326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
11336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Gets KR(South Korea) content rating.
11346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     *
11356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * @param ratingRegion a {@link RatingRegion} instance which may contain rating information.
11366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * @return A string representing the KR content rating. The format of the string is defined in
11376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     *     {@link TvContentRating}. null, if no such a string exists.
11386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
11396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static String getKrRating(RatingRegion ratingRegion) {
11406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (ratingRegion.getName() != RATING_REGION_KR_TV) {
11416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return null;
11426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
11436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<RegionalRating> regionalRatings = ratingRegion.getRegionalRatings();
11446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        String rating = null;
11456ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (RegionalRating index : regionalRatings) {
11466ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (index.getDimension() == 0
11476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    && index.getRating() >= 0
11486ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    && index.getRating() < RATING_REGION_TABLE_KR_TV.length) {
11496ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                rating = RATING_REGION_TABLE_KR_TV[index.getRating()];
11506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                break;
11516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
11526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
11536ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (rating == null) {
11546ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return null;
11556ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
11566ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return TvContentRating.createRating(
11576ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_KR_TV, rating)
11586ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                .flattenToString();
11591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
11601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
11611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static String generateBroadcastGenre(List<TsDescriptor> descriptors) {
11621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (TsDescriptor descriptor : descriptors) {
11631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (descriptor instanceof GenreDescriptor) {
11641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                GenreDescriptor genreDescriptor =
11651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        (GenreDescriptor) descriptor;
11661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return TextUtils.join(",", genreDescriptor.getBroadcastGenres());
11671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
11681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
11691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return null;
11701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
11711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
11721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static String generateCanonicalGenre(List<TsDescriptor> descriptors) {
11731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (TsDescriptor descriptor : descriptors) {
11741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (descriptor instanceof GenreDescriptor) {
11751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                GenreDescriptor genreDescriptor =
11761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        (GenreDescriptor) descriptor;
11771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return Genres.encode(genreDescriptor.getCanonicalGenres());
11781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
11791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
11801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return null;
11811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
11821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
11836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static List<ServiceDescriptor> generateServiceDescriptors(
11846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            List<TsDescriptor> descriptors) {
11856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<ServiceDescriptor> serviceDescriptors = new ArrayList<>();
11866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (TsDescriptor descriptor : descriptors) {
11876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (descriptor instanceof ServiceDescriptor) {
11886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                ServiceDescriptor serviceDescriptor = (ServiceDescriptor) descriptor;
11896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                serviceDescriptors.add(serviceDescriptor);
11906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
11916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
11926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return serviceDescriptors;
11936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
11946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
11956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static String generateShortEventName(List<TsDescriptor> descriptors) {
11966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (TsDescriptor descriptor : descriptors) {
11976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (descriptor instanceof ShortEventDescriptor) {
11986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                ShortEventDescriptor shortEventDescriptor = (ShortEventDescriptor) descriptor;
11996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                return shortEventDescriptor.getEventName();
12006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
12016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
12026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return "";
12036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
12046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
12051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static List<TsDescriptor> parseDescriptors(byte[] data, int offset, int limit) {
12061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For details of the structure for descriptors, see ATSC A/65 Section 6.9.
12071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<TsDescriptor> descriptors = new ArrayList<>();
12081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (data.length < limit) {
12091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return descriptors;
12101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
12111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int pos = offset;
12121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        while (pos + 1 < limit) {
12131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int tag = data[pos] & 0xff;
12141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int length = data[pos + 1] & 0xff;
12151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (length <= 0) {
12161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
12171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
12181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (limit < pos + length + 2) {
12191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
12201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
12211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (DEBUG) {
12221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.d(TAG, String.format("Descriptor tag: %02x", tag));
12231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
12241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            TsDescriptor descriptor = null;
12251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            switch (tag) {
12261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case DESCRIPTOR_TAG_CONTENT_ADVISORY:
12271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    descriptor = parseContentAdvisory(data, pos, pos + length + 2);
12281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
12291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
12301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case DESCRIPTOR_TAG_CAPTION_SERVICE:
12311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    descriptor = parseCaptionService(data, pos, pos + length + 2);
12321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
12331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
12341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME:
12351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    descriptor = parseLongChannelName(data, pos, pos + length + 2);
12361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
12371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
12381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case DESCRIPTOR_TAG_GENRE:
12391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    descriptor = parseGenre(data, pos, pos + length + 2);
12401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
12411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
12421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case DESCRIPTOR_TAG_AC3_AUDIO_STREAM:
12431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    descriptor = parseAc3AudioStream(data, pos, pos + length + 2);
12441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
12451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
12461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case DESCRIPTOR_TAG_ISO639LANGUAGE:
12471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    descriptor = parseIso639Language(data, pos, pos + length + 2);
12481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
12491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
12506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DVB_DESCRIPTOR_TAG_SERVICE:
12516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    descriptor = parseDvbService(data, pos, pos + length + 2);
12526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
12536ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
12546ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DVB_DESCRIPTOR_TAG_SHORT_EVENT:
12556ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    descriptor = parseDvbShortEvent(data, pos, pos + length + 2);
12566ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
12576ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
12586ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DVB_DESCRIPTOR_TAG_CONTENT:
12596ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    descriptor = parseDvbContent(data, pos, pos + length + 2);
12606ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
12616ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
12626ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case DVB_DESCRIPTOR_TAG_PARENTAL_RATING:
12636ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    descriptor = parseDvbParentalRating(data, pos, pos + length + 2);
12646ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    break;
12656ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
12661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                default:
12671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
12681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (descriptor != null) {
12691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
12701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, "Descriptor parsed: " + descriptor);
12711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
12721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                descriptors.add(descriptor);
12731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
12741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += length + 2;
12751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
12761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return descriptors;
12771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
12781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
12791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static Iso639LanguageDescriptor parseIso639Language(byte[] data, int pos, int limit) {
12801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of the structure of ISO 639 language descriptor,
12811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // see ISO13818-1 second edition Section 2.6.18.
12821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        pos += 2;
12831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<AtscAudioTrack> audioTracks = new ArrayList<>();
12841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        while (pos + 4 <= limit) {
12851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (limit <= pos + 3) {
12861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken Iso639Language.");
12871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return null;
12881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
12891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            String language = new String(data, pos, 3);
12901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int audioType = data[pos + 3] & 0xff;
12911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            AtscAudioTrack audioTrack = new AtscAudioTrack();
12921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            audioTrack.language = language;
12931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            audioTrack.audioType = audioType;
12941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            audioTracks.add(audioTrack);
12951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 4;
12961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
12971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return new Iso639LanguageDescriptor(audioTracks);
12981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
12991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
13001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static CaptionServiceDescriptor parseCaptionService(byte[] data, int pos, int limit) {
13011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of the structure of caption service descriptor,
13021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // see ATSC A/65 Section 6.9.2.
13031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (limit <= pos + 2) {
13041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken CaptionServiceDescriptor.");
13051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return null;
13061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
13071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<AtscCaptionTrack> services = new ArrayList<>();
13081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        pos += 2;
13091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int numberServices = data[pos] & 0x1f;
13101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ++pos;
13111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (limit < pos + numberServices * 6) {
13121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken CaptionServiceDescriptor.");
13131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return null;
13141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
13151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < numberServices; ++i) {
13161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            String language = new String(Arrays.copyOfRange(data, pos, pos + 3));
13171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 3;
13181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            boolean ccType = (data[pos] & 0x80) != 0;
13191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (!ccType) {
13206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                pos +=3;
13211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                continue;
13221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
13231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int captionServiceNumber = data[pos] & 0x3f;
13241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ++pos;
13251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            boolean easyReader = (data[pos] & 0x80) != 0;
13261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            boolean wideAspectRatio = (data[pos] & 0x40) != 0;
13271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            byte[] reserved = new byte[2];
13281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            reserved[0] = (byte) (data[pos] << 2);
13291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            reserved[0] |= (byte) ((data[pos + 1] & 0xc0) >>> 6);
13301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            reserved[1] = (byte) ((data[pos + 1] & 0x3f) << 2);
13311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 2;
13321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            AtscCaptionTrack captionTrack = new AtscCaptionTrack();
13331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            captionTrack.language = language;
13341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            captionTrack.serviceNumber = captionServiceNumber;
13351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            captionTrack.easyReader = easyReader;
13361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            captionTrack.wideAspectRatio = wideAspectRatio;
13371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            services.add(captionTrack);
13381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
13391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return new CaptionServiceDescriptor(services);
13401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
13411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
13421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static ContentAdvisoryDescriptor parseContentAdvisory(byte[] data, int pos, int limit) {
13431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For details of the structure for content advisory descriptor, see A/65 Table 6.27.
13441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (limit <= pos + 2) {
13451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken ContentAdvisory");
13461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return null;
13471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
13481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int count = data[pos + 2] & 0x3f;
13491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        pos += 3;
13501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        List<RatingRegion> ratingRegions = new ArrayList<>();
13511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < count; ++i) {
13521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (limit <= pos + 1) {
13531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken ContentAdvisory");
13541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return null;
13551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
13561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            List<RegionalRating> indices = new ArrayList<>();
13571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int ratingRegion = data[pos] & 0xff;
13581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int dimensionCount = data[pos + 1] & 0xff;
13591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 2;
13606ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int previousDimension = -1;
13611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            for (int j = 0; j < dimensionCount; ++j) {
13621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (limit <= pos + 1) {
13631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.e(TAG, "Broken ContentAdvisory");
13641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    return null;
13651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
13661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int dimensionIndex = data[pos] & 0xff;
13671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int ratingValue = data[pos + 1] & 0x0f;
13686ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                if (dimensionIndex <= previousDimension) {
13696ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // According to Table 6.27 of ATSC A65,
13706ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // the indices shall be in increasing order.
13716ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    Log.e(TAG, "Broken ContentAdvisory");
13726ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    return null;
13736ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                }
13746ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                previousDimension = dimensionIndex;
13751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                pos += 2;
13761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                indices.add(new RegionalRating(dimensionIndex, ratingValue));
13771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
13781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (limit <= pos) {
13791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken ContentAdvisory");
13801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return null;
13811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
13821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int ratingDescriptionLength = data[pos] & 0xff;
13831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ++pos;
13841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (limit < pos + ratingDescriptionLength) {
13851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken ContentAdvisory");
13861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return null;
13871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
13881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            String ratingDescription = extractText(data, pos);
13891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += ratingDescriptionLength;
13901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ratingRegions.add(new RatingRegion(ratingRegion, ratingDescription, indices));
13911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
13921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return new ContentAdvisoryDescriptor(ratingRegions);
13931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
13941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
13951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static ExtendedChannelNameDescriptor parseLongChannelName(byte[] data, int pos,
13961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int limit) {
13971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (limit <= pos + 2) {
13981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken ExtendedChannelName.");
13991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return null;
14001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
14011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        pos += 2;
14021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        String text = extractText(data, pos);
14031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (text == null) {
14041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken ExtendedChannelName.");
14051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return null;
14061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
14071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return new ExtendedChannelNameDescriptor(text);
14081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
14091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
14101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static GenreDescriptor parseGenre(byte[] data, int pos, int limit) {
14111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        pos += 2;
14121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int attributeCount = data[pos] & 0x1f;
14131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (limit <= pos + attributeCount) {
14141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken Genre.");
14151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return null;
14161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
14171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        HashSet<String> broadcastGenreSet = new HashSet<>();
14181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        HashSet<String> canonicalGenreSet = new HashSet<>();
14191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < attributeCount; ++i) {
14201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ++pos;
14211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int genreCode = data[pos] & 0xff;
14221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (genreCode < BROADCAST_GENRES_TABLE.length) {
14231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                String broadcastGenre = BROADCAST_GENRES_TABLE[genreCode];
14241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (broadcastGenre != null && !broadcastGenreSet.contains(broadcastGenre)) {
14251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    broadcastGenreSet.add(broadcastGenre);
14261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
14271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
14281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (genreCode < CANONICAL_GENRES_TABLE.length) {
14291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                String canonicalGenre = CANONICAL_GENRES_TABLE[genreCode];
14301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (canonicalGenre != null && !canonicalGenreSet.contains(canonicalGenre)) {
14311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    canonicalGenreSet.add(canonicalGenre);
14321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
14331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
14341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
14351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return new GenreDescriptor(broadcastGenreSet.toArray(new String[broadcastGenreSet.size()]),
14361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                canonicalGenreSet.toArray(new String[canonicalGenreSet.size()]));
14371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
14381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
14391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static TsDescriptor parseAc3AudioStream(byte[] data, int pos, int limit) {
14401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For details of the AC3 audio stream descriptor, see A/52 Table A4.1.
14411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (limit <= pos + 5) {
14421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken AC3 audio stream descriptor.");
14431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return null;
14441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
14451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        pos += 2;
14461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte sampleRateCode = (byte) ((data[pos] & 0xe0) >> 5);
14471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte bsid = (byte) (data[pos] & 0x1f);
14481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ++pos;
14491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte bitRateCode = (byte) ((data[pos] & 0xfc) >> 2);
14501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte surroundMode = (byte) (data[pos] & 0x03);
14511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ++pos;
14521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte bsmod = (byte) ((data[pos] & 0xe0) >> 5);
14531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int numChannels = (data[pos] & 0x1e) >> 1;
14541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        boolean fullSvc = (data[pos] & 0x01) != 0;
14551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ++pos;
14561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte langCod = data[pos];
14571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte langCod2 = 0;
14581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (numChannels == 0) {
14591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (limit <= pos) {
14601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken AC3 audio stream descriptor.");
14611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return null;
14621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
14631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ++pos;
14641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            langCod2 = data[pos];
14651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
14661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (limit <= pos + 1) {
14671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.e(TAG, "Broken AC3 audio stream descriptor.");
14681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return null;
14691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
14701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte mainId = 0;
14711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte priority = 0;
14721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte asvcflags = 0;
14731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ++pos;
14741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (bsmod < 2) {
14751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mainId = (byte) ((data[pos] & 0xe0) >> 5);
14761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            priority = (byte) ((data[pos] & 0x18) >> 3);
14771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if ((data[pos] & 0x07) != 0x07) {
14781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken AC3 audio stream descriptor reserved failed");
14791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return null;
14801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
14811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else {
14821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            asvcflags = data[pos];
14831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
14841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
14851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // See A/52B Table A3.6 num_channels.
14861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int numEncodedChannels;
14871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        switch (numChannels) {
14881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 1:
14891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 8:
14901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                numEncodedChannels = 1;
14911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
14921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 2:
14931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 9:
14941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                numEncodedChannels = 2;
14951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
14961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 3:
14971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 4:
14981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 10:
14991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                numEncodedChannels = 3;
15001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
15011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 5:
15021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 6:
15031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 11:
15041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                numEncodedChannels = 4;
15051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
15061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 7:
15071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 12:
15081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                numEncodedChannels = 5;
15091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
15101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case 13:
15111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                numEncodedChannels = 6;
15121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
15131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            default:
15141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                numEncodedChannels = 0;
15151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
15161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
15171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
15181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (limit <= pos + 1) {
15191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            Log.w(TAG, "Missing text and language fields on AC3 audio stream descriptor.");
15201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return new Ac3AudioDescriptor(sampleRateCode, bsid, bitRateCode, surroundMode, bsmod,
15211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    numEncodedChannels, fullSvc, langCod, langCod2, mainId, priority, asvcflags,
15221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    null, null, null);
15231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
15241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ++pos;
15251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int textLen = (data[pos] & 0xfe) >> 1;
15261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        boolean textCode = (data[pos] & 0x01) != 0;
15271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ++pos;
15281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        String text = "";
15291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (textLen > 0) {
15301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (limit < pos + textLen) {
15311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken AC3 audio stream descriptor");
15321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return null;
15331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
15341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (textCode) {
15351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                text = new String(data, pos, textLen);
15361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            } else {
15371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                text = new String(data, pos, textLen, Charset.forName("UTF-16"));
15381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
15391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += textLen;
15401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
15411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        String language = null;
15421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        String language2 = null;
15431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (pos < limit) {
15441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // Many AC3 audio stream descriptors skip the language fields.
15451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            boolean languageFlag1 = (data[pos] & 0x80) != 0;
15461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            boolean languageFlag2 = (data[pos] & 0x40) != 0;
15471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if ((data[pos] & 0x3f) != 0x3f) {
15481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken AC3 audio stream descriptor");
15491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return null;
15501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
15511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (pos + (languageFlag1 ? 3 : 0) + (languageFlag2 ? 3 : 0) > limit) {
15521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken AC3 audio stream descriptor");
15531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return null;
15541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
15551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ++pos;
15561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (languageFlag1) {
15571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                language = new String(data, pos, 3);
15581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                pos += 3;
15591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
15601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (languageFlag2) {
15611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                language2 = new String(data, pos, 3);
15621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
15631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
15641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
15651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return new Ac3AudioDescriptor(sampleRateCode, bsid, bitRateCode, surroundMode, bsmod,
15661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                numEncodedChannels, fullSvc, langCod, langCod2, mainId, priority, asvcflags, text,
15671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                language, language2);
15681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
15691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
15706ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static TsDescriptor parseDvbService(byte[] data, int pos, int limit) {
15716ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // For details of DVB service descriptors, see DVB Document A038 Table 86.
15726ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (limit < pos + 5) {
15736ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.e(TAG, "Broken service descriptor.");
15746ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return null;
15756ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
15766ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        pos += 2;
15776ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int serviceType = data[pos] & 0xff;
15786ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        pos++;
15796ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int serviceProviderNameLength = data[pos] & 0xff;
15806ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        pos++;
15816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        String serviceProviderName = extractTextFromDvb(data, pos, serviceProviderNameLength);
15826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        pos += serviceProviderNameLength;
15836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int serviceNameLength = data[pos] & 0xff;
15846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        pos++;
15856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        String serviceName = extractTextFromDvb(data, pos, serviceNameLength);
15866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return new ServiceDescriptor(serviceType, serviceProviderName, serviceName);
15876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
15886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
15896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static TsDescriptor parseDvbShortEvent(byte[] data, int pos, int limit) {
15906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // For details of DVB service descriptors, see DVB Document A038 Table 91.
15916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (limit < pos + 7) {
15926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.e(TAG, "Broken short event descriptor.");
15936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return null;
15946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
15956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        pos += 2;
15966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        String language = new String(data, pos, 3);
15976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int eventNameLength = data[pos + 3] & 0xff;
15986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        pos += 4;
15996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (pos + eventNameLength > limit) {
16006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.e(TAG, "Broken short event descriptor.");
16016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return null;
16026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
16036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        String eventName = new String(data, pos, eventNameLength);
16046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        pos += eventNameLength;
16056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int textLength = data[pos] & 0xff;
16066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (pos + textLength > limit) {
16076ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.e(TAG, "Broken short event descriptor.");
16086ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return null;
16096ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
16106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        pos++;
16116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        String text = new String(data, pos, textLength);
16126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return new ShortEventDescriptor(language, eventName, text);
16136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
16146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
16156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static TsDescriptor parseDvbContent(byte[] data, int pos, int limit) {
16166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // TODO: According to DVB Document A038 Table 27 to add a parser for content descriptor to
16176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // get content genre.
16186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return null;
16196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
16206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
16216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static TsDescriptor parseDvbParentalRating(byte[] data, int pos, int limit) {
16226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // For details of DVB service descriptors, see DVB Document A038 Table 81.
16236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        HashMap<String, Integer> ratings = new HashMap<>();
16246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        pos += 2;
16256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        while (pos + 4 <= limit) {
16266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            String countryCode = new String(data, pos, 3);
16276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            int rating = data[pos + 3] & 0xff;
16286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            pos += 4;
16296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (rating > 15) {
16306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                // Rating > 15 means that the ratings is defined by broadcaster.
16316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                continue;
16326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
16336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            ratings.put(countryCode, rating + 3);
16346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
16356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return new ParentalRatingDescriptor(ratings);
16366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
16376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
16381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static int getShortNameSize(byte[] data, int offset) {
16391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < MAX_SHORT_NAME_BYTES; i += 2) {
16401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (data[offset + i] == 0 && data[offset + i + 1] == 0) {
16411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return i;
16421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
16431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
16441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return MAX_SHORT_NAME_BYTES;
16451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
16461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
16471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static String extractText(byte[] data, int pos) {
16481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (data.length < pos)  {
16491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return null;
16501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
16511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int numStrings = data[pos] & 0xff;
16521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        pos++;
16531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < numStrings; ++i) {
16541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (data.length <= pos + 3) {
16551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                Log.e(TAG, "Broken text.");
16561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return null;
16571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
16581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int numSegments = data[pos + 3] & 0xff;
16591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 4;
16601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            for (int j = 0; j < numSegments; ++j) {
16611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (data.length <= pos + 2) {
16621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.e(TAG, "Broken text.");
16631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    return null;
16641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
16651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int compressionType = data[pos] & 0xff;
16661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int mode = data[pos + 1] & 0xff;
16671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int numBytes = data[pos + 2] & 0xff;
16681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (data.length < pos + 3 + numBytes) {
16691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.e(TAG, "Broken text.");
16701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    return null;
16711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
16721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                byte[] bytes = Arrays.copyOfRange(data, pos + 3, pos + 3 + numBytes);
16731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (compressionType == COMPRESSION_TYPE_NO_COMPRESSION) {
16741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    try {
16751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        switch (mode) {
16761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            case MODE_SELECTED_UNICODE_RANGE_1:
16771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                return new String(bytes, "ISO-8859-1");
16781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            case MODE_SCSU:
16791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                return UnicodeDecompressor.decompress(bytes);
16801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            case MODE_UTF16:
16811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                return new String(bytes, "UTF-16");
16821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        }
16831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    } catch (UnsupportedEncodingException e) {
16841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        Log.e(TAG, "Unsupported text format.", e);
16851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    }
16861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
16871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                pos += 3 + numBytes;
16881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
16891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
16901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return null;
16911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
16921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
16936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static String extractTextFromDvb(byte[] data, int pos, int length) {
16946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // For details of DVB character set selection, see DVB Document A038 Annex A.
16956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (data.length < pos + length) {
16966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return null;
16976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
16986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        try {
16996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            String charsetPrefix = "ISO-8859-";
17006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            switch (data[0]) {
17016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x01:
17026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x02:
17036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x03:
17046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x04:
17056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x05:
17066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x06:
17076ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x07:
17086ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x09:
17096ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x0A:
17106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x0B:
17116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    String charset = charsetPrefix + String.valueOf(data[0] & 0xff + 4);
17126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    return new String(data, pos, length, charset);
17136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x10:
17146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    if (length < 3) {
17156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        Log.e(TAG, "Broken DVB text");
17166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        return null;
17176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    }
17186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    int codeTable = data[pos + 2] & 0xff;
17196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    if (data[pos + 1] == 0 && codeTable > 0 && codeTable < 15) {
17206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        return new String(
17216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                data, pos, length, charsetPrefix + String.valueOf(codeTable));
17226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    } else {
17236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        return new String(data, pos, length, "ISO-8859-1");
17246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    }
17256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x11:
17266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x14:
17276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x15:
17286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    return new String(data, pos, length, "UTF-16BE");
17296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x12:
17306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    return new String(data, pos, length, "EUC-KR");
17316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                case 0x13:
17326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    return new String(data, pos, length, "GB2312");
17336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                default:
17346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    return new String(data, pos, length, "ISO-8859-1");
17356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
17366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        } catch (UnsupportedEncodingException e) {
17376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.e(TAG, "Unsupported text format.", e);
17386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
17396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return new String(data, pos, length);
17406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
17416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
17421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static boolean checkSanity(byte[] data) {
17431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (data.length <= 1) {
17441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return false;
17451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
17461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        boolean hasCRC = (data[1] & 0x80) != 0; // section_syntax_indicator
17471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (hasCRC) {
17481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int crc = 0xffffffff;
17491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            for(byte b : data) {
17501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int index = ((crc >> 24) ^ (b & 0xff)) & 0xff;
17511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                crc = CRC_TABLE[index] ^ (crc << 8);
17521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
17531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if(crc != 0){
17541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return false;
17551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
17561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
17571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return true;
17581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
17591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko}
1760