blob: 1cf514c1646c8dab31b7273af5e789a984ed698a [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tv.tuner.data;
import android.support.annotation.NonNull;
import android.util.Log;
import com.android.tv.tuner.data.nano.Channel;
import com.android.tv.tuner.data.nano.Channel.TunerChannelProto;
import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import com.android.tv.tuner.util.Ints;
import com.android.tv.util.StringUtils;
import com.google.protobuf.nano.MessageNano;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* A class that represents a single channel accessible through a tuner.
*/
public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracksInterface {
private static final String TAG = "TunerChannel";
/**
* Channel number separator between major number and minor number.
*/
public static final char CHANNEL_NUMBER_SEPARATOR = '-';
// See ATSC Code Points Registry.
private static final String[] ATSC_SERVICE_TYPE_NAMES = new String[] {
"ATSC Reserved",
"Analog television channels",
"ATSC_digital_television",
"ATSC_audio",
"ATSC_data_only_service",
"Software Download",
"Unassociated/Small Screen Service",
"Parameterized Service",
"ATSC NRT Service",
"Extended Parameterized Service" };
private static final String ATSC_SERVICE_TYPE_NAME_RESERVED =
ATSC_SERVICE_TYPE_NAMES[Channel.SERVICE_TYPE_ATSC_RESERVED];
public static final int INVALID_FREQUENCY = -1;
// According to RFC4259, The number of available PIDs ranges from 0 to 8191.
public static final int INVALID_PID = -1;
// According to ISO13818-1, Mpeg2 StreamType has a range from 0x00 to 0xff.
public static final int INVALID_STREAMTYPE = -1;
// @GuardedBy(this) Writing operations and toByteArray will be guarded. b/34197766
private final TunerChannelProto mProto;
private TunerChannel(PsipData.VctItem channel, int programNumber,
List<PsiData.PmtItem> pmtItems, int type) {
mProto = new TunerChannelProto();
if (channel == null) {
mProto.shortName = "";
mProto.tsid = 0;
mProto.programNumber = programNumber;
mProto.virtualMajor = 0;
mProto.virtualMinor = 0;
} else {
mProto.shortName = channel.getShortName();
if (channel.getLongName() != null) {
mProto.longName = channel.getLongName();
}
mProto.tsid = channel.getChannelTsid();
mProto.programNumber = channel.getProgramNumber();
mProto.virtualMajor = channel.getMajorChannelNumber();
mProto.virtualMinor = channel.getMinorChannelNumber();
if (channel.getDescription() != null) {
mProto.description = channel.getDescription();
}
mProto.serviceType = channel.getServiceType();
}
initProto(pmtItems, type);
}
private void initProto(List<PsiData.PmtItem> pmtItems, int type) {
mProto.type = type;
mProto.channelId = -1L;
mProto.frequency = INVALID_FREQUENCY;
mProto.videoPid = INVALID_PID;
mProto.videoStreamType = INVALID_STREAMTYPE;
List<Integer> audioPids = new ArrayList<>();
List<Integer> audioStreamTypes = new ArrayList<>();
for (PsiData.PmtItem pmt : pmtItems) {
switch (pmt.getStreamType()) {
// MPEG ES stream video types
case Channel.MPEG1:
case Channel.MPEG2:
case Channel.H263:
case Channel.H264:
case Channel.H265:
mProto.videoPid = pmt.getEsPid();
mProto.videoStreamType = pmt.getStreamType();
break;
// MPEG ES stream audio types
case Channel.MPEG1AUDIO:
case Channel.MPEG2AUDIO:
case Channel.MPEG2AACAUDIO:
case Channel.MPEG4LATMAACAUDIO:
case Channel.A52AC3AUDIO:
case Channel.EAC3AUDIO:
audioPids.add(pmt.getEsPid());
audioStreamTypes.add(pmt.getStreamType());
break;
// Non MPEG ES stream types
case 0x100: // PmtItem.ES_PID_PCR:
mProto.pcrPid = pmt.getEsPid();
break;
}
}
mProto.audioPids = Ints.toArray(audioPids);
mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
mProto.audioTrackIndex = (audioPids.size() > 0) ? 0 : -1;
}
private TunerChannel(int programNumber, int type, PsipData.SdtItem channel,
List<PsiData.PmtItem> pmtItems) {
mProto = new TunerChannelProto();
mProto.tsid = 0;
mProto.virtualMajor = 0;
mProto.virtualMinor = 0;
if (channel == null) {
mProto.shortName = "";
mProto.programNumber = programNumber;
} else {
mProto.shortName = channel.getServiceName();
mProto.programNumber = channel.getServiceId();
mProto.serviceType = channel.getServiceType();
}
initProto(pmtItems, type);
}
/**
* Initialize tuner channel with VCT items and PMT items.
*/
public TunerChannel(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
this(channel, 0, pmtItems, Channel.TYPE_TUNER);
}
/**
* Initialize tuner channel with program number and PMT items.
*/
public TunerChannel(int programNumber, List<PsiData.PmtItem> pmtItems) {
this(null, programNumber, pmtItems, Channel.TYPE_TUNER);
}
/**
* Initialize tuner channel with SDT items and PMT items.
*/
public TunerChannel(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
this(0, Channel.TYPE_TUNER, channel, pmtItems);
}
private TunerChannel(TunerChannelProto tunerChannelProto) {
mProto = tunerChannelProto;
}
public static TunerChannel forFile(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
return new TunerChannel(channel, 0, pmtItems, Channel.TYPE_FILE);
}
public static TunerChannel forDvbFile(
PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
return new TunerChannel(0, Channel.TYPE_FILE, channel, pmtItems);
}
/**
* Create a TunerChannel object suitable for network tuners
* @param major Channel number major
* @param minor Channel number minor
* @param programNumber Program number
* @param shortName Short name
* @param recordingProhibited Recording prohibition info
* @param videoFormat Video format. Should be {@code null} or one of the followings:
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_240P},
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_360P},
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_480I},
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_480P},
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_576I},
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_576P},
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_720P},
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080I},
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080P},
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_2160P},
* {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_4320P}
* @return a TunerChannel object
*/
public static TunerChannel forNetwork(int major, int minor, int programNumber,
String shortName, boolean recordingProhibited, String videoFormat) {
TunerChannel tunerChannel = new TunerChannel(
null, programNumber, Collections.EMPTY_LIST, Channel.TYPE_NETWORK);
tunerChannel.setVirtualMajor(major);
tunerChannel.setVirtualMinor(minor);
tunerChannel.setShortName(shortName);
// Set audio and video pids in order to work around the audio-only channel check.
tunerChannel.setAudioPids(new ArrayList<>(Arrays.asList(0)));
tunerChannel.selectAudioTrack(0);
tunerChannel.setVideoPid(0);
tunerChannel.setRecordingProhibited(recordingProhibited);
if (videoFormat != null) {
tunerChannel.setVideoFormat(videoFormat);
}
return tunerChannel;
}
public String getName() {
return (!mProto.shortName.isEmpty()) ? mProto.shortName : mProto.longName;
}
public String getShortName() {
return mProto.shortName;
}
public int getProgramNumber() {
return mProto.programNumber;
}
public int getServiceType() {
return mProto.serviceType;
}
public String getServiceTypeName() {
int serviceType = mProto.serviceType;
if (serviceType >= 0 && serviceType < ATSC_SERVICE_TYPE_NAMES.length) {
return ATSC_SERVICE_TYPE_NAMES[serviceType];
}
return ATSC_SERVICE_TYPE_NAME_RESERVED;
}
public int getVirtualMajor() {
return mProto.virtualMajor;
}
public int getVirtualMinor() {
return mProto.virtualMinor;
}
public int getFrequency() {
return mProto.frequency;
}
public String getModulation() {
return mProto.modulation;
}
public int getTsid() {
return mProto.tsid;
}
public int getVideoPid() {
return mProto.videoPid;
}
synchronized public void setVideoPid(int videoPid) {
mProto.videoPid = videoPid;
}
public int getVideoStreamType() {
return mProto.videoStreamType;
}
public int getAudioPid() {
if (mProto.audioTrackIndex == -1) {
return INVALID_PID;
}
return mProto.audioPids[mProto.audioTrackIndex];
}
public int getAudioStreamType() {
if (mProto.audioTrackIndex == -1) {
return INVALID_STREAMTYPE;
}
return mProto.audioStreamTypes[mProto.audioTrackIndex];
}
public List<Integer> getAudioPids() {
return Ints.asList(mProto.audioPids);
}
synchronized public void setAudioPids(List<Integer> audioPids) {
mProto.audioPids = Ints.toArray(audioPids);
}
public List<Integer> getAudioStreamTypes() {
return Ints.asList(mProto.audioStreamTypes);
}
synchronized public void setAudioStreamTypes(List<Integer> audioStreamTypes) {
mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
}
public int getPcrPid() {
return mProto.pcrPid;
}
public int getType() {
return mProto.type;
}
synchronized public void setFilepath(String filepath) {
mProto.filepath = filepath == null ? "" : filepath;
}
public String getFilepath() {
return mProto.filepath;
}
synchronized public void setVirtualMajor(int virtualMajor) {
mProto.virtualMajor = virtualMajor;
}
synchronized public void setVirtualMinor(int virtualMinor) {
mProto.virtualMinor = virtualMinor;
}
synchronized public void setShortName(String shortName) {
mProto.shortName = shortName == null ? "" : shortName;
}
synchronized public void setFrequency(int frequency) {
mProto.frequency = frequency;
}
synchronized public void setModulation(String modulation) {
mProto.modulation = modulation == null ? "" : modulation;
}
public boolean hasVideo() {
return mProto.videoPid != INVALID_PID;
}
public boolean hasAudio() {
return getAudioPid() != INVALID_PID;
}
public long getChannelId() {
return mProto.channelId;
}
synchronized public void setChannelId(long channelId) {
mProto.channelId = channelId;
}
public String getDisplayNumber() {
return getDisplayNumber(true);
}
public String getDisplayNumber(boolean ignoreZeroMinorNumber) {
if (mProto.virtualMajor != 0 && (mProto.virtualMinor != 0 || !ignoreZeroMinorNumber)) {
return String.format("%d%c%d", mProto.virtualMajor, CHANNEL_NUMBER_SEPARATOR,
mProto.virtualMinor);
} else if (mProto.virtualMajor != 0) {
return Integer.toString(mProto.virtualMajor);
} else {
return Integer.toString(mProto.programNumber);
}
}
public String getDescription() {
return mProto.description;
}
@Override
synchronized public void setHasCaptionTrack() {
mProto.hasCaptionTrack = true;
}
@Override
public boolean hasCaptionTrack() {
return mProto.hasCaptionTrack;
}
@Override
public List<AtscAudioTrack> getAudioTracks() {
return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks));
}
synchronized public void setAudioTracks(List<AtscAudioTrack> audioTracks) {
mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]);
}
@Override
public List<AtscCaptionTrack> getCaptionTracks() {
return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks));
}
synchronized public void setCaptionTracks(List<AtscCaptionTrack> captionTracks) {
mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]);
}
synchronized public void selectAudioTrack(int index) {
if (0 <= index && index < mProto.audioPids.length) {
mProto.audioTrackIndex = index;
} else {
mProto.audioTrackIndex = -1;
}
}
synchronized public void setRecordingProhibited(boolean recordingProhibited) {
mProto.recordingProhibited = recordingProhibited;
}
public boolean isRecordingProhibited() {
return mProto.recordingProhibited;
}
synchronized public void setVideoFormat(String videoFormat) {
mProto.videoFormat = videoFormat == null ? "" : videoFormat;
}
public String getVideoFormat() {
return mProto.videoFormat;
}
@Override
public String toString() {
switch (mProto.type) {
case Channel.TYPE_FILE:
return String.format("{%d-%d %s} Filepath: %s, ProgramNumber %d",
mProto.virtualMajor, mProto.virtualMinor, mProto.shortName,
mProto.filepath, mProto.programNumber);
//case Channel.TYPE_TUNER:
default:
return String.format("{%d-%d %s} Frequency: %d, ProgramNumber %d",
mProto.virtualMajor, mProto.virtualMinor, mProto.shortName,
mProto.frequency, mProto.programNumber);
}
}
@Override
public int compareTo(@NonNull TunerChannel channel) {
// In the same frequency, the program number acts as the sub-channel number.
int ret = getFrequency() - channel.getFrequency();
if (ret != 0) {
return ret;
}
ret = getProgramNumber() - channel.getProgramNumber();
if (ret != 0) {
return ret;
}
ret = StringUtils.compare(getName(), channel.getName());
if (ret != 0) {
return ret;
}
// For FileTsStreamer, file paths should be compared.
return StringUtils.compare(getFilepath(), channel.getFilepath());
}
@Override
public boolean equals(Object o) {
if (!(o instanceof TunerChannel)) {
return false;
}
return compareTo((TunerChannel) o) == 0;
}
@Override
public int hashCode() {
return Objects.hash(getFrequency(), getProgramNumber(), getName(), getFilepath());
}
// Serialization
synchronized public byte[] toByteArray() {
try {
return MessageNano.toByteArray(mProto);
} catch (Exception e) {
// Retry toByteArray. b/34197766
Log.w(TAG, "TunerChannel or its variables are modified in multiple thread without lock",
e);
return MessageNano.toByteArray(mProto);
}
}
public static TunerChannel parseFrom(byte[] data) {
if (data == null) {
return null;
}
try {
return new TunerChannel(TunerChannelProto.parseFrom(data));
} catch (IOException e) {
Log.e(TAG, "Could not parse from byte array", e);
return null;
}
}
}