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.usbtuner.data;
18
19import android.support.annotation.NonNull;
20import android.util.Log;
21
22import com.android.usbtuner.data.Channel;
23import com.android.usbtuner.data.Channel.TunerChannelProto;
24import com.android.usbtuner.data.PsiData.PmtItem;
25import com.android.usbtuner.data.PsipData.TvTracksInterface;
26import com.android.usbtuner.data.PsipData.VctItem;
27import com.android.usbtuner.data.Track.AtscAudioTrack;
28import com.android.usbtuner.data.Track.AtscCaptionTrack;
29import com.android.usbtuner.util.Ints;
30import com.android.usbtuner.util.StringUtils;
31import com.google.protobuf.nano.MessageNano;
32
33import java.io.IOException;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.Collections;
37import java.util.List;
38
39/**
40 * A class that represents a single channel accessible through a tuner.
41 */
42public 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
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
135    public TunerChannel(VctItem channel, List<PmtItem> pmtItems) {
136        this(channel, 0, pmtItems, Channel.TYPE_TUNER);
137    }
138
139    public TunerChannel(int programNumber, List<PmtItem> pmtItems) {
140        this(null, programNumber, pmtItems, Channel.TYPE_TUNER);
141    }
142
143    private TunerChannel(TunerChannelProto tunerChannelProto) {
144        mProto = tunerChannelProto;
145    }
146
147    public static TunerChannel forFile(VctItem channel, List<PmtItem> pmtItems) {
148        return new TunerChannel(channel, 0, pmtItems, Channel.TYPE_FILE);
149    }
150
151    public String getName() {
152        return (mProto.longName.isEmpty()) ? mProto.shortName : mProto.longName;
153    }
154
155    public String getShortName() {
156        return mProto.shortName;
157    }
158
159    public int getProgramNumber() {
160        return mProto.programNumber;
161    }
162
163    public int getServiceType() {
164        return mProto.serviceType;
165    }
166
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
175    public int getVirtualMajor() {
176        return mProto.virtualMajor;
177    }
178
179    public int getVirtualMinor() {
180        return mProto.virtualMinor;
181    }
182
183    public int getFrequency() {
184        return mProto.frequency;
185    }
186
187    public String getModulation() {
188        return mProto.modulation;
189    }
190
191    public int getTsid() {
192        return mProto.tsid;
193    }
194
195    public int getVideoPid() {
196        return mProto.videoPid;
197    }
198
199    public void setVideoPid(int videoPid) {
200        mProto.videoPid = videoPid;
201    }
202
203    public int getVideoStreamType() {
204        return mProto.videoStreamType;
205    }
206
207    public int getAudioPid() {
208        if (mProto.audioTrackIndex == -1) {
209            return INVALID_PID;
210        }
211        return mProto.audioPids[mProto.audioTrackIndex];
212    }
213
214    public int getAudioStreamType() {
215        if (mProto.audioTrackIndex == -1) {
216            return INVALID_STREAMTYPE;
217        }
218        return mProto.audioStreamTypes[mProto.audioTrackIndex];
219    }
220
221    public List<Integer> getAudioPids() {
222        return Ints.asList(mProto.audioPids);
223    }
224
225    public void setAudioPids(List<Integer> audioPids) {
226        mProto.audioPids = Ints.toArray(audioPids);
227    }
228
229    public List<Integer> getAudioStreamTypes() {
230        return Ints.asList(mProto.audioStreamTypes);
231    }
232
233    public void setAudioStreamTypes(List<Integer> audioStreamTypes) {
234        mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
235    }
236
237    public int getPcrPid() {
238        return mProto.pcrPid;
239    }
240
241    public int getType() {
242        return mProto.type;
243    }
244
245    public void setFilepath(String filepath) {
246        mProto.filepath = filepath;
247    }
248
249    public String getFilepath() {
250        return mProto.filepath;
251    }
252
253    public void setFrequency(int frequency) {
254        mProto.frequency = frequency;
255    }
256
257    public void setModulation(String modulation) {
258        mProto.modulation = modulation;
259    }
260
261    public boolean hasVideo() {
262        return mProto.videoPid != INVALID_PID;
263    }
264
265    public boolean hasAudio() {
266        return getAudioPid() != INVALID_PID;
267    }
268
269    public long getChannelId() {
270        return mProto.channelId;
271    }
272
273    public void setChannelId(long channelId) {
274        mProto.channelId = channelId;
275    }
276
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
287    public String getDescription() {
288        return mProto.description;
289    }
290
291    @Override
292    public void setHasCaptionTrack() {
293        mProto.hasCaptionTrack = true;
294    }
295
296    @Override
297    public boolean hasCaptionTrack() {
298        return mProto.hasCaptionTrack;
299    }
300
301    @Override
302    public List<AtscAudioTrack> getAudioTracks() {
303        return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks));
304    }
305
306    public void setAudioTracks(List<AtscAudioTrack> audioTracks) {
307        mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]);
308    }
309
310    @Override
311    public List<AtscCaptionTrack> getCaptionTracks() {
312        return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks));
313    }
314
315    public void setCaptionTracks(List<AtscCaptionTrack> captionTracks) {
316        mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]);
317    }
318
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
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
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
359    public byte[] toByteArray() {
360        return MessageNano.toByteArray(mProto);
361    }
362
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