• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.tv.tuner.data;
18 
19 import android.database.Cursor;
20 import android.support.annotation.NonNull;
21 import android.util.Log;
22 import com.android.tv.common.util.StringUtils;
23 import com.android.tv.tuner.data.nano.Channel;
24 import com.android.tv.tuner.data.nano.Channel.TunerChannelProto;
25 import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
26 import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
27 import com.android.tv.tuner.util.Ints;
28 import com.google.protobuf.nano.MessageNano;
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Objects;
35 
36 /** A class that represents a single channel accessible through a tuner. */
37 public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracksInterface {
38     private static final String TAG = "TunerChannel";
39 
40     /** Channel number separator between major number and minor number. */
41     public static final char CHANNEL_NUMBER_SEPARATOR = '-';
42 
43     // See ATSC Code Points Registry.
44     private static final String[] ATSC_SERVICE_TYPE_NAMES =
45             new String[] {
46                 "ATSC Reserved",
47                 "Analog television channels",
48                 "ATSC_digital_television",
49                 "ATSC_audio",
50                 "ATSC_data_only_service",
51                 "Software Download",
52                 "Unassociated/Small Screen Service",
53                 "Parameterized Service",
54                 "ATSC NRT Service",
55                 "Extended Parameterized Service"
56             };
57     private static final String ATSC_SERVICE_TYPE_NAME_RESERVED =
58             ATSC_SERVICE_TYPE_NAMES[Channel.AtscServiceType.SERVICE_TYPE_ATSC_RESERVED];
59 
60     public static final int INVALID_FREQUENCY = -1;
61 
62     // According to RFC4259, The number of available PIDs ranges from 0 to 8191.
63     public static final int INVALID_PID = -1;
64 
65     // According to ISO13818-1, Mpeg2 StreamType has a range from 0x00 to 0xff.
66     public static final int INVALID_STREAMTYPE = -1;
67 
68     // @GuardedBy(this) Writing operations and toByteArray will be guarded. b/34197766
69     private final TunerChannelProto mProto;
70 
TunerChannel( PsipData.VctItem channel, int programNumber, List<PsiData.PmtItem> pmtItems, int type)71     private TunerChannel(
72             PsipData.VctItem channel, int programNumber, List<PsiData.PmtItem> pmtItems, int type) {
73         mProto = new TunerChannelProto();
74         if (channel == null) {
75             mProto.shortName = "";
76             mProto.tsid = 0;
77             mProto.programNumber = programNumber;
78             mProto.virtualMajor = 0;
79             mProto.virtualMinor = 0;
80         } else {
81             mProto.shortName = channel.getShortName();
82             if (channel.getLongName() != null) {
83                 mProto.longName = channel.getLongName();
84             }
85             mProto.tsid = channel.getChannelTsid();
86             mProto.programNumber = channel.getProgramNumber();
87             mProto.virtualMajor = channel.getMajorChannelNumber();
88             mProto.virtualMinor = channel.getMinorChannelNumber();
89             if (channel.getDescription() != null) {
90                 mProto.description = channel.getDescription();
91             }
92             mProto.serviceType = channel.getServiceType();
93         }
94         initProto(pmtItems, type);
95     }
96 
initProto(List<PsiData.PmtItem> pmtItems, int type)97     private void initProto(List<PsiData.PmtItem> pmtItems, int type) {
98         mProto.type = type;
99         mProto.channelId = -1L;
100         mProto.frequency = INVALID_FREQUENCY;
101         mProto.videoPid = INVALID_PID;
102         mProto.videoStreamType = INVALID_STREAMTYPE;
103         List<Integer> audioPids = new ArrayList<>();
104         List<Integer> audioStreamTypes = new ArrayList<>();
105         for (PsiData.PmtItem pmt : pmtItems) {
106             switch (pmt.getStreamType()) {
107                     // MPEG ES stream video types
108                 case Channel.VideoStreamType.MPEG1:
109                 case Channel.VideoStreamType.MPEG2:
110                 case Channel.VideoStreamType.H263:
111                 case Channel.VideoStreamType.H264:
112                 case Channel.VideoStreamType.H265:
113                     mProto.videoPid = pmt.getEsPid();
114                     mProto.videoStreamType = pmt.getStreamType();
115                     break;
116 
117                     // MPEG ES stream audio types
118                 case Channel.AudioStreamType.MPEG1AUDIO:
119                 case Channel.AudioStreamType.MPEG2AUDIO:
120                 case Channel.AudioStreamType.MPEG2AACAUDIO:
121                 case Channel.AudioStreamType.MPEG4LATMAACAUDIO:
122                 case Channel.AudioStreamType.A52AC3AUDIO:
123                 case Channel.AudioStreamType.EAC3AUDIO:
124                     audioPids.add(pmt.getEsPid());
125                     audioStreamTypes.add(pmt.getStreamType());
126                     break;
127 
128                     // Non MPEG ES stream types
129                 case 0x100: // PmtItem.ES_PID_PCR:
130                     mProto.pcrPid = pmt.getEsPid();
131                     break;
132                 default:
133                     // fall out
134             }
135         }
136         mProto.audioPids = Ints.toArray(audioPids);
137         mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
138         mProto.audioTrackIndex = (audioPids.size() > 0) ? 0 : -1;
139     }
140 
TunerChannel( int programNumber, int type, PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems)141     private TunerChannel(
142             int programNumber, int type, PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
143         mProto = new TunerChannelProto();
144         mProto.tsid = 0;
145         mProto.virtualMajor = 0;
146         mProto.virtualMinor = 0;
147         if (channel == null) {
148             mProto.shortName = "";
149             mProto.programNumber = programNumber;
150         } else {
151             mProto.shortName = channel.getServiceName();
152             mProto.programNumber = channel.getServiceId();
153             mProto.serviceType = channel.getServiceType();
154         }
155         initProto(pmtItems, type);
156     }
157 
158     /** Initialize tuner channel with VCT items and PMT items. */
TunerChannel(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems)159     public TunerChannel(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
160         this(channel, 0, pmtItems, Channel.TunerType.TYPE_TUNER);
161     }
162 
163     /** Initialize tuner channel with program number and PMT items. */
TunerChannel(int programNumber, List<PsiData.PmtItem> pmtItems)164     public TunerChannel(int programNumber, List<PsiData.PmtItem> pmtItems) {
165         this(null, programNumber, pmtItems, Channel.TunerType.TYPE_TUNER);
166     }
167 
168     /** Initialize tuner channel with SDT items and PMT items. */
TunerChannel(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems)169     public TunerChannel(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
170         this(0, Channel.TunerType.TYPE_TUNER, channel, pmtItems);
171     }
172 
TunerChannel(TunerChannelProto tunerChannelProto)173     private TunerChannel(TunerChannelProto tunerChannelProto) {
174         mProto = tunerChannelProto;
175     }
176 
forFile(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems)177     public static TunerChannel forFile(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
178         return new TunerChannel(channel, 0, pmtItems, Channel.TunerType.TYPE_FILE);
179     }
180 
forDvbFile( PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems)181     public static TunerChannel forDvbFile(
182             PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
183         return new TunerChannel(0, Channel.TunerType.TYPE_FILE, channel, pmtItems);
184     }
185 
186     /**
187      * Create a TunerChannel object suitable for network tuners
188      *
189      * @param major Channel number major
190      * @param minor Channel number minor
191      * @param programNumber Program number
192      * @param shortName Short name
193      * @param recordingProhibited Recording prohibition info
194      * @param videoFormat Video format. Should be {@code null} or one of the followings: {@link
195      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_240P}, {@link
196      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_360P}, {@link
197      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_480I}, {@link
198      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_480P}, {@link
199      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_576I}, {@link
200      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_576P}, {@link
201      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_720P}, {@link
202      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080I}, {@link
203      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080P}, {@link
204      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_2160P}, {@link
205      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_4320P}
206      * @return a TunerChannel object
207      */
forNetwork( int major, int minor, int programNumber, String shortName, boolean recordingProhibited, String videoFormat)208     public static TunerChannel forNetwork(
209             int major,
210             int minor,
211             int programNumber,
212             String shortName,
213             boolean recordingProhibited,
214             String videoFormat) {
215         TunerChannel tunerChannel =
216                 new TunerChannel(
217                         null,
218                         programNumber,
219                         Collections.EMPTY_LIST,
220                         Channel.TunerType.TYPE_NETWORK);
221         tunerChannel.setVirtualMajor(major);
222         tunerChannel.setVirtualMinor(minor);
223         tunerChannel.setShortName(shortName);
224         // Set audio and video pids in order to work around the audio-only channel check.
225         tunerChannel.setAudioPids(new ArrayList<>(Arrays.asList(0)));
226         tunerChannel.selectAudioTrack(0);
227         tunerChannel.setVideoPid(0);
228         tunerChannel.setRecordingProhibited(recordingProhibited);
229         if (videoFormat != null) {
230             tunerChannel.setVideoFormat(videoFormat);
231         }
232         return tunerChannel;
233     }
234 
getName()235     public String getName() {
236         return (!mProto.shortName.isEmpty()) ? mProto.shortName : mProto.longName;
237     }
238 
getShortName()239     public String getShortName() {
240         return mProto.shortName;
241     }
242 
getProgramNumber()243     public int getProgramNumber() {
244         return mProto.programNumber;
245     }
246 
getServiceType()247     public int getServiceType() {
248         return mProto.serviceType;
249     }
250 
getServiceTypeName()251     public String getServiceTypeName() {
252         int serviceType = mProto.serviceType;
253         if (serviceType >= 0 && serviceType < ATSC_SERVICE_TYPE_NAMES.length) {
254             return ATSC_SERVICE_TYPE_NAMES[serviceType];
255         }
256         return ATSC_SERVICE_TYPE_NAME_RESERVED;
257     }
258 
getVirtualMajor()259     public int getVirtualMajor() {
260         return mProto.virtualMajor;
261     }
262 
getVirtualMinor()263     public int getVirtualMinor() {
264         return mProto.virtualMinor;
265     }
266 
getFrequency()267     public int getFrequency() {
268         return mProto.frequency;
269     }
270 
getModulation()271     public String getModulation() {
272         return mProto.modulation;
273     }
274 
getTsid()275     public int getTsid() {
276         return mProto.tsid;
277     }
278 
getVideoPid()279     public int getVideoPid() {
280         return mProto.videoPid;
281     }
282 
setVideoPid(int videoPid)283     public synchronized void setVideoPid(int videoPid) {
284         mProto.videoPid = videoPid;
285     }
286 
getVideoStreamType()287     public int getVideoStreamType() {
288         return mProto.videoStreamType;
289     }
290 
getAudioPid()291     public int getAudioPid() {
292         if (mProto.audioTrackIndex == -1) {
293             return INVALID_PID;
294         }
295         return mProto.audioPids[mProto.audioTrackIndex];
296     }
297 
getAudioStreamType()298     public int getAudioStreamType() {
299         if (mProto.audioTrackIndex == -1) {
300             return INVALID_STREAMTYPE;
301         }
302         return mProto.audioStreamTypes[mProto.audioTrackIndex];
303     }
304 
getAudioPids()305     public List<Integer> getAudioPids() {
306         return Ints.asList(mProto.audioPids);
307     }
308 
setAudioPids(List<Integer> audioPids)309     public synchronized void setAudioPids(List<Integer> audioPids) {
310         mProto.audioPids = Ints.toArray(audioPids);
311     }
312 
getAudioStreamTypes()313     public List<Integer> getAudioStreamTypes() {
314         return Ints.asList(mProto.audioStreamTypes);
315     }
316 
setAudioStreamTypes(List<Integer> audioStreamTypes)317     public synchronized void setAudioStreamTypes(List<Integer> audioStreamTypes) {
318         mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
319     }
320 
getPcrPid()321     public int getPcrPid() {
322         return mProto.pcrPid;
323     }
324 
getType()325     public int getType() {
326         return mProto.type;
327     }
328 
setFilepath(String filepath)329     public synchronized void setFilepath(String filepath) {
330         mProto.filepath = filepath == null ? "" : filepath;
331     }
332 
getFilepath()333     public String getFilepath() {
334         return mProto.filepath;
335     }
336 
setVirtualMajor(int virtualMajor)337     public synchronized void setVirtualMajor(int virtualMajor) {
338         mProto.virtualMajor = virtualMajor;
339     }
340 
setVirtualMinor(int virtualMinor)341     public synchronized void setVirtualMinor(int virtualMinor) {
342         mProto.virtualMinor = virtualMinor;
343     }
344 
setShortName(String shortName)345     public synchronized void setShortName(String shortName) {
346         mProto.shortName = shortName == null ? "" : shortName;
347     }
348 
setFrequency(int frequency)349     public synchronized void setFrequency(int frequency) {
350         mProto.frequency = frequency;
351     }
352 
setModulation(String modulation)353     public synchronized void setModulation(String modulation) {
354         mProto.modulation = modulation == null ? "" : modulation;
355     }
356 
hasVideo()357     public boolean hasVideo() {
358         return mProto.videoPid != INVALID_PID;
359     }
360 
hasAudio()361     public boolean hasAudio() {
362         return getAudioPid() != INVALID_PID;
363     }
364 
getChannelId()365     public long getChannelId() {
366         return mProto.channelId;
367     }
368 
setChannelId(long channelId)369     public synchronized void setChannelId(long channelId) {
370         mProto.channelId = channelId;
371     }
372 
373     /**
374      * The flag indicating whether this TV channel is locked or not. This is primarily used for
375      * alternative parental control to prevent unauthorized users from watching the current channel
376      * regardless of the content rating
377      *
378      * @see <a
379      *     href="https://developer.android.com/reference/android/media/tv/TvContract.Channels.html#COLUMN_LOCKED">link</a>
380      */
isLocked()381     public boolean isLocked() {
382         return mProto.locked;
383     }
384 
setLocked(boolean locked)385     public synchronized void setLocked(boolean locked) {
386         mProto.locked = locked;
387     }
388 
getDisplayNumber()389     public String getDisplayNumber() {
390         return getDisplayNumber(true);
391     }
392 
getDisplayNumber(boolean ignoreZeroMinorNumber)393     public String getDisplayNumber(boolean ignoreZeroMinorNumber) {
394         if (mProto.virtualMajor != 0 && (mProto.virtualMinor != 0 || !ignoreZeroMinorNumber)) {
395             return String.format(
396                     "%d%c%d", mProto.virtualMajor, CHANNEL_NUMBER_SEPARATOR, mProto.virtualMinor);
397         } else if (mProto.virtualMajor != 0) {
398             return Integer.toString(mProto.virtualMajor);
399         } else {
400             return Integer.toString(mProto.programNumber);
401         }
402     }
403 
getDescription()404     public String getDescription() {
405         return mProto.description;
406     }
407 
408     @Override
setHasCaptionTrack()409     public synchronized void setHasCaptionTrack() {
410         mProto.hasCaptionTrack = true;
411     }
412 
413     @Override
hasCaptionTrack()414     public boolean hasCaptionTrack() {
415         return mProto.hasCaptionTrack;
416     }
417 
418     @Override
getAudioTracks()419     public List<AtscAudioTrack> getAudioTracks() {
420         return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks));
421     }
422 
setAudioTracks(List<AtscAudioTrack> audioTracks)423     public synchronized void setAudioTracks(List<AtscAudioTrack> audioTracks) {
424         mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]);
425     }
426 
427     @Override
getCaptionTracks()428     public List<AtscCaptionTrack> getCaptionTracks() {
429         return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks));
430     }
431 
setCaptionTracks(List<AtscCaptionTrack> captionTracks)432     public synchronized void setCaptionTracks(List<AtscCaptionTrack> captionTracks) {
433         mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]);
434     }
435 
selectAudioTrack(int index)436     public synchronized void selectAudioTrack(int index) {
437         if (0 <= index && index < mProto.audioPids.length) {
438             mProto.audioTrackIndex = index;
439         } else {
440             mProto.audioTrackIndex = -1;
441         }
442     }
443 
setRecordingProhibited(boolean recordingProhibited)444     public synchronized void setRecordingProhibited(boolean recordingProhibited) {
445         mProto.recordingProhibited = recordingProhibited;
446     }
447 
isRecordingProhibited()448     public boolean isRecordingProhibited() {
449         return mProto.recordingProhibited;
450     }
451 
setVideoFormat(String videoFormat)452     public synchronized void setVideoFormat(String videoFormat) {
453         mProto.videoFormat = videoFormat == null ? "" : videoFormat;
454     }
455 
getVideoFormat()456     public String getVideoFormat() {
457         return mProto.videoFormat;
458     }
459 
460     @Override
toString()461     public String toString() {
462         switch (mProto.type) {
463             case Channel.TunerType.TYPE_FILE:
464                 return String.format(
465                         "{%d-%d %s} Filepath: %s, ProgramNumber %d",
466                         mProto.virtualMajor,
467                         mProto.virtualMinor,
468                         mProto.shortName,
469                         mProto.filepath,
470                         mProto.programNumber);
471                 // case Channel.TunerType.TYPE_TUNER:
472             default:
473                 return String.format(
474                         "{%d-%d %s} Frequency: %d, ProgramNumber %d",
475                         mProto.virtualMajor,
476                         mProto.virtualMinor,
477                         mProto.shortName,
478                         mProto.frequency,
479                         mProto.programNumber);
480         }
481     }
482 
483     @Override
compareTo(@onNull TunerChannel channel)484     public int compareTo(@NonNull TunerChannel channel) {
485         // In the same frequency, the program number acts as the sub-channel number.
486         int ret = getFrequency() - channel.getFrequency();
487         if (ret != 0) {
488             return ret;
489         }
490         ret = getProgramNumber() - channel.getProgramNumber();
491         if (ret != 0) {
492             return ret;
493         }
494         ret = StringUtils.compare(getName(), channel.getName());
495         if (ret != 0) {
496             return ret;
497         }
498         // For FileTsStreamer, file paths should be compared.
499         return StringUtils.compare(getFilepath(), channel.getFilepath());
500     }
501 
502     @Override
equals(Object o)503     public boolean equals(Object o) {
504         if (!(o instanceof TunerChannel)) {
505             return false;
506         }
507         return compareTo((TunerChannel) o) == 0;
508     }
509 
510     @Override
hashCode()511     public int hashCode() {
512         return Objects.hash(getFrequency(), getProgramNumber(), getName(), getFilepath());
513     }
514 
515     // Serialization
toByteArray()516     public synchronized byte[] toByteArray() {
517         try {
518             return MessageNano.toByteArray(mProto);
519         } catch (Exception e) {
520             // Retry toByteArray. b/34197766
521             Log.w(
522                     TAG,
523                     "TunerChannel or its variables are modified in multiple thread without lock",
524                     e);
525             return MessageNano.toByteArray(mProto);
526         }
527     }
528 
parseFrom(byte[] data)529     public static TunerChannel parseFrom(byte[] data) {
530         if (data == null) {
531             return null;
532         }
533         try {
534             return new TunerChannel(TunerChannelProto.parseFrom(data));
535         } catch (IOException e) {
536             Log.e(TAG, "Could not parse from byte array", e);
537             return null;
538         }
539     }
540 
fromCursor(Cursor cursor)541     public static TunerChannel fromCursor(Cursor cursor) {
542         long channelId = cursor.getLong(0);
543         boolean locked = cursor.getInt(1) > 0;
544         byte[] data = cursor.getBlob(2);
545         TunerChannel channel = TunerChannel.parseFrom(data);
546         if (channel != null) {
547             channel.setChannelId(channelId);
548             channel.setLocked(locked);
549         }
550         return channel;
551     }
552 }
553