From 6ebde20b03db4c0d57f67acaac11832b610b966b Mon Sep 17 00:00:00 2001 From: Nick Chalko Date: Tue, 3 Oct 2017 10:16:37 -0700 Subject: Sync to match Live Channels 1.15(ncis) aka ub-tv-dev at a73a150bb7d0d1ce867ef980c6ac8411899d40ad Bug: 64021596 Change-Id: I7c544fd15e2c58784f8babc31877ad0dfeebb4c0 (cherry picked from commit 633eb826b8c97731dfc5ef12c7bf78a63734275d) --- src/com/android/tv/tuner/ts/SectionParser.java | 569 +++++++++++++++++++++++-- 1 file changed, 532 insertions(+), 37 deletions(-) (limited to 'src/com/android/tv/tuner/ts/SectionParser.java') diff --git a/src/com/android/tv/tuner/ts/SectionParser.java b/src/com/android/tv/tuner/ts/SectionParser.java index 8c1f6a1b..e1f890f3 100644 --- a/src/com/android/tv/tuner/ts/SectionParser.java +++ b/src/com/android/tv/tuner/ts/SectionParser.java @@ -18,11 +18,12 @@ package com.android.tv.tuner.ts; import android.media.tv.TvContentRating; import android.media.tv.TvContract.Programs.Genres; +import android.support.annotation.VisibleForTesting; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; -import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.PsiData.PatItem; import com.android.tv.tuner.data.PsiData.PmtItem; import com.android.tv.tuner.data.PsipData.Ac3AudioDescriptor; @@ -34,24 +35,32 @@ import com.android.tv.tuner.data.PsipData.ExtendedChannelNameDescriptor; import com.android.tv.tuner.data.PsipData.GenreDescriptor; import com.android.tv.tuner.data.PsipData.Iso639LanguageDescriptor; import com.android.tv.tuner.data.PsipData.MgtItem; +import com.android.tv.tuner.data.PsipData.ParentalRatingDescriptor; import com.android.tv.tuner.data.PsipData.PsipSection; import com.android.tv.tuner.data.PsipData.RatingRegion; import com.android.tv.tuner.data.PsipData.RegionalRating; +import com.android.tv.tuner.data.PsipData.SdtItem; +import com.android.tv.tuner.data.PsipData.ServiceDescriptor; +import com.android.tv.tuner.data.PsipData.ShortEventDescriptor; import com.android.tv.tuner.data.PsipData.TsDescriptor; import com.android.tv.tuner.data.PsipData.VctItem; +import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.util.ByteArrayBuffer; +import com.android.tv.tuner.util.ConvertUtils; import com.ibm.icu.text.UnicodeDecompressor; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; +import java.util.Calendar; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Parses ATSC PSIP sections. @@ -68,6 +77,13 @@ public class SectionParser { private static final byte TABLE_ID_EIT = (byte) 0xcb; private static final byte TABLE_ID_ETT = (byte) 0xcc; + // Table id for DVB + private static final byte TABLE_ID_SDT = (byte) 0x42; + private static final byte TABLE_ID_DVB_ACTUAL_P_F_EIT = (byte) 0x4e; + private static final byte TABLE_ID_DVB_OTHER_P_F_EIT = (byte) 0x4f; + private static final byte TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT = (byte) 0x50; + private static final byte TABLE_ID_DVB_OTHER_SCHEDULE_EIT = (byte) 0x60; + // For details of the structure for the tags of descriptors, see ATSC A/65 Table 6.25. public static final int DESCRIPTOR_TAG_ISO639LANGUAGE = 0x0a; public static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86; @@ -76,6 +92,12 @@ public class SectionParser { public static final int DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME = 0xa0; public static final int DESCRIPTOR_TAG_GENRE = 0xab; + // For details of the structure for the tags of DVB descriptors, see DVB Document A038 Table 12. + public static final int DVB_DESCRIPTOR_TAG_SERVICE = 0x48; + public static final int DVB_DESCRIPTOR_TAG_SHORT_EVENT = 0X4d; + public static final int DVB_DESCRIPTOR_TAG_CONTENT = 0x54; + public static final int DVB_DESCRIPTOR_TAG_PARENTAL_RATING = 0x55; + private static final byte COMPRESSION_TYPE_NO_COMPRESSION = (byte) 0x00; private static final byte MODE_SELECTED_UNICODE_RANGE_1 = (byte) 0x00; // 0x0000 - 0x00ff private static final byte MODE_UTF16 = (byte) 0x3f; @@ -88,17 +110,57 @@ public class SectionParser { // The following values are defined in the live channels app. // See https://developer.android.com/reference/android/media/tv/TvContentRating.html. + private static final String RATING_DOMAIN = "com.android.tv"; private static final String RATING_REGION_RATING_SYSTEM_US_TV = "US_TV"; + private static final String RATING_REGION_RATING_SYSTEM_US_MV = "US_MV"; private static final String RATING_REGION_RATING_SYSTEM_KR_TV = "KR_TV"; private static final String[] RATING_REGION_TABLE_US_TV = { "US_TV_Y", "US_TV_Y7", "US_TV_G", "US_TV_PG", "US_TV_14", "US_TV_MA" }; + private static final String[] RATING_REGION_TABLE_US_MV = { + "US_MV_G", "US_MV_PG", "US_MV_PG13", "US_MV_R", "US_MV_NC17" + }; + private static final String[] RATING_REGION_TABLE_KR_TV = { "KR_TV_ALL", "KR_TV_7", "KR_TV_12", "KR_TV_15", "KR_TV_19" }; + private static final String[] RATING_REGION_TABLE_US_TV_SUBRATING = { + "US_TV_D", "US_TV_L", "US_TV_S", "US_TV_V", "US_TV_FV" + }; + + // According to ANSI-CEA-766-D + private static final int VALUE_US_TV_Y = 1; + private static final int VALUE_US_TV_Y7 = 2; + private static final int VALUE_US_TV_NONE = 1; + private static final int VALUE_US_TV_G = 2; + private static final int VALUE_US_TV_PG = 3; + private static final int VALUE_US_TV_14 = 4; + private static final int VALUE_US_TV_MA = 5; + + private static final int DIMENSION_US_TV_RATING = 0; + private static final int DIMENSION_US_TV_D = 1; + private static final int DIMENSION_US_TV_L = 2; + private static final int DIMENSION_US_TV_S = 3; + private static final int DIMENSION_US_TV_V = 4; + private static final int DIMENSION_US_TV_Y = 5; + private static final int DIMENSION_US_TV_FV = 6; + private static final int DIMENSION_US_MV_RATING = 7; + + private static final int VALUE_US_MV_G = 2; + private static final int VALUE_US_MV_PG = 3; + private static final int VALUE_US_MV_PG13 = 4; + private static final int VALUE_US_MV_R = 5; + private static final int VALUE_US_MV_NC17 = 6; + private static final int VALUE_US_MV_X = 7; + + private static final String STRING_US_TV_Y = "US_TV_Y"; + private static final String STRING_US_TV_Y7 = "US_TV_Y7"; + private static final String STRING_US_TV_FV = "US_TV_FV"; + + /* * The following CRC table is from the code generated by the following command. * $ python pycrc.py --model crc-32-mpeg --algorithm table-driven --generate c @@ -330,6 +392,7 @@ public class SectionParser { void onVctParsed(List items, int sectionNumber, int lastSectionNumber); void onEitParsed(int sourceId, List items); void onEttParsed(int sourceId, List descriptions); + void onSdtParsed(List items); } private final OutputListener mListener; @@ -367,6 +430,10 @@ public class SectionParser { mParsedEttItems.clear(); } + public void resetVersionNumbers() { + mSectionVersionMap.clear(); + } + private void parseSection(byte[] data) { if (!checkSanity(data)) { Log.d(TAG, "Bad CRC!"); @@ -410,6 +477,13 @@ public class SectionParser { case TABLE_ID_ETT: result = parseETT(data); break; + case TABLE_ID_SDT: + result = parseSDT(data); + break; + case TABLE_ID_DVB_ACTUAL_P_F_EIT: + case TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT: + result = parseDVBEIT(data); + break; default: break; } @@ -510,10 +584,8 @@ public class SectionParser { pos += 11 + descriptorsLength; results.add(new MgtItem(tableType, tableTypePid)); } - if ((data[pos] & 0xf0) != 0xf0) { - Log.e(TAG, "Broken MGT."); - return false; - } + // Skip the remaining descriptor part which we don't use. + if (mListener != null) { mListener.onMgtParsed(results); } @@ -704,6 +776,127 @@ public class SectionParser { return true; } + private boolean parseSDT(byte[] data) { + // For details of the structure for SDT, see DVB Document A038 Table 5. + if (DEBUG) { + Log.d(TAG, "SDT id discovered"); + } + if (data.length <= 11) { + Log.e(TAG, "Broken SDT."); + return false; + } + if ((data[1] & 0x80) >> 7 != 1) { + Log.e(TAG, "Broken SDT, section syntax indicator error."); + return false; + } + int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff); + int transportStreamId = ((data[3] & 0xff) << 8) | (data[4] & 0xff); + int originalNetworkId = ((data[8] & 0xff) << 8) | (data[9] & 0xff); + int pos = 11; + if (sectionLength + 3 > data.length) { + Log.e(TAG, "Broken SDT."); + } + List sdtItems = new ArrayList<>(); + while (pos + 9 < data.length) { + int serviceId = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff); + int descriptorsLength = ((data[pos + 3] & 0x0f) << 8) | (data[pos + 4] & 0xff); + pos += 5; + List descriptors = parseDescriptors(data, pos, pos + descriptorsLength); + List serviceDescriptors = generateServiceDescriptors(descriptors); + String serviceName = ""; + String serviceProviderName = ""; + int serviceType = 0; + for (ServiceDescriptor serviceDescriptor : serviceDescriptors) { + serviceName = serviceDescriptor.getServiceName(); + serviceProviderName = serviceDescriptor.getServiceProviderName(); + serviceType = serviceDescriptor.getServiceType(); + } + if (serviceDescriptors.size() > 0) { + sdtItems.add(new SdtItem(serviceName, serviceProviderName, serviceType, serviceId, + originalNetworkId)); + } + pos += descriptorsLength; + } + if (mListener != null) { + mListener.onSdtParsed(sdtItems); + } + return true; + } + + private boolean parseDVBEIT(byte[] data) { + // For details of the structure for DVB ETT, see DVB Document A038 Table 7. + if (DEBUG) { + Log.d(TAG, "DVB EIT is discovered."); + } + if (data.length < 18) { + Log.e(TAG, "Broken DVB EIT."); + return false; + } + int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff); + int sourceId = ((data[3] & 0xff) << 8) | (data[4] & 0xff); + int transportStreamId = ((data[8] & 0xff) << 8) | (data[9] & 0xff); + int originalNetworkId = ((data[10] & 0xff) << 8) | (data[11] & 0xff); + + int pos = 14; + List results = new ArrayList<>(); + while (pos + 12 < data.length) { + int eventId = ((data[pos] & 0xff) << 8) + (data[pos + 1] & 0xff); + float modifiedJulianDate = ((data[pos + 2] & 0xff) << 8) | (data[pos + 3] & 0xff); + int startYear = (int) ((modifiedJulianDate - 15078.2f) / 365.25f); + int mjdMonth = (int) ((modifiedJulianDate - 14956.1f + - (int) (startYear * 365.25f)) / 30.6001f); + int startDay = (int) modifiedJulianDate - 14956 - (int) (startYear * 365.25f) + - (int) (mjdMonth * 30.6001f); + int startMonth = mjdMonth - 1; + if (mjdMonth == 14 || mjdMonth == 15) { + startYear += 1; + startMonth -= 12; + } + int startHour = ((data[pos + 4] & 0xf0) >> 4) * 10 + (data[pos + 4] & 0x0f); + int startMinute = ((data[pos + 5] & 0xf0) >> 4) * 10 + (data[pos + 5] & 0x0f); + int startSecond = ((data[pos + 6] & 0xf0) >> 4) * 10 + (data[pos + 6] & 0x0f); + Calendar calendar = Calendar.getInstance(); + startYear += 1900; + calendar.set(startYear, startMonth, startDay, startHour, startMinute, startSecond); + long startTime = ConvertUtils.convertUnixEpochToGPSTime( + calendar.getTimeInMillis() / 1000); + int durationInSecond = (((data[pos + 7] & 0xf0) >> 4) * 10 + + (data[pos + 7] & 0x0f)) * 3600 + + (((data[pos + 8] & 0xf0) >> 4) * 10 + (data[pos + 8] & 0x0f)) * 60 + + (((data[pos + 9] & 0xf0) >> 4) * 10 + (data[pos + 9] & 0x0f)); + int descriptorsLength = ((data[pos + 10] & 0x0f) << 8) + | (data[pos + 10 + 1] & 0xff); + int descriptorsPos = pos + 10 + 2; + if (data.length < descriptorsPos + descriptorsLength) { + Log.e(TAG, "Broken EIT."); + return false; + } + List descriptors = parseDescriptors( + data, descriptorsPos, descriptorsPos + descriptorsLength); + if (DEBUG) { + Log.d(TAG, String.format("DVB EIT descriptors size: %d", descriptors.size())); + } + // TODO: Add logic to generating content rating for dvb. See DVB document 6.2.28 for + // details. Content rating here will be null + String contentRating = generateContentRating(descriptors); + // TODO: Add logic for generating genre for dvb. See DVB document 6.2.9 for details. + // Genre here will be null here. + String broadcastGenre = generateBroadcastGenre(descriptors); + String canonicalGenre = generateCanonicalGenre(descriptors); + String titleText = generateShortEventName(descriptors); + List audioTracks = generateAudioTracks(descriptors); + List captionTracks = generateCaptionTracks(descriptors); + pos += 12 + descriptorsLength; + results.add(new EitItem(EitItem.INVALID_PROGRAM_ID, eventId, titleText, + startTime, durationInSecond, contentRating, audioTracks, captionTracks, + broadcastGenre, canonicalGenre, null)); + } + if (mListener != null) { + mListener.onEitParsed(sourceId, results); + } + return true; + } + private static List generateAudioTracks(List descriptors) { // The list of audio tracks sent is located at both AC3 Audio descriptor and ISO 639 // Language descriptor. @@ -717,6 +910,9 @@ public class SectionParser { if (audioDescriptor.getLanguage() != null) { audioTrack.language = audioDescriptor.getLanguage(); } + if (audioTrack.language == null) { + audioTrack.language = ""; + } audioTrack.audioType = AtscAudioTrack.AUDIOTYPE_UNDEFINED; audioTrack.channelCount = audioDescriptor.getNumChannels(); audioTrack.sampleRate = audioDescriptor.getSampleRate(); @@ -787,44 +983,179 @@ public class SectionParser { return services; } - private static String generateContentRating(List descriptors) { - List contentRatings = new ArrayList<>(); + @VisibleForTesting + static String generateContentRating(List descriptors) { + Set contentRatings = new ArraySet<>(); + List usRatingRegions = getRatingRegions(descriptors, RATING_REGION_US_TV); + List krRatingRegions = getRatingRegions(descriptors, RATING_REGION_KR_TV); + for (RatingRegion region : usRatingRegions) { + String contentRating = getUsRating(region); + if (contentRating != null) { + contentRatings.add(contentRating); + } + } + for (RatingRegion region : krRatingRegions) { + String contentRating = getKrRating(region); + if (contentRating != null) { + contentRatings.add(contentRating); + } + } + return TextUtils.join(",", contentRatings); + } + + /** + * Gets a list of {@link RatingRegion} in the specific region. + * + * @param descriptors {@link TsDescriptor} list which may contains rating information + * @param region the specific region + * @return a list of {@link RatingRegion} in the specific region + */ + private static List getRatingRegions(List descriptors, int region) { + List ratingRegions = new ArrayList<>(); for (TsDescriptor descriptor : descriptors) { - if (descriptor instanceof ContentAdvisoryDescriptor) { - ContentAdvisoryDescriptor contentAdvisoryDescriptor = - (ContentAdvisoryDescriptor) descriptor; - for (RatingRegion ratingRegion : contentAdvisoryDescriptor.getRatingRegions()) { - for (RegionalRating index : ratingRegion.getRegionalRatings()) { - String ratingSystem = null; - String rating = null; - switch (ratingRegion.getName()) { - case RATING_REGION_US_TV: - ratingSystem = RATING_REGION_RATING_SYSTEM_US_TV; - if (index.getDimension() == 0 && index.getRating() >= 0 - && index.getRating() < RATING_REGION_TABLE_US_TV.length) { - rating = RATING_REGION_TABLE_US_TV[index.getRating()]; - } - break; - case RATING_REGION_KR_TV: - ratingSystem = RATING_REGION_RATING_SYSTEM_KR_TV; - if (index.getDimension() == 0 && index.getRating() >= 0 - && index.getRating() < RATING_REGION_TABLE_KR_TV.length) { - rating = RATING_REGION_TABLE_KR_TV[index.getRating()]; - } - break; - default: - break; + if (!(descriptor instanceof ContentAdvisoryDescriptor)) { + continue; + } + ContentAdvisoryDescriptor contentAdvisoryDescriptor = + (ContentAdvisoryDescriptor) descriptor; + for (RatingRegion ratingRegion : contentAdvisoryDescriptor.getRatingRegions()) { + if (ratingRegion.getName() == region) { + ratingRegions.add(ratingRegion); + } + } + } + return ratingRegions; + } + + /** + * Gets US content rating and subratings (if any). + * + * @param ratingRegion a {@link RatingRegion} instance which may contain rating information. + * @return A string representing the US content rating and subratings. The format of the string + * is defined in {@link TvContentRating}. null, if no such a string exists. + */ + private static String getUsRating(RatingRegion ratingRegion) { + if (ratingRegion.getName() != RATING_REGION_US_TV) { + return null; + } + List regionalRatings = ratingRegion.getRegionalRatings(); + String rating = null; + int ratingIndex = VALUE_US_TV_NONE; + List subratings = new ArrayList<>(); + for (RegionalRating index : regionalRatings) { + // See Table 3 of ANSI-CEA-766-D + int dimension = index.getDimension(); + int value = index.getRating(); + switch (dimension) { + // According to Table 6.27 of ATSC A65, + // the dimensions shall be in increasing order. + // Therefore, rating and ratingIndex are assigned before any corresponding + // subrating. + case DIMENSION_US_TV_RATING: + if (value >= VALUE_US_TV_G && value < RATING_REGION_TABLE_US_TV.length) { + rating = RATING_REGION_TABLE_US_TV[value]; + ratingIndex = value; + } + break; + case DIMENSION_US_TV_D: + if (value == 1 + && (ratingIndex == VALUE_US_TV_PG || ratingIndex == VALUE_US_TV_14)) { + // US_TV_D is applicable to US_TV_PG and US_TV_14 + subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]); + } + break; + case DIMENSION_US_TV_L: + case DIMENSION_US_TV_S: + case DIMENSION_US_TV_V: + if (value == 1 + && ratingIndex >= VALUE_US_TV_PG + && ratingIndex <= VALUE_US_TV_MA) { + // US_TV_L, US_TV_S, and US_TV_V are applicable to + // US_TV_PG, US_TV_14 and US_TV_MA + subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]); + } + break; + case DIMENSION_US_TV_Y: + if (rating == null) { + if (value == VALUE_US_TV_Y) { + rating = STRING_US_TV_Y; + } else if (value == VALUE_US_TV_Y7) { + rating = STRING_US_TV_Y7; } - if (ratingSystem != null && rating != null) { - contentRatings.add(TvContentRating - .createRating("com.android.tv", ratingSystem, rating) - .flattenToString()); + } + break; + case DIMENSION_US_TV_FV: + if (STRING_US_TV_Y7.equals(rating) && value == 1) { + // US_TV_FV is applicable to US_TV_Y7 + subratings.add(STRING_US_TV_FV); + } + break; + case DIMENSION_US_MV_RATING: + if (value >= VALUE_US_MV_G && value <= VALUE_US_MV_X) { + if (value == VALUE_US_MV_X) { + // US_MV_X was replaced by US_MV_NC17 in 1990, + // and it's not supported by TvContentRating + value = VALUE_US_MV_NC17; + } + if (rating != null) { + // According to Table 3 of ANSI-CEA-766-D, + // DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING shall not be + // present in the same descriptor. + Log.w( + TAG, + "DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING are " + + "present in the same descriptor"); + } else { + return TvContentRating.createRating( + RATING_DOMAIN, + RATING_REGION_RATING_SYSTEM_US_MV, + RATING_REGION_TABLE_US_MV[value - 2]) + .flattenToString(); } } - } + break; + + default: + break; } } - return TextUtils.join(",", contentRatings); + if (rating == null) { + return null; + } + + String[] subratingArray = subratings.toArray(new String[subratings.size()]); + return TvContentRating.createRating( + RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_US_TV, rating, subratingArray) + .flattenToString(); + } + + /** + * Gets KR(South Korea) content rating. + * + * @param ratingRegion a {@link RatingRegion} instance which may contain rating information. + * @return A string representing the KR content rating. The format of the string is defined in + * {@link TvContentRating}. null, if no such a string exists. + */ + private static String getKrRating(RatingRegion ratingRegion) { + if (ratingRegion.getName() != RATING_REGION_KR_TV) { + return null; + } + List regionalRatings = ratingRegion.getRegionalRatings(); + String rating = null; + for (RegionalRating index : regionalRatings) { + if (index.getDimension() == 0 + && index.getRating() >= 0 + && index.getRating() < RATING_REGION_TABLE_KR_TV.length) { + rating = RATING_REGION_TABLE_KR_TV[index.getRating()]; + break; + } + } + if (rating == null) { + return null; + } + return TvContentRating.createRating( + RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_KR_TV, rating) + .flattenToString(); } private static String generateBroadcastGenre(List descriptors) { @@ -849,6 +1180,28 @@ public class SectionParser { return null; } + private static List generateServiceDescriptors( + List descriptors) { + List serviceDescriptors = new ArrayList<>(); + for (TsDescriptor descriptor : descriptors) { + if (descriptor instanceof ServiceDescriptor) { + ServiceDescriptor serviceDescriptor = (ServiceDescriptor) descriptor; + serviceDescriptors.add(serviceDescriptor); + } + } + return serviceDescriptors; + } + + private static String generateShortEventName(List descriptors) { + for (TsDescriptor descriptor : descriptors) { + if (descriptor instanceof ShortEventDescriptor) { + ShortEventDescriptor shortEventDescriptor = (ShortEventDescriptor) descriptor; + return shortEventDescriptor.getEventName(); + } + } + return ""; + } + private static List parseDescriptors(byte[] data, int offset, int limit) { // For details of the structure for descriptors, see ATSC A/65 Section 6.9. List descriptors = new ArrayList<>(); @@ -894,6 +1247,22 @@ public class SectionParser { descriptor = parseIso639Language(data, pos, pos + length + 2); break; + case DVB_DESCRIPTOR_TAG_SERVICE: + descriptor = parseDvbService(data, pos, pos + length + 2); + break; + + case DVB_DESCRIPTOR_TAG_SHORT_EVENT: + descriptor = parseDvbShortEvent(data, pos, pos + length + 2); + break; + + case DVB_DESCRIPTOR_TAG_CONTENT: + descriptor = parseDvbContent(data, pos, pos + length + 2); + break; + + case DVB_DESCRIPTOR_TAG_PARENTAL_RATING: + descriptor = parseDvbParentalRating(data, pos, pos + length + 2); + break; + default: } if (descriptor != null) { @@ -948,6 +1317,7 @@ public class SectionParser { pos += 3; boolean ccType = (data[pos] & 0x80) != 0; if (!ccType) { + pos +=3; continue; } int captionServiceNumber = data[pos] & 0x3f; @@ -987,6 +1357,7 @@ public class SectionParser { int ratingRegion = data[pos] & 0xff; int dimensionCount = data[pos + 1] & 0xff; pos += 2; + int previousDimension = -1; for (int j = 0; j < dimensionCount; ++j) { if (limit <= pos + 1) { Log.e(TAG, "Broken ContentAdvisory"); @@ -994,6 +1365,13 @@ public class SectionParser { } int dimensionIndex = data[pos] & 0xff; int ratingValue = data[pos + 1] & 0x0f; + if (dimensionIndex <= previousDimension) { + // According to Table 6.27 of ATSC A65, + // the indices shall be in increasing order. + Log.e(TAG, "Broken ContentAdvisory"); + return null; + } + previousDimension = dimensionIndex; pos += 2; indices.add(new RegionalRating(dimensionIndex, ratingValue)); } @@ -1189,6 +1567,74 @@ public class SectionParser { language, language2); } + private static TsDescriptor parseDvbService(byte[] data, int pos, int limit) { + // For details of DVB service descriptors, see DVB Document A038 Table 86. + if (limit < pos + 5) { + Log.e(TAG, "Broken service descriptor."); + return null; + } + pos += 2; + int serviceType = data[pos] & 0xff; + pos++; + int serviceProviderNameLength = data[pos] & 0xff; + pos++; + String serviceProviderName = extractTextFromDvb(data, pos, serviceProviderNameLength); + pos += serviceProviderNameLength; + int serviceNameLength = data[pos] & 0xff; + pos++; + String serviceName = extractTextFromDvb(data, pos, serviceNameLength); + return new ServiceDescriptor(serviceType, serviceProviderName, serviceName); + } + + private static TsDescriptor parseDvbShortEvent(byte[] data, int pos, int limit) { + // For details of DVB service descriptors, see DVB Document A038 Table 91. + if (limit < pos + 7) { + Log.e(TAG, "Broken short event descriptor."); + return null; + } + pos += 2; + String language = new String(data, pos, 3); + int eventNameLength = data[pos + 3] & 0xff; + pos += 4; + if (pos + eventNameLength > limit) { + Log.e(TAG, "Broken short event descriptor."); + return null; + } + String eventName = new String(data, pos, eventNameLength); + pos += eventNameLength; + int textLength = data[pos] & 0xff; + if (pos + textLength > limit) { + Log.e(TAG, "Broken short event descriptor."); + return null; + } + pos++; + String text = new String(data, pos, textLength); + return new ShortEventDescriptor(language, eventName, text); + } + + private static TsDescriptor parseDvbContent(byte[] data, int pos, int limit) { + // TODO: According to DVB Document A038 Table 27 to add a parser for content descriptor to + // get content genre. + return null; + } + + private static TsDescriptor parseDvbParentalRating(byte[] data, int pos, int limit) { + // For details of DVB service descriptors, see DVB Document A038 Table 81. + HashMap ratings = new HashMap<>(); + pos += 2; + while (pos + 4 <= limit) { + String countryCode = new String(data, pos, 3); + int rating = data[pos + 3] & 0xff; + pos += 4; + if (rating > 15) { + // Rating > 15 means that the ratings is defined by broadcaster. + continue; + } + ratings.put(countryCode, rating + 3); + } + return new ParentalRatingDescriptor(ratings); + } + private static int getShortNameSize(byte[] data, int offset) { for (int i = 0; i < MAX_SHORT_NAME_BYTES; i += 2) { if (data[offset + i] == 0 && data[offset + i + 1] == 0) { @@ -1244,6 +1690,55 @@ public class SectionParser { return null; } + private static String extractTextFromDvb(byte[] data, int pos, int length) { + // For details of DVB character set selection, see DVB Document A038 Annex A. + if (data.length < pos + length) { + return null; + } + try { + String charsetPrefix = "ISO-8859-"; + switch (data[0]) { + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x09: + case 0x0A: + case 0x0B: + String charset = charsetPrefix + String.valueOf(data[0] & 0xff + 4); + return new String(data, pos, length, charset); + case 0x10: + if (length < 3) { + Log.e(TAG, "Broken DVB text"); + return null; + } + int codeTable = data[pos + 2] & 0xff; + if (data[pos + 1] == 0 && codeTable > 0 && codeTable < 15) { + return new String( + data, pos, length, charsetPrefix + String.valueOf(codeTable)); + } else { + return new String(data, pos, length, "ISO-8859-1"); + } + case 0x11: + case 0x14: + case 0x15: + return new String(data, pos, length, "UTF-16BE"); + case 0x12: + return new String(data, pos, length, "EUC-KR"); + case 0x13: + return new String(data, pos, length, "GB2312"); + default: + return new String(data, pos, length, "ISO-8859-1"); + } + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Unsupported text format.", e); + } + return new String(data, pos, length); + } + private static boolean checkSanity(byte[] data) { if (data.length <= 1) { return false; -- cgit v1.2.3