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;
18
19import android.content.Context;
20import android.support.annotation.IntDef;
21import android.support.annotation.StringDef;
22import android.util.Log;
23
24import com.android.usbtuner.util.TisConfiguration;
25
26import java.lang.annotation.Retention;
27import java.lang.annotation.RetentionPolicy;
28import java.util.Objects;
29
30/**
31 * A base class to handle a hardware tuner device.
32 */
33public abstract class TunerHal implements AutoCloseable {
34    protected static final String TAG = "TunerHal";
35    protected static final boolean DEBUG = false;
36
37    @IntDef({ FILTER_TYPE_OTHER, FILTER_TYPE_AUDIO, FILTER_TYPE_VIDEO, FILTER_TYPE_PCR })
38    @Retention(RetentionPolicy.SOURCE)
39    public @interface FilterType {}
40    public static final int FILTER_TYPE_OTHER = 0;
41    public static final int FILTER_TYPE_AUDIO = 1;
42    public static final int FILTER_TYPE_VIDEO = 2;
43    public static final int FILTER_TYPE_PCR = 3;
44
45    @StringDef({ MODULATION_8VSB, MODULATION_QAM256 })
46    @Retention(RetentionPolicy.SOURCE)
47    public @interface ModulationType {}
48    public static final String MODULATION_8VSB = "8VSB";
49    public static final String MODULATION_QAM256 = "QAM256";
50
51    protected static final int PID_PAT = 0;
52    protected static final int PID_ATSC_SI_BASE = 0x1ffb;
53    protected static final int DEFAULT_VSB_TUNE_TIMEOUT_MS = 2000;
54    protected static final int DEFAULT_QAM_TUNE_TIMEOUT_MS = 4000; // Some device takes time for
55                                                                   // QAM256 tuning.
56    private boolean mIsStreaming;
57    private int mFrequency;
58    private String mModulation;
59
60    static {
61        System.loadLibrary("tunertvinput_jni");
62    }
63
64    /**
65     * Creates a TunerHal instance.
66     * @param context context for creating the TunerHal instance
67     * @return the TunerHal instance
68     */
69    public static TunerHal createInstance(Context context) {
70        TunerHal tunerHal;
71        if (TisConfiguration.isPackagedWithLiveChannels(context)) {
72            tunerHal = new UsbTunerHal(context);
73        } else {
74            tunerHal = new InternalTunerHal(context);
75        }
76        if (tunerHal.openFirstAvailable()) {
77            return tunerHal;
78        }
79        return null;
80    }
81
82    protected TunerHal(Context context) {
83        mIsStreaming = false;
84        mFrequency = -1;
85        mModulation = null;
86    }
87
88    protected boolean isStreaming() {
89        return mIsStreaming;
90    }
91
92    @Override
93    protected void finalize() throws Throwable {
94        super.finalize();
95        close();
96    }
97
98    protected native void nativeFinalize(long deviceId);
99
100    /**
101     * Acquires the first available tuner device. If there is a tuner device that is available, the
102     * tuner device will be locked to the current instance.
103     *
104     * @return {@code true} if the operation was successful, {@code false} otherwise
105     */
106    protected abstract boolean openFirstAvailable();
107
108    protected abstract boolean isDeviceOpen();
109
110    protected abstract long getDeviceId();
111
112    /**
113     * Sets the tuner channel. This should be called after acquiring a tuner device.
114     *
115     * @param frequency a frequency of the channel to tune to
116     * @param modulation a modulation method of the channel to tune to
117     * @return {@code true} if the operation was successful, {@code false} otherwise
118     */
119    public boolean tune(int frequency, @ModulationType String modulation) {
120        if (!isDeviceOpen()) {
121            Log.e(TAG, "There's no available device");
122            return false;
123        }
124        if (mIsStreaming) {
125            nativeCloseAllPidFilters(getDeviceId());
126            mIsStreaming = false;
127        }
128
129        // When tuning to a new channel in the same frequency, there's no need to stop current tuner
130        // device completely and the only thing necessary for tuning is reopening pid filters.
131        if (mFrequency == frequency && Objects.equals(mModulation, modulation)) {
132            addPidFilter(PID_PAT, FILTER_TYPE_OTHER);
133            addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER);
134            mIsStreaming = true;
135            return true;
136        }
137        int timeout_ms = modulation.equals(MODULATION_8VSB) ? DEFAULT_VSB_TUNE_TIMEOUT_MS
138                : DEFAULT_QAM_TUNE_TIMEOUT_MS;
139        if (nativeTune(getDeviceId(), frequency, modulation, timeout_ms)) {
140            addPidFilter(PID_PAT, FILTER_TYPE_OTHER);
141            addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER);
142            mFrequency = frequency;
143            mModulation = modulation;
144            mIsStreaming = true;
145            return true;
146        }
147        return false;
148    }
149
150    protected native boolean nativeTune(long deviceId, int frequency,
151            @ModulationType String modulation, int timeout_ms);
152
153    /**
154     * Sets a pid filter. This should be set after setting a channel.
155     *
156     * @param pid a pid number to be added to filter list
157     * @param filterType a type of pid. Must be one of (FILTER_TYPE_XXX)
158     * @return {@code true} if the operation was successful, {@code false} otherwise
159     */
160    public boolean addPidFilter(int pid, @FilterType int filterType) {
161        if (!isDeviceOpen()) {
162            Log.e(TAG, "There's no available device");
163            return false;
164        }
165        if (pid >= 0 && pid <= 0x1fff) {
166            nativeAddPidFilter(getDeviceId(), pid, filterType);
167            return true;
168        }
169        return false;
170    }
171
172    protected native void nativeAddPidFilter(long deviceId, int pid, @FilterType int filterType);
173    protected native void nativeCloseAllPidFilters(long deviceId);
174
175    /**
176     * Stops current tuning. The tuner device and pid filters will be reset by this call and make
177     * the tuner ready to accept another tune request.
178     */
179    public void stopTune() {
180        if (isDeviceOpen()) {
181            if (mIsStreaming) {
182                nativeCloseAllPidFilters(getDeviceId());
183            }
184            nativeStopTune(getDeviceId());
185        }
186        mIsStreaming = false;
187        mFrequency = -1;
188        mModulation = null;
189    }
190
191    protected native void nativeStopTune(long deviceId);
192
193    /**
194     * This method must be called after {@link TunerHal#tune} and before
195     * {@link TunerHal#stopStreaming}. Writes at most maxSize TS frames in a buffer
196     * provided by the user. The frames employ MPEG encoding.
197     *
198     * @param javaBuffer a buffer to write the video data in
199     * @param javaBufferSize the max amount of bytes to write in this buffer. Usually this number
200     *            should be equal to the length of the buffer.
201     * @return the amount of bytes written in the buffer. Note that this value could be 0 if no new
202     *         frames have been obtained since the last call.
203     */
204    public int readTsStream(byte[] javaBuffer, int javaBufferSize) {
205        if (isDeviceOpen()) {
206            return nativeWriteInBuffer(getDeviceId(), javaBuffer, javaBufferSize);
207        } else {
208            return 0;
209        }
210    }
211
212    protected native int nativeWriteInBuffer(long deviceId, byte[] javaBuffer, int javaBufferSize);
213
214    /**
215     * Opens Linux DVB frontend device. This method is called from native JNI and used only for
216     * UsbTunerHal.
217     */
218    protected int openDvbFrontEndFd() {
219        return -1;
220    }
221
222    /**
223     * Opens Linux DVB demux device. This method is called from native JNI and used only for
224     * UsbTunerHal.
225     */
226    protected int openDvbDemuxFd() {
227        return -1;
228    }
229
230    /**
231     * Opens Linux DVB dvr device. This method is called from native JNI and used only for
232     * UsbTunerHal.
233     */
234    protected int openDvbDvrFd() {
235        return -1;
236    }
237}
238