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.util.Log;
21 
22 import com.android.usbtuner.data.Channel;
23 import com.android.usbtuner.data.Channel.TunerChannelProto;
24 import com.android.usbtuner.data.PsiData.PmtItem;
25 import com.android.usbtuner.data.PsipData.TvTracksInterface;
26 import com.android.usbtuner.data.PsipData.VctItem;
27 import com.android.usbtuner.data.Track.AtscAudioTrack;
28 import com.android.usbtuner.data.Track.AtscCaptionTrack;
29 import com.android.usbtuner.util.Ints;
30 import com.android.usbtuner.util.StringUtils;
31 import com.google.protobuf.nano.MessageNano;
32 
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.List;
38 
39 /**
40  * A class that represents a single channel accessible through a tuner.
41  */
42 public class TunerChannel implements Comparable<TunerChannel>, TvTracksInterface {
43     private static final String TAG = "TunerChannel";
44 
45     // See ATSC Code Points Registry.
46     private static final String[] ATSC_SERVICE_TYPE_NAMES = new String[] {
47             "ATSC Reserved",
48             "Analog television channels",
49             "ATSC_digital_television",
50             "ATSC_audio",
51             "ATSC_data_only_service",
52             "Software Download",
53             "Unassociated/Small Screen Service",
54             "Parameterized Service",
55             "ATSC NRT Service",
56             "Extended Parameterized Service" };
57     private static final String ATSC_SERVICE_TYPE_NAME_RESERVED =
58             ATSC_SERVICE_TYPE_NAMES[Channel.SERVICE_TYPE_ATSC_RESERVED];
59     private static final String ATSC_SERVICE_TYPE_NAME_DIGITAL_TELEVISION =
60             ATSC_SERVICE_TYPE_NAMES[Channel.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION];
61 
62     public static final int INVALID_FREQUENCY = -1;
63 
64     // According to RFC4259, The number of available PIDs ranges from 0 to 8191.
65     public static final int INVALID_PID = -1;
66 
67     // According to ISO13818-1, Mpeg2 StreamType has a range from 0x00 to 0xff.
68     public static final int INVALID_STREAMTYPE = -1;
69 
70     private final TunerChannelProto mProto;
71 
TunerChannel(VctItem channel, int programNumber, List<PmtItem> pmtItems, int type)72     private TunerChannel(VctItem channel, int programNumber, List<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         mProto.type = type;
95         mProto.channelId = -1L;
96         mProto.frequency = INVALID_FREQUENCY;
97         mProto.videoPid = INVALID_PID;
98         mProto.videoStreamType = INVALID_STREAMTYPE;
99         List<Integer> audioPids = new ArrayList<>();
100         List<Integer> audioStreamTypes = new ArrayList<>();
101         for (PsiData.PmtItem pmt : pmtItems) {
102             switch (pmt.getStreamType()) {
103                 // MPEG ES stream video types
104                 case Channel.MPEG1:
105                 case Channel.MPEG2:
106                 case Channel.H263:
107                 case Channel.H264:
108                 case Channel.H265:
109                     mProto.videoPid = pmt.getEsPid();
110                     mProto.videoStreamType = pmt.getStreamType();
111                     break;
112 
113                 // MPEG ES stream audio types
114                 case Channel.MPEG1AUDIO:
115                 case Channel.MPEG2AUDIO:
116                 case Channel.MPEG2AACAUDIO:
117                 case Channel.MPEG4LATMAACAUDIO:
118                 case Channel.A52AC3AUDIO:
119                 case Channel.EAC3AUDIO:
120                     audioPids.add(pmt.getEsPid());
121                     audioStreamTypes.add(pmt.getStreamType());
122                     break;
123 
124                 // Non MPEG ES stream types
125                 case 0x100: // PmtItem.ES_PID_PCR:
126                     mProto.pcrPid = pmt.getEsPid();
127                     break;
128             }
129         }
130         mProto.audioPids = Ints.toArray(audioPids);
131         mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
132         mProto.audioTrackIndex = (audioPids.size() > 0) ? 0 : -1;
133     }
134 
TunerChannel(VctItem channel, List<PmtItem> pmtItems)135     public TunerChannel(VctItem channel, List<PmtItem> pmtItems) {
136         this(channel, 0, pmtItems, Channel.TYPE_TUNER);
137     }
138 
TunerChannel(int programNumber, List<PmtItem> pmtItems)139     public TunerChannel(int programNumber, List<PmtItem> pmtItems) {
140         this(null, programNumber, pmtItems, Channel.TYPE_TUNER);
141     }
142 
TunerChannel(TunerChannelProto tunerChannelProto)143     private TunerChannel(TunerChannelProto tunerChannelProto) {
144         mProto = tunerChannelProto;
145     }
146 
forFile(VctItem channel, List<PmtItem> pmtItems)147     public static TunerChannel forFile(VctItem channel, List<PmtItem> pmtItems) {
148         return new TunerChannel(channel, 0, pmtItems, Channel.TYPE_FILE);
149     }
150 
getName()151     public String getName() {
152         return (mProto.longName.isEmpty()) ? mProto.shortName : mProto.longName;
153     }
154 
getShortName()155     public String getShortName() {
156         return mProto.shortName;
157     }
158 
getProgramNumber()159     public int getProgramNumber() {
160         return mProto.programNumber;
161     }
162 
getServiceType()163     public int getServiceType() {
164         return mProto.serviceType;
165     }
166 
getServiceTypeName()167     public String getServiceTypeName() {
168         int serviceType = mProto.serviceType;
169         if (serviceType >= 0 && serviceType < ATSC_SERVICE_TYPE_NAMES.length) {
170             return ATSC_SERVICE_TYPE_NAMES[serviceType];
171         }
172         return ATSC_SERVICE_TYPE_NAME_RESERVED;
173     }
174 
getVirtualMajor()175     public int getVirtualMajor() {
176         return mProto.virtualMajor;
177     }
178 
getVirtualMinor()179     public int getVirtualMinor() {
180         return mProto.virtualMinor;
181     }
182 
getFrequency()183     public int getFrequency() {
184         return mProto.frequency;
185     }
186 
getModulation()187     public String getModulation() {
188         return mProto.modulation;
189     }
190 
getTsid()191     public int getTsid() {
192         return mProto.tsid;
193     }
194 
getVideoPid()195     public int getVideoPid() {
196         return mProto.videoPid;
197     }
198 
setVideoPid(int videoPid)199     public void setVideoPid(int videoPid) {
200         mProto.videoPid = videoPid;
201     }
202 
getVideoStreamType()203     public int getVideoStreamType() {
204         return mProto.videoStreamType;
205     }
206 
getAudioPid()207     public int getAudioPid() {
208         if (mProto.audioTrackIndex == -1) {
209             return INVALID_PID;
210         }
211         return mProto.audioPids[mProto.audioTrackIndex];
212     }
213 
getAudioStreamType()214     public int getAudioStreamType() {
215         if (mProto.audioTrackIndex == -1) {
216             return INVALID_STREAMTYPE;
217         }
218         return mProto.audioStreamTypes[mProto.audioTrackIndex];
219     }
220 
getAudioPids()221     public List<Integer> getAudioPids() {
222         return Ints.asList(mProto.audioPids);
223     }
224 
setAudioPids(List<Integer> audioPids)225     public void setAudioPids(List<Integer> audioPids) {
226         mProto.audioPids = Ints.toArray(audioPids);
227     }
228 
getAudioStreamTypes()229     public List<Integer> getAudioStreamTypes() {
230         return Ints.asList(mProto.audioStreamTypes);
231     }
232 
setAudioStreamTypes(List<Integer> audioStreamTypes)233     public void setAudioStreamTypes(List<Integer> audioStreamTypes) {
234         mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
235     }
236 
getPcrPid()237     public int getPcrPid() {
238         return mProto.pcrPid;
239     }
240 
getType()241     public int getType() {
242         return mProto.type;
243     }
244 
setFilepath(String filepath)245     public void setFilepath(String filepath) {
246         mProto.filepath = filepath;
247     }
248 
getFilepath()249     public String getFilepath() {
250         return mProto.filepath;
251     }
252 
setFrequency(int frequency)253     public void setFrequency(int frequency) {
254         mProto.frequency = frequency;
255     }
256 
setModulation(String modulation)257     public void setModulation(String modulation) {
258         mProto.modulation = modulation;
259     }
260 
hasVideo()261     public boolean hasVideo() {
262         return mProto.videoPid != INVALID_PID;
263     }
264 
hasAudio()265     public boolean hasAudio() {
266         return getAudioPid() != INVALID_PID;
267     }
268 
getChannelId()269     public long getChannelId() {
270         return mProto.channelId;
271     }
272 
setChannelId(long channelId)273     public void setChannelId(long channelId) {
274         mProto.channelId = channelId;
275     }
276 
getDisplayNumber()277     public String getDisplayNumber() {
278         if (mProto.virtualMajor != 0 && mProto.virtualMinor != 0) {
279             return String.format("%d-%d", mProto.virtualMajor, mProto.virtualMinor);
280         } else if (mProto.virtualMajor != 0) {
281             return Integer.toString(mProto.virtualMajor);
282         } else {
283             return Integer.toString(mProto.programNumber);
284         }
285     }
286 
getDescription()287     public String getDescription() {
288         return mProto.description;
289     }
290 
291     @Override
setHasCaptionTrack()292     public void setHasCaptionTrack() {
293         mProto.hasCaptionTrack = true;
294     }
295 
296     @Override
hasCaptionTrack()297     public boolean hasCaptionTrack() {
298         return mProto.hasCaptionTrack;
299     }
300 
301     @Override
getAudioTracks()302     public List<AtscAudioTrack> getAudioTracks() {
303         return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks));
304     }
305 
setAudioTracks(List<AtscAudioTrack> audioTracks)306     public void setAudioTracks(List<AtscAudioTrack> audioTracks) {
307         mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]);
308     }
309 
310     @Override
getCaptionTracks()311     public List<AtscCaptionTrack> getCaptionTracks() {
312         return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks));
313     }
314 
setCaptionTracks(List<AtscCaptionTrack> captionTracks)315     public void setCaptionTracks(List<AtscCaptionTrack> captionTracks) {
316         mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]);
317     }
318 
selectAudioTrack(int index)319     public void selectAudioTrack(int index) {
320         if (0 <= index && index < mProto.audioPids.length) {
321             mProto.audioTrackIndex = index;
322         } else {
323             mProto.audioTrackIndex = -1;
324         }
325     }
326 
327     @Override
toString()328     public String toString() {
329         switch (mProto.type) {
330             case Channel.TYPE_FILE:
331                 return String.format("{%d-%d %s} Filepath: %s, ProgramNumber %d",
332                         mProto.virtualMajor, mProto.virtualMinor, mProto.shortName,
333                         mProto.filepath, mProto.programNumber);
334             //case Channel.TYPE_TUNER:
335             default:
336                 return String.format("{%d-%d %s} Frequency: %d, ProgramNumber %d",
337                         mProto.virtualMajor, mProto.virtualMinor, mProto.shortName,
338                         mProto.frequency, mProto.programNumber);
339         }
340     }
341 
342     @Override
compareTo(@onNull TunerChannel channel)343     public int compareTo(@NonNull TunerChannel channel) {
344         // In the same frequency, the program number acts as the sub-channel number.
345         int ret = getFrequency() - channel.getFrequency();
346         if (ret != 0) {
347             return ret;
348         }
349         ret = getProgramNumber() - channel.getProgramNumber();
350         if (ret != 0) {
351             return ret;
352         }
353 
354         // For FileDataSource, file paths should be compared.
355         return StringUtils.compare(getFilepath(), channel.getFilepath());
356     }
357 
358     // Serialization
toByteArray()359     public byte[] toByteArray() {
360         return MessageNano.toByteArray(mProto);
361     }
362 
parseFrom(byte[] data)363     public static TunerChannel parseFrom(byte[] data) {
364         if (data == null) {
365             return null;
366         }
367         try {
368             return new TunerChannel(TunerChannelProto.parseFrom(data));
369         } catch (IOException e) {
370             Log.e(TAG, "Could not parse from byte array", e);
371             return null;
372         }
373     }
374 }
375