1 /*
2  * Copyright (C) 2015 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.usbtuner.data;
18 
19 import android.support.annotation.NonNull;
20 import android.text.TextUtils;
21 import android.text.format.DateUtils;
22 
23 import com.android.usbtuner.data.Track.AtscAudioTrack;
24 import com.android.usbtuner.data.Track.AtscCaptionTrack;
25 import com.android.usbtuner.ts.SectionParser;
26 import com.android.usbtuner.util.ConvertUtils;
27 import com.android.usbtuner.util.IsoUtils;
28 import com.android.usbtuner.util.StringUtils;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * Collection of ATSC PSIP table items.
35  */
36 public class PsipData {
37 
PsipData()38     private PsipData() {
39     }
40 
41     public static class PsipSection {
42         private final int mTableId;
43         private final int mTableIdExtension;
44         private final int mSectionNumber;
45         private final boolean mCurrentNextIndicator;
46 
create(byte[] data)47         public static PsipSection create(byte[] data) {
48             if (data.length < 9) {
49                 return null;
50             }
51             int tableId = data[0] & 0xff;
52             int tableIdExtension = (data[3] & 0xff) << 8 | (data[4] & 0xff);
53             int sectionNumber = data[6] & 0xff;
54             boolean currentNextIndicator = (data[5] & 0x01) != 0;
55             return new PsipSection(tableId, tableIdExtension, sectionNumber, currentNextIndicator);
56         }
57 
PsipSection(int tableId, int tableIdExtension, int sectionNumber, boolean currentNextIndicator)58         private PsipSection(int tableId, int tableIdExtension, int sectionNumber,
59                 boolean currentNextIndicator) {
60             mTableId = tableId;
61             mTableIdExtension = tableIdExtension;
62             mSectionNumber = sectionNumber;
63             mCurrentNextIndicator = currentNextIndicator;
64         }
65 
getTableId()66         public int getTableId() {
67             return mTableId;
68         }
69 
getTableIdExtension()70         public int getTableIdExtension() {
71             return mTableIdExtension;
72         }
73 
getSectionNumber()74         public int getSectionNumber() {
75             return mSectionNumber;
76         }
77 
78         // This is for indicating that the section sent is applicable.
79         // We only consider a situation where currentNextIndicator is expected to have a true value.
80         // So, we are not going to compare this variable in hashCode() and equals() methods.
getCurrentNextIndicator()81         public boolean getCurrentNextIndicator() {
82             return mCurrentNextIndicator;
83         }
84 
85         @Override
hashCode()86         public int hashCode() {
87             int result = 17;
88             result = 31 * result + mTableId;
89             result = 31 * result + mTableIdExtension;
90             result = 31 * result + mSectionNumber;
91             return result;
92         }
93 
94         @Override
equals(Object obj)95         public boolean equals(Object obj) {
96             if (obj instanceof PsipSection) {
97                 PsipSection another = (PsipSection) obj;
98                 return mTableId == another.getTableId()
99                         && mTableIdExtension == another.getTableIdExtension()
100                         && mSectionNumber == another.getSectionNumber();
101             }
102             return false;
103         }
104     }
105 
106     /**
107      * {@link TvTracksInterface} for serving the audio and caption tracks.
108      */
109     public interface TvTracksInterface {
110         /**
111          * Set the flag that tells the caption tracks have been found in this section container.
112          */
setHasCaptionTrack()113         void setHasCaptionTrack();
114 
115         /**
116          * Returns whether or not the caption tracks have been found in this section container.
117          * If true, zero caption track will be interpreted as a clearance of the caption tracks.
118          */
hasCaptionTrack()119         boolean hasCaptionTrack();
120 
121         /**
122          * Returns the audio tracks received.
123          */
getAudioTracks()124         List<AtscAudioTrack> getAudioTracks();
125 
126         /**
127          * Returns the caption tracks received.
128          */
getCaptionTracks()129         List<AtscCaptionTrack> getCaptionTracks();
130     }
131 
132     public static class MgtItem {
133         public static final int TABLE_TYPE_EIT_RANGE_START = 0x0100;
134         public static final int TABLE_TYPE_EIT_RANGE_END = 0x017f;
135         public static final int TABLE_TYPE_CHANNEL_ETT = 0x0004;
136         public static final int TABLE_TYPE_ETT_RANGE_START = 0x0200;
137         public static final int TABLE_TYPE_ETT_RANGE_END = 0x027f;
138 
139         private final int mTableType;
140         private final int mTableTypePid;
141 
MgtItem(int tableType, int tableTypePid)142         public MgtItem(int tableType, int tableTypePid) {
143             mTableType = tableType;
144             mTableTypePid = tableTypePid;
145         }
146 
getTableType()147         public int getTableType() {
148             return mTableType;
149         }
150 
getTableTypePid()151         public int getTableTypePid() {
152             return mTableTypePid;
153         }
154     }
155 
156     public static class VctItem {
157         private final String mShortName;
158         private final String mLongName;
159         private final int mServiceType;
160         private final int mChannelTsid;
161         private final int mProgramNumber;
162         private final int mMajorChannelNumber;
163         private final int mMinorChannelNumber;
164         private final int mSourceId;
165         private String mDescription;
166 
VctItem(String shortName, String longName, int serviceType, int channelTsid, int programNumber, int majorChannelNumber, int minorChannelNumber, int sourceId)167         public VctItem(String shortName, String longName, int serviceType, int channelTsid,
168                 int programNumber, int majorChannelNumber, int minorChannelNumber, int sourceId) {
169             mShortName = shortName;
170             mLongName = longName;
171             mServiceType = serviceType;
172             mChannelTsid = channelTsid;
173             mProgramNumber = programNumber;
174             mMajorChannelNumber = majorChannelNumber;
175             mMinorChannelNumber = minorChannelNumber;
176             mSourceId = sourceId;
177         }
178 
getShortName()179         public String getShortName() {
180             return mShortName;
181         }
182 
getLongName()183         public String getLongName() {
184             return mLongName;
185         }
186 
getServiceType()187         public int getServiceType() {
188             return mServiceType;
189         }
190 
getChannelTsid()191         public int getChannelTsid() {
192             return mChannelTsid;
193         }
194 
getProgramNumber()195         public int getProgramNumber() {
196             return mProgramNumber;
197         }
198 
getMajorChannelNumber()199         public int getMajorChannelNumber() {
200             return mMajorChannelNumber;
201         }
202 
getMinorChannelNumber()203         public int getMinorChannelNumber() {
204             return mMinorChannelNumber;
205         }
206 
getSourceId()207         public int getSourceId() {
208             return mSourceId;
209         }
210 
211         @Override
toString()212         public String toString() {
213             return String.format("ShortName: %s LongName: %s ServiceType: %d ChannelTsid: %x "
214                             + "ProgramNumber:%d %d-%d SourceId: %x",
215                     mShortName, mLongName, mServiceType, mChannelTsid,
216                     mProgramNumber, mMajorChannelNumber, mMinorChannelNumber, mSourceId);
217         }
218 
setDescription(String description)219         public void setDescription(String description) {
220             mDescription = description;
221         }
222 
getDescription()223         public String getDescription() {
224             return mDescription;
225         }
226     }
227 
228     /**
229      * A base class for descriptors of Ts packets.
230      */
231     public abstract static class TsDescriptor {
getTag()232         public abstract int getTag();
233     }
234 
235     public static class ContentAdvisoryDescriptor extends TsDescriptor {
236         private final List<RatingRegion> mRatingRegions;
237 
ContentAdvisoryDescriptor(List<RatingRegion> ratingRegions)238         public ContentAdvisoryDescriptor(List<RatingRegion> ratingRegions) {
239             mRatingRegions = ratingRegions;
240         }
241 
242         @Override
getTag()243         public int getTag() {
244             return SectionParser.DESCRIPTOR_TAG_CONTENT_ADVISORY;
245         }
246 
getRatingRegions()247         public List<RatingRegion> getRatingRegions() {
248             return mRatingRegions;
249         }
250     }
251 
252     public static class CaptionServiceDescriptor extends TsDescriptor {
253         private final List<AtscCaptionTrack> mCaptionTracks;
254 
CaptionServiceDescriptor(List<AtscCaptionTrack> captionTracks)255         public CaptionServiceDescriptor(List<AtscCaptionTrack> captionTracks) {
256             mCaptionTracks = captionTracks;
257         }
258 
259         @Override
getTag()260         public int getTag() {
261             return SectionParser.DESCRIPTOR_TAG_CAPTION_SERVICE;
262         }
263 
getCaptionTracks()264         public List<AtscCaptionTrack> getCaptionTracks() {
265             return mCaptionTracks;
266         }
267     }
268 
269     public static class ExtendedChannelNameDescriptor extends TsDescriptor {
270         private final String mLongChannelName;
271 
ExtendedChannelNameDescriptor(String longChannelName)272         public ExtendedChannelNameDescriptor(String longChannelName) {
273             mLongChannelName = longChannelName;
274         }
275 
276         @Override
getTag()277         public int getTag() {
278             return SectionParser.DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME;
279         }
280 
getLongChannelName()281         public String getLongChannelName() {
282             return mLongChannelName;
283         }
284     }
285 
286     public static class GenreDescriptor extends TsDescriptor {
287         private final String[] mBroadcastGenres;
288         private final String[] mCanonicalGenres;
289 
GenreDescriptor(String[] broadcastGenres, String[] canonicalGenres)290         public GenreDescriptor(String[] broadcastGenres, String[] canonicalGenres) {
291             mBroadcastGenres = broadcastGenres;
292             mCanonicalGenres = canonicalGenres;
293         }
294 
295         @Override
getTag()296         public int getTag() {
297             return SectionParser.DESCRIPTOR_TAG_GENRE;
298         }
299 
getBroadcastGenres()300         public String[] getBroadcastGenres() {
301             return mBroadcastGenres;
302         }
303 
getCanonicalGenres()304         public String[] getCanonicalGenres() {
305             return mCanonicalGenres;
306         }
307     }
308 
309     public static class Ac3AudioDescriptor extends TsDescriptor {
310         // See A/52 Annex A. Table A4.2
311         private static final byte SAMPLE_RATE_CODE_48000HZ = 0;
312         private static final byte SAMPLE_RATE_CODE_44100HZ = 1;
313         private static final byte SAMPLE_RATE_CODE_32000HZ = 2;
314 
315         private final byte mSampleRateCode;
316         private final byte mBsid;
317         private final byte mBitRateCode;
318         private final byte mSurroundMode;
319         private final byte mBsmod;
320         private final int mNumChannels;
321         private final boolean mFullSvc;
322         private final byte mLangCod;
323         private final byte mLangCod2;
324         private final byte mMainId;
325         private final byte mPriority;
326         private final byte mAsvcflags;
327         private final String mText;
328         private final String mLanguage;
329         private final String mLanguage2;
330 
Ac3AudioDescriptor(byte sampleRateCode, byte bsid, byte bitRateCode, byte surroundMode, byte bsmod, int numChannels, boolean fullSvc, byte langCod, byte langCod2, byte mainId, byte priority, byte asvcflags, String text, String language, String language2)331         public Ac3AudioDescriptor(byte sampleRateCode, byte bsid, byte bitRateCode,
332                 byte surroundMode, byte bsmod, int numChannels, boolean fullSvc, byte langCod,
333                 byte langCod2, byte mainId, byte priority, byte asvcflags, String text,
334                 String language, String language2) {
335             mSampleRateCode = sampleRateCode;
336             mBsid = bsid;
337             mBitRateCode = bitRateCode;
338             mSurroundMode = surroundMode;
339             mBsmod = bsmod;
340             mNumChannels = numChannels;
341             mFullSvc = fullSvc;
342             mLangCod = langCod;
343             mLangCod2 = langCod2;
344             mMainId = mainId;
345             mPriority = priority;
346             mAsvcflags = asvcflags;
347             mText = text;
348             mLanguage = language;
349             mLanguage2 = language2;
350         }
351 
352         @Override
getTag()353         public int getTag() {
354             return SectionParser.DESCRIPTOR_TAG_AC3_AUDIO_STREAM;
355         }
356 
getSampleRateCode()357         public byte getSampleRateCode() {
358             return mSampleRateCode;
359         }
360 
getSampleRate()361         public int getSampleRate() {
362             switch (mSampleRateCode) {
363                 case SAMPLE_RATE_CODE_48000HZ:
364                     return 48000;
365                 case SAMPLE_RATE_CODE_44100HZ:
366                     return 44100;
367                 case SAMPLE_RATE_CODE_32000HZ:
368                     return 32000;
369                 default:
370                     return 0;
371             }
372         }
373 
getBsid()374         public byte getBsid() {
375             return mBsid;
376         }
377 
getBitRateCode()378         public byte getBitRateCode() {
379             return mBitRateCode;
380         }
381 
getSurroundMode()382         public byte getSurroundMode() {
383             return mSurroundMode;
384         }
385 
getBsmod()386         public byte getBsmod() {
387             return mBsmod;
388         }
389 
getNumChannels()390         public int getNumChannels() {
391             return mNumChannels;
392         }
393 
isFullSvc()394         public boolean isFullSvc() {
395             return mFullSvc;
396         }
397 
getLangCod()398         public byte getLangCod() {
399             return mLangCod;
400         }
401 
getLangCod2()402         public byte getLangCod2() {
403             return mLangCod2;
404         }
405 
getMainId()406         public byte getMainId() {
407             return mMainId;
408         }
409 
getPriority()410         public byte getPriority() {
411             return mPriority;
412         }
413 
getAsvcflags()414         public byte getAsvcflags() {
415             return mAsvcflags;
416         }
417 
getText()418         public String getText() {
419             return mText;
420         }
421 
getLanguage()422         public String getLanguage() {
423             return mLanguage;
424         }
425 
getLanguage2()426         public String getLanguage2() {
427             return mLanguage2;
428         }
429 
430         @Override
toString()431         public String toString() {
432             return String.format("AC3 audio stream sampleRateCode: %d, bsid: %d, bitRateCode: %d, "
433                     + "surroundMode: %d, bsmod: %d, numChannels: %d, fullSvc: %s, langCod: %d, "
434                     + "langCod2: %d, mainId: %d, priority: %d, avcflags: %d, text: %s, language: %s"
435                     + ", language2: %s", mSampleRateCode, mBsid, mBitRateCode, mSurroundMode,
436                     mBsmod, mNumChannels, mFullSvc, mLangCod, mLangCod2, mMainId, mPriority,
437                     mAsvcflags, mText, mLanguage, mLanguage2);
438         }
439     }
440 
441     public static class Iso639LanguageDescriptor extends TsDescriptor {
442         private final List<AtscAudioTrack> mAudioTracks;
443 
Iso639LanguageDescriptor(List<AtscAudioTrack> audioTracks)444         public Iso639LanguageDescriptor(List<AtscAudioTrack> audioTracks) {
445             mAudioTracks = audioTracks;
446         }
447 
448         @Override
getTag()449         public int getTag() {
450             return SectionParser.DESCRIPTOR_TAG_ISO639LANGUAGE;
451         }
452 
getAudioTracks()453         public List<AtscAudioTrack> getAudioTracks() {
454             return mAudioTracks;
455         }
456 
457         @Override
toString()458         public String toString() {
459             return String.format("%s %s", getClass().getName(), mAudioTracks);
460         }
461     }
462 
463     public static class RatingRegion {
464         private final int mName;
465         private final String mDescription;
466         private final List<RegionalRating> mRegionalRatings;
467 
RatingRegion(int name, String description, List<RegionalRating> regionalRatings)468         public RatingRegion(int name, String description, List<RegionalRating> regionalRatings) {
469             mName = name;
470             mDescription = description;
471             mRegionalRatings = regionalRatings;
472         }
473 
getName()474         public int getName() {
475             return mName;
476         }
477 
getDescription()478         public String getDescription() {
479             return mDescription;
480         }
481 
getRegionalRatings()482         public List<RegionalRating> getRegionalRatings() {
483             return mRegionalRatings;
484         }
485     }
486 
487     public static class RegionalRating {
488         private int mDimension;
489         private int mRating;
490 
RegionalRating(int dimension, int rating)491         public RegionalRating(int dimension, int rating) {
492             mDimension = dimension;
493             mRating = rating;
494         }
495 
getDimension()496         public int getDimension() {
497             return mDimension;
498         }
499 
getRating()500         public int getRating() {
501             return mRating;
502         }
503     }
504 
505     public static class EitItem implements Comparable<EitItem>, TvTracksInterface {
506         public static long INVALID_PROGRAM_ID = -1;
507 
508         // A program id is a primary key of TvContract.Programs table. So it must be positive.
509         private final long mProgramId;
510         private final int mEventId;
511         private final String mTitleText;
512         private String mDescription;
513         private final long mStartTime;
514         private final int mLengthInSecond;
515         private final String mContentRating;
516         private final List<AtscAudioTrack> mAudioTracks;
517         private final List<AtscCaptionTrack> mCaptionTracks;
518         private boolean mHasCaptionTrack;
519         private final String mBroadcastGenre;
520         private final String mCanonicalGenre;
521 
EitItem(long programId, int eventId, String titleText, long startTime, int lengthInSecond, String contentRating, List<AtscAudioTrack> audioTracks, List<AtscCaptionTrack> captionTracks, String broadcastGenre, String canonicalGenre, String description)522         public EitItem(long programId, int eventId, String titleText, long startTime,
523                 int lengthInSecond, String contentRating, List<AtscAudioTrack> audioTracks,
524                 List<AtscCaptionTrack> captionTracks, String broadcastGenre, String canonicalGenre,
525                 String description) {
526             mProgramId = programId;
527             mEventId = eventId;
528             mTitleText = titleText;
529             mStartTime = startTime;
530             mLengthInSecond = lengthInSecond;
531             mContentRating = contentRating;
532             mAudioTracks = audioTracks;
533             mCaptionTracks = captionTracks;
534             mBroadcastGenre = broadcastGenre;
535             mCanonicalGenre = canonicalGenre;
536             mDescription = description;
537         }
538 
getProgramId()539         public long getProgramId() {
540             return mProgramId;
541         }
542 
getEventId()543         public int getEventId() {
544             return mEventId;
545         }
546 
getTitleText()547         public String getTitleText() {
548             return mTitleText;
549         }
550 
setDescription(String description)551         public void setDescription(String description) {
552             mDescription = description;
553         }
554 
getDescription()555         public String getDescription() {
556             return mDescription;
557         }
558 
getStartTime()559         public long getStartTime() {
560             return mStartTime;
561         }
562 
getLengthInSecond()563         public int getLengthInSecond() {
564             return mLengthInSecond;
565         }
566 
getStartTimeUtcMillis()567         public long getStartTimeUtcMillis() {
568             return ConvertUtils.convertGPSTimeToUnixEpoch(mStartTime) * DateUtils.SECOND_IN_MILLIS;
569         }
570 
getEndTimeUtcMillis()571         public long getEndTimeUtcMillis() {
572             return ConvertUtils.convertGPSTimeToUnixEpoch(
573                     mStartTime + mLengthInSecond) * DateUtils.SECOND_IN_MILLIS;
574         }
575 
getContentRating()576         public String getContentRating() {
577             return mContentRating;
578         }
579 
580         @Override
getAudioTracks()581         public List<AtscAudioTrack> getAudioTracks() {
582             return mAudioTracks;
583         }
584 
585         @Override
getCaptionTracks()586         public List<AtscCaptionTrack> getCaptionTracks() {
587             return mCaptionTracks;
588         }
589 
getBroadcastGenre()590         public String getBroadcastGenre() {
591             return mBroadcastGenre;
592         }
593 
getCanonicalGenre()594         public String getCanonicalGenre() {
595             return mCanonicalGenre;
596         }
597 
598         @Override
setHasCaptionTrack()599         public void setHasCaptionTrack() {
600             mHasCaptionTrack = true;
601         }
602 
603         @Override
hasCaptionTrack()604         public boolean hasCaptionTrack() {
605             return mHasCaptionTrack;
606         }
607 
608         @Override
compareTo(@onNull EitItem item)609         public int compareTo(@NonNull EitItem item) {
610             // The list of caption tracks and the program ids are not compared in here because the
611             // channels in TIF have the concept of the caption and audio tracks while the programs
612             // do not and the programs in TIF only have a program id since they are the rows of
613             // Content Provider.
614             int ret = mEventId - item.getEventId();
615             if (ret != 0) {
616                 return ret;
617             }
618             ret = StringUtils.compare(mTitleText, item.getTitleText());
619             if (ret != 0) {
620                 return ret;
621             }
622             if (mStartTime > item.getStartTime()) {
623                 return 1;
624             } else if (mStartTime < item.getStartTime()) {
625                 return -1;
626             }
627             if (mLengthInSecond > item.getLengthInSecond()) {
628                 return 1;
629             } else if (mLengthInSecond < item.getLengthInSecond()) {
630                 return -1;
631             }
632 
633             // Compares content ratings
634             ret = StringUtils.compare(mContentRating, item.getContentRating());
635             if (ret != 0) {
636                 return ret;
637             }
638 
639             // Compares broadcast genres
640             ret = StringUtils.compare(mBroadcastGenre, item.getBroadcastGenre());
641             if (ret != 0) {
642                 return ret;
643             }
644             // Compares canonical genres
645             ret = StringUtils.compare(mCanonicalGenre, item.getCanonicalGenre());
646             if (ret != 0) {
647                 return ret;
648             }
649 
650             // Compares descriptions
651             return StringUtils.compare(mDescription, item.getDescription());
652         }
653 
getAudioLanguage()654         public String getAudioLanguage() {
655             if (mAudioTracks == null) {
656                 return "";
657             }
658             ArrayList<String> languages = new ArrayList<>();
659             for (AtscAudioTrack audioTrack : mAudioTracks) {
660                 if (IsoUtils.isValidIso3Language(audioTrack.language)) {
661                     languages.add(audioTrack.language);
662                 }
663             }
664             return TextUtils.join(",", languages);
665         }
666 
667         @Override
toString()668         public String toString() {
669             return String.format("EitItem programId: %d, eventId: %d, title: %s, startTime: %10d"
670                             + "length: %6d, rating: %s, audio tracks: %d, caption tracks: %d, "
671                             + "genres (broadcast: %s, canonical: %s), description: %s",
672                     mProgramId, mEventId, mTitleText, mStartTime, mLengthInSecond, mContentRating,
673                     mAudioTracks != null ? mAudioTracks.size() : 0,
674                     mCaptionTracks != null ? mCaptionTracks.size() : 0,
675                     mBroadcastGenre, mCanonicalGenre, mDescription);
676         }
677     }
678 
679     public static class EttItem {
680         public final int eventId;
681         public final String text;
682 
EttItem(int eventId, String text)683         public EttItem(int eventId, String text) {
684             this.eventId = eventId;
685             this.text = text;
686         }
687     }
688 }
689