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