1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.samples.sampletunertvinput; 18 19 import android.util.Log; 20 21 import java.nio.charset.StandardCharsets; 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.List; 25 import java.util.Locale; 26 27 /** Parser for ATSC PSIP sections */ 28 public class SampleTunerTvInputSectionParser { 29 private static final String TAG = "SampleTunerTvInput"; 30 private static final boolean DEBUG = true; 31 32 public static final byte DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME = (byte) 0xa0; 33 public static final byte COMPRESSION_TYPE_NO_COMPRESSION = (byte) 0x00; 34 public static final byte MODE_UTF16 = (byte) 0x3f; 35 36 /** 37 * Parses a single TVCT section, as defined in A/65 6.4 38 * @param data, a ByteBuffer containing a single TVCT section which describes only one channel 39 * @return null if there is an error while parsing, the channel with parsed data otherwise 40 */ parseTvctSection(byte[] data)41 public static TvctChannelInfo parseTvctSection(byte[] data) { 42 if (!checkValidPsipSection(data)) { 43 return null; 44 } 45 int numChannels = data[9] & 0xff; 46 if(numChannels != 1) { 47 Log.e(TAG, "parseTVCTSection expected 1 channel, found " + numChannels); 48 return null; 49 } 50 // TVCT Sections are a minimum of 16 bytes, with a minimum of 32 bytes per channel 51 if(data.length < 48) { 52 Log.e(TAG, "parseTVCTSection found section under minimum length"); 53 return null; 54 } 55 56 // shortName begins at data[10] and ends at either the first stuffing 57 // UTF-16 character of value 0x0000, or at a length of 14 Bytes 58 int shortNameLength = 14; 59 for(int i = 0; i < 14; i += 2) { 60 int charValue = ((data[10 + i] & 0xff) << 8) | (data[10 + (i + 1)] & 0xff); 61 if (charValue == 0x0000) { 62 shortNameLength = i; 63 break; 64 } 65 } 66 // Data field positions are as defined by A/65 Section 6.4 for one channel 67 String name = new String(Arrays.copyOfRange(data, 10, 10 + shortNameLength), 68 StandardCharsets.UTF_16); 69 int majorNumber = ((data[24] & 0x0f) << 6) | ((data[25] & 0xff) >> 2); 70 int minorNumber = ((data[25] & 0x03) << 8) | (data[26] & 0xff); 71 if (DEBUG) { 72 Log.d(TAG, "parseTVCTSection found shortName: " + name 73 + " channel number: " + majorNumber + "-" + minorNumber); 74 } 75 int descriptorsLength = ((data[40] & 0x03) << 8) | (data[41] & 0xff); 76 List<TsDescriptor> descriptors = parseDescriptors(data, 42, 42 + descriptorsLength); 77 for (TsDescriptor descriptor : descriptors) { 78 if (descriptor instanceof ExtendedChannelNameDescriptor) { 79 ExtendedChannelNameDescriptor longNameDescriptor = 80 (ExtendedChannelNameDescriptor)descriptor; 81 name = longNameDescriptor.getLongChannelName(); 82 if (DEBUG) { 83 Log.d(TAG, "parseTVCTSection found longName: " + name); 84 } 85 } 86 } 87 88 return new TvctChannelInfo(name, majorNumber, minorNumber); 89 } 90 91 /** 92 * Parses a single EIT section, as defined in ATSC A/65 Section 6.5 93 * @param data, a byte array containing a single EIT section which describes only one event 94 * @return {@code null} if there is an error while parsing, the event with parsed data otherwise 95 */ parseEitSection(byte[] data)96 public static EitEventInfo parseEitSection(byte[] data) { 97 if (!checkValidPsipSection(data)) { 98 return null; 99 } 100 int numEvents = data[9] & 0xff; 101 if(numEvents != 1) { 102 Log.e(TAG, "parseEitSection expected 1 event, found " + numEvents); 103 return null; 104 } 105 // EIT Sections are a minimum of 14 bytes, with a minimum of 12 bytes per event 106 if(data.length < 26) { 107 Log.e(TAG, "parseEitSection found section under minimum length"); 108 return null; 109 } 110 111 // Data field positions are as defined by A/65 Section 6.5 for one event 112 int lengthInSeconds = ((data[16] & 0x0f) << 16) | ((data[17] & 0xff) << 8) 113 | (data[18] & 0xff); 114 int titleLength = data[19] & 0xff; 115 String titleText = parseMultipleStringStructure(data, 20, 20 + titleLength); 116 117 if (DEBUG) { 118 Log.d(TAG, "parseEitSection found titleText: " + titleText 119 + " lengthInSeconds: " + lengthInSeconds); 120 } 121 return new EitEventInfo(titleText, lengthInSeconds); 122 } 123 124 125 // Descriptor data structure defined in ISO/IEC 13818-1 Section 2.6 126 // Returns an empty list on parsing failures parseDescriptors(byte[] data, int offset, int limit)127 private static List<TsDescriptor> parseDescriptors(byte[] data, int offset, int limit) { 128 List<TsDescriptor> descriptors = new ArrayList<>(); 129 if (data.length < limit) { 130 Log.e(TAG, "parseDescriptors given limit larger than data"); 131 return descriptors; 132 } 133 int pos = offset; 134 while (pos + 1 < limit) { 135 int tag = data[pos] & 0xff; 136 int length = data[pos + 1] & 0xff; 137 if (length <= 0) { 138 continue; 139 } 140 pos += 2; 141 142 if (limit < pos + length) { 143 Log.e(TAG, "parseDescriptors found descriptor with length longer than limit"); 144 break; 145 } 146 if (DEBUG) { 147 Log.d(TAG, "parseDescriptors found descriptor with tag: " + tag); 148 } 149 TsDescriptor descriptor = null; 150 switch ((byte) tag) { 151 case DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME: 152 descriptor = parseExtendedChannelNameDescriptor(data, pos, pos + length); 153 break; 154 default: 155 break; 156 } 157 if (descriptor != null) { 158 descriptors.add(descriptor); 159 } 160 pos += length; 161 } 162 return descriptors; 163 } 164 165 // ExtendedChannelNameDescriptor is defined in ATSC A/65 Section 6.9.4 as containing only 166 // a single MultipleStringStructure after its tag and length. 167 // @return {@code null} if parsing MultipleStringStructure fails parseExtendedChannelNameDescriptor(byte[] data, int offset, int limit)168 private static ExtendedChannelNameDescriptor parseExtendedChannelNameDescriptor(byte[] data, 169 int offset, int limit) { 170 String channelName = parseMultipleStringStructure(data, offset, limit); 171 return channelName == null ? null : new ExtendedChannelNameDescriptor(channelName); 172 } 173 174 // MultipleStringStructure is defined in ATSC A/65 Section 6.10 175 // Returns first string segment with supported compression and mode 176 // @return {@code null} on invalid data or no supported string segments parseMultipleStringStructure(byte[] data, int offset, int limit)177 private static String parseMultipleStringStructure(byte[] data, int offset, int limit) { 178 if (limit < offset + 8) { 179 Log.e(TAG, "parseMultipleStringStructure given too little data"); 180 return null; 181 } 182 183 int numStrings = data[offset] & 0xff; 184 if (numStrings <= 0) { 185 Log.e(TAG, "parseMultipleStringStructure found no strings"); 186 return null; 187 } 188 int pos = offset + 1; 189 for (int i = 0; i < numStrings; i++) { 190 if (limit < pos + 4) { 191 Log.e(TAG, "parseMultipleStringStructure ran out of data"); 192 return null; 193 } 194 int numSegments = data[pos + 3] & 0xff; 195 pos += 4; 196 for (int j = 0; j < numSegments; j++) { 197 if (limit < pos + 3) { 198 Log.e(TAG, "parseMultipleStringStructure ran out of data"); 199 return null; 200 } 201 int compressionType = data[pos] & 0xff; 202 int mode = data[pos + 1] & 0xff; 203 int numBytes = data[pos + 2] & 0xff; 204 pos += 3; 205 if (data.length < pos + numBytes) { 206 Log.e(TAG, "parseMultipleStringStructure ran out of data"); 207 return null; 208 } 209 if (compressionType == COMPRESSION_TYPE_NO_COMPRESSION && mode == MODE_UTF16) { 210 return new String(data, pos, numBytes, StandardCharsets.UTF_16); 211 } 212 pos += numBytes; 213 } 214 } 215 216 Log.e(TAG, "parseMultipleStringStructure found no supported segments"); 217 return null; 218 } 219 checkValidPsipSection(byte[] data)220 private static boolean checkValidPsipSection(byte[] data) { 221 if (data.length < 13) { 222 Log.e(TAG, "Section was too small"); 223 return false; 224 } 225 if ((data[0] & 0xff) == 0xff) { 226 // Should clear stuffing bytes as detailed by H222.0 section 2.4.4. 227 Log.e(TAG, "Unexpected stuffing bytes while parsing section"); 228 return false; 229 } 230 int sectionLength = (((data[1] & 0x0f) << 8) | (data[2] & 0xff)) + 3; 231 if (sectionLength != data.length) { 232 Log.e(TAG, "Length mismatch while parsing section"); 233 return false; 234 } 235 int sectionNumber = data[6] & 0xff; 236 int lastSectionNumber = data[7] & 0xff; 237 if(sectionNumber > lastSectionNumber) { 238 Log.e(TAG, "Found sectionNumber > lastSectionNumber while parsing section"); 239 return false; 240 } 241 // TODO: Check CRC 32/MPEG for validity 242 return true; 243 } 244 245 // Contains the portion of the data contained in the TVCT used by 246 // our SampleTunerTvInputSetupActivity 247 public static class TvctChannelInfo { 248 private final String mChannelName; 249 private final int mMajorChannelNumber; 250 private final int mMinorChannelNumber; 251 TvctChannelInfo( String channelName, int majorChannelNumber, int minorChannelNumber)252 public TvctChannelInfo( 253 String channelName, 254 int majorChannelNumber, 255 int minorChannelNumber) { 256 mChannelName = channelName; 257 mMajorChannelNumber = majorChannelNumber; 258 mMinorChannelNumber = minorChannelNumber; 259 } 260 getChannelName()261 public String getChannelName() { 262 return mChannelName; 263 } 264 getMajorChannelNumber()265 public int getMajorChannelNumber() { 266 return mMajorChannelNumber; 267 } 268 getMinorChannelNumber()269 public int getMinorChannelNumber() { 270 return mMinorChannelNumber; 271 } 272 273 @Override toString()274 public String toString() { 275 return String.format( 276 Locale.US, 277 "ChannelName: %s ChannelNumber: %d-%d", 278 mChannelName, 279 mMajorChannelNumber, 280 mMinorChannelNumber); 281 } 282 } 283 284 /** 285 * Contains the portion of the data contained in the EIT used by 286 * our SampleTunerTvInputService 287 */ 288 public static class EitEventInfo { 289 private final String mEventTitle; 290 private final int mLengthSeconds; 291 EitEventInfo( String eventTitle, int lengthSeconds)292 public EitEventInfo( 293 String eventTitle, 294 int lengthSeconds) { 295 mEventTitle = eventTitle; 296 mLengthSeconds = lengthSeconds; 297 } 298 getEventTitle()299 public String getEventTitle() { 300 return mEventTitle; 301 } 302 getLengthSeconds()303 public int getLengthSeconds() { 304 return mLengthSeconds; 305 } 306 307 @Override toString()308 public String toString() { 309 return String.format( 310 Locale.US, 311 "Event Title: %s Length in Seconds: %d", 312 mEventTitle, 313 mLengthSeconds); 314 } 315 } 316 317 /** 318 * A base class for TS descriptors 319 * For details of their structure, see ATSC A/65 Section 6.9 320 */ 321 public abstract static class TsDescriptor { getTag()322 public abstract int getTag(); 323 } 324 325 public static class ExtendedChannelNameDescriptor extends TsDescriptor { 326 private final String mLongChannelName; 327 ExtendedChannelNameDescriptor(String longChannelName)328 public ExtendedChannelNameDescriptor(String longChannelName) { 329 mLongChannelName = longChannelName; 330 } 331 332 @Override getTag()333 public int getTag() { 334 return DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME; 335 } 336 getLongChannelName()337 public String getLongChannelName() { 338 return mLongChannelName; 339 } 340 } 341 } 342