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