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