blob: 5801406b7cc7f499f3c7a646e50bf29b361671d4 [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;
import android.content.Context;
import android.support.annotation.IntDef;
import android.support.annotation.StringDef;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.util.Pair;
import com.android.tv.common.BuildConfig;
import com.android.tv.common.customization.CustomizationManager;
import com.android.tv.common.annotation.UsedByNative;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/** A base class to handle a hardware tuner device. */
public abstract class TunerHal implements AutoCloseable {
protected static final String TAG = "TunerHal";
protected static final boolean DEBUG = false;
@IntDef({FILTER_TYPE_OTHER, FILTER_TYPE_AUDIO, FILTER_TYPE_VIDEO, FILTER_TYPE_PCR})
@Retention(RetentionPolicy.SOURCE)
public @interface FilterType {}
public static final int FILTER_TYPE_OTHER = 0;
public static final int FILTER_TYPE_AUDIO = 1;
public static final int FILTER_TYPE_VIDEO = 2;
public static final int FILTER_TYPE_PCR = 3;
@StringDef({MODULATION_8VSB, MODULATION_QAM256})
@Retention(RetentionPolicy.SOURCE)
public @interface ModulationType {}
public static final String MODULATION_8VSB = "8VSB";
public static final String MODULATION_QAM256 = "QAM256";
@IntDef({
DELIVERY_SYSTEM_UNDEFINED,
DELIVERY_SYSTEM_ATSC,
DELIVERY_SYSTEM_DVBC,
DELIVERY_SYSTEM_DVBS,
DELIVERY_SYSTEM_DVBS2,
DELIVERY_SYSTEM_DVBT,
DELIVERY_SYSTEM_DVBT2
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeliverySystemType {}
public static final int DELIVERY_SYSTEM_UNDEFINED = 0;
public static final int DELIVERY_SYSTEM_ATSC = 1;
public static final int DELIVERY_SYSTEM_DVBC = 2;
public static final int DELIVERY_SYSTEM_DVBS = 3;
public static final int DELIVERY_SYSTEM_DVBS2 = 4;
public static final int DELIVERY_SYSTEM_DVBT = 5;
public static final int DELIVERY_SYSTEM_DVBT2 = 6;
@IntDef({TUNER_TYPE_BUILT_IN, TUNER_TYPE_USB, TUNER_TYPE_NETWORK})
@Retention(RetentionPolicy.SOURCE)
public @interface TunerType {}
public static final int TUNER_TYPE_BUILT_IN = 1;
public static final int TUNER_TYPE_USB = 2;
public static final int TUNER_TYPE_NETWORK = 3;
protected static final int PID_PAT = 0;
protected static final int PID_ATSC_SI_BASE = 0x1ffb;
protected static final int PID_DVB_SDT = 0x0011;
protected static final int PID_DVB_EIT = 0x0012;
protected static final int DEFAULT_VSB_TUNE_TIMEOUT_MS = 2000;
protected static final int DEFAULT_QAM_TUNE_TIMEOUT_MS = 4000; // Some device takes time for
// QAM256 tuning.
@IntDef({
BUILT_IN_TUNER_TYPE_LINUX_DVB
})
@Retention(RetentionPolicy.SOURCE)
private @interface BuiltInTunerType {}
private static final int BUILT_IN_TUNER_TYPE_LINUX_DVB = 1;
private static Integer sBuiltInTunerType;
protected @DeliverySystemType int mDeliverySystemType;
private boolean mIsStreaming;
private int mFrequency;
private String mModulation;
static {
if (!BuildConfig.NO_JNI_TEST) {
System.loadLibrary("tunertvinput_jni");
}
}
/**
* Creates a TunerHal instance.
*
* @param context context for creating the TunerHal instance
* @return the TunerHal instance
*/
@WorkerThread
public static synchronized TunerHal createInstance(Context context) {
TunerHal tunerHal = null;
if (DvbTunerHal.getNumberOfDevices(context) > 0) {
if (DEBUG) Log.d(TAG, "Use DvbTunerHal");
tunerHal = new DvbTunerHal(context);
}
return tunerHal != null && tunerHal.openFirstAvailable() ? tunerHal : null;
}
/** Gets the number of tuner devices currently present. */
@WorkerThread
public static Pair<Integer, Integer> getTunerTypeAndCount(Context context) {
if (useBuiltInTuner(context)) {
if (getBuiltInTunerType(context) == BUILT_IN_TUNER_TYPE_LINUX_DVB) {
return new Pair<>(TUNER_TYPE_BUILT_IN, DvbTunerHal.getNumberOfDevices(context));
}
} else {
int usbTunerCount = DvbTunerHal.getNumberOfDevices(context);
if (usbTunerCount > 0) {
return new Pair<>(TUNER_TYPE_USB, usbTunerCount);
}
}
return new Pair<>(null, 0);
}
/** Check a delivery system is for DVB or not. */
public static boolean isDvbDeliverySystem(@DeliverySystemType int deliverySystemType) {
return deliverySystemType == DELIVERY_SYSTEM_DVBC
|| deliverySystemType == DELIVERY_SYSTEM_DVBS
|| deliverySystemType == DELIVERY_SYSTEM_DVBS2
|| deliverySystemType == DELIVERY_SYSTEM_DVBT
|| deliverySystemType == DELIVERY_SYSTEM_DVBT2;
}
/**
* Returns if tuner input service would use built-in tuners instead of USB tuners or network
* tuners.
*/
public static boolean useBuiltInTuner(Context context) {
return getBuiltInTunerType(context) != 0;
}
private static @BuiltInTunerType int getBuiltInTunerType(Context context) {
if (sBuiltInTunerType == null) {
sBuiltInTunerType = 0;
if (CustomizationManager.hasLinuxDvbBuiltInTuner(context)
&& DvbTunerHal.getNumberOfDevices(context) > 0) {
sBuiltInTunerType = BUILT_IN_TUNER_TYPE_LINUX_DVB;
}
}
return sBuiltInTunerType;
}
protected TunerHal(Context context) {
mIsStreaming = false;
mFrequency = -1;
mModulation = null;
}
protected boolean isStreaming() {
return mIsStreaming;
}
protected void getDeliverySystemTypeFromDevice() {
if (mDeliverySystemType == DELIVERY_SYSTEM_UNDEFINED) {
mDeliverySystemType = nativeGetDeliverySystemType(getDeviceId());
}
}
/**
* Returns {@code true} if this tuner HAL can be reused to save tuning time between channels of
* the same frequency.
*/
public boolean isReusable() {
return true;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
close();
}
protected native void nativeFinalize(long deviceId);
/**
* Acquires the first available tuner device. If there is a tuner device that is available, the
* tuner device will be locked to the current instance.
*
* @return {@code true} if the operation was successful, {@code false} otherwise
*/
protected abstract boolean openFirstAvailable();
protected abstract boolean isDeviceOpen();
protected abstract long getDeviceId();
/**
* Sets the tuner channel. This should be called after acquiring a tuner device.
*
* @param frequency a frequency of the channel to tune to
* @param modulation a modulation method of the channel to tune to
* @param channelNumber channel number when channel number is already known. Some tuner HAL may
* use channelNumber instead of frequency for tune.
* @return {@code true} if the operation was successful, {@code false} otherwise
*/
public synchronized boolean tune(
int frequency, @ModulationType String modulation, String channelNumber) {
if (!isDeviceOpen()) {
Log.e(TAG, "There's no available device");
return false;
}
if (mIsStreaming) {
nativeCloseAllPidFilters(getDeviceId());
mIsStreaming = false;
}
// When tuning to a new channel in the same frequency, there's no need to stop current tuner
// device completely and the only thing necessary for tuning is reopening pid filters.
if (mFrequency == frequency && Objects.equals(mModulation, modulation)) {
addPidFilter(PID_PAT, FILTER_TYPE_OTHER);
addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER);
if (isDvbDeliverySystem(mDeliverySystemType)) {
addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER);
addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER);
}
mIsStreaming = true;
return true;
}
int timeout_ms =
modulation.equals(MODULATION_8VSB)
? DEFAULT_VSB_TUNE_TIMEOUT_MS
: DEFAULT_QAM_TUNE_TIMEOUT_MS;
if (nativeTune(getDeviceId(), frequency, modulation, timeout_ms)) {
addPidFilter(PID_PAT, FILTER_TYPE_OTHER);
addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER);
if (isDvbDeliverySystem(mDeliverySystemType)) {
addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER);
addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER);
}
mFrequency = frequency;
mModulation = modulation;
mIsStreaming = true;
return true;
}
return false;
}
protected native boolean nativeTune(
long deviceId, int frequency, @ModulationType String modulation, int timeout_ms);
/**
* Sets a pid filter. This should be set after setting a channel.
*
* @param pid a pid number to be added to filter list
* @param filterType a type of pid. Must be one of (FILTER_TYPE_XXX)
* @return {@code true} if the operation was successful, {@code false} otherwise
*/
public synchronized boolean addPidFilter(int pid, @FilterType int filterType) {
if (!isDeviceOpen()) {
Log.e(TAG, "There's no available device");
return false;
}
if (pid >= 0 && pid <= 0x1fff) {
nativeAddPidFilter(getDeviceId(), pid, filterType);
return true;
}
return false;
}
protected native void nativeAddPidFilter(long deviceId, int pid, @FilterType int filterType);
protected native void nativeCloseAllPidFilters(long deviceId);
protected native void nativeSetHasPendingTune(long deviceId, boolean hasPendingTune);
protected native int nativeGetDeliverySystemType(long deviceId);
/**
* Stops current tuning. The tuner device and pid filters will be reset by this call and make
* the tuner ready to accept another tune request.
*/
public synchronized void stopTune() {
if (isDeviceOpen()) {
if (mIsStreaming) {
nativeCloseAllPidFilters(getDeviceId());
}
nativeStopTune(getDeviceId());
}
mIsStreaming = false;
mFrequency = -1;
mModulation = null;
}
public void setHasPendingTune(boolean hasPendingTune) {
nativeSetHasPendingTune(getDeviceId(), hasPendingTune);
}
public int getDeliverySystemType() {
return mDeliverySystemType;
}
protected native void nativeStopTune(long deviceId);
/**
* This method must be called after {@link TunerHal#tune} and before {@link TunerHal#stopTune}.
* Writes at most maxSize TS frames in a buffer provided by the user. The frames employ MPEG
* encoding.
*
* @param javaBuffer a buffer to write the video data in
* @param javaBufferSize the max amount of bytes to write in this buffer. Usually this number
* should be equal to the length of the buffer.
* @return the amount of bytes written in the buffer. Note that this value could be 0 if no new
* frames have been obtained since the last call.
*/
public synchronized int readTsStream(byte[] javaBuffer, int javaBufferSize) {
if (isDeviceOpen()) {
return nativeWriteInBuffer(getDeviceId(), javaBuffer, javaBufferSize);
} else {
return 0;
}
}
protected native int nativeWriteInBuffer(long deviceId, byte[] javaBuffer, int javaBufferSize);
/**
* Opens Linux DVB frontend device. This method is called from native JNI and used only for
* DvbTunerHal.
*/
@UsedByNative("DvbManager.cpp")
protected int openDvbFrontEndFd() {
return -1;
}
/**
* Opens Linux DVB demux device. This method is called from native JNI and used only for
* DvbTunerHal.
*/
@UsedByNative("DvbManager.cpp")
protected int openDvbDemuxFd() {
return -1;
}
/**
* Opens Linux DVB dvr device. This method is called from native JNI and used only for
* DvbTunerHal.
*/
@UsedByNative("DvbManager.cpp")
protected int openDvbDvrFd() {
return -1;
}
}