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