Home | History | Annotate | Download | only in tuner
      1 /*
      2  * Copyright (C) 2016 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;
     18 
     19 import android.content.Context;
     20 import android.os.SystemClock;
     21 import android.util.Log;
     22 import android.util.SparseBooleanArray;
     23 import com.android.tv.testing.utils.Utils;
     24 import java.io.File;
     25 import java.io.IOException;
     26 import java.io.RandomAccessFile;
     27 import java.util.Random;
     28 
     29 /** This class simulate the actions happened in TunerHal. */
     30 public class FileTunerHal extends TunerHal {
     31 
     32     private static final String TAG = "FileTunerHal";
     33     private static final int DEVICE_ID = 0;
     34     private static final int TS_PACKET_SIZE = 188;
     35     // To keep consistent with tunertvinput_jni, which fit Ethernet MTU (1500)
     36     private static final int TS_PACKET_COUNT_PER_PAYLOAD = 7;
     37     private static final int TS_PAYLOAD_SIZE = TS_PACKET_SIZE * TS_PACKET_COUNT_PER_PAYLOAD;
     38     private static final int TS_SYNC_BYTE = 0x47;
     39     // Make the read size from file and size in nativeWriteInBuffer same to make read logic simpler.
     40     private static final int MIN_READ_UNIT = TS_PACKET_SIZE * TS_PACKET_COUNT_PER_PAYLOAD;
     41     private static final long DELAY_IOCTL_SET_FRONTEND = 100;
     42     private static final long DELAY_IOCTL_WAIT_FRONTEND_LOCKED = 500;
     43 
     44     // The terrestrial broadcast mode (known as 8-VSB) delivers an MPEG-2 TS at rate
     45     // approximately 19.39 Mbps in a 6 MHz channel. (See section #5 of ATSC A/53 Part 2:2011)
     46     private static final double TS_READ_BYTES_PER_MS = 19.39 * 1024 * 1024 / (8 * 1000.0);
     47     private static final long INITIAL_BUFFERED_TS_BYTES = (long) (TS_READ_BYTES_PER_MS * 150);
     48 
     49     private static boolean sIsDeviceOpen;
     50 
     51     private final SparseBooleanArray mPids = new SparseBooleanArray();
     52     private final byte[] mBuffer = new byte[MIN_READ_UNIT];
     53     private final File mTestFile;
     54     private final Random mGenerator;
     55     private RandomAccessFile mAccessFile;
     56     private boolean mHasPendingTune;
     57     private long mReadStartMs;
     58     private long mTotalReadBytes;
     59     private long mInitialSkipMs;
     60     private boolean mPacketMissing;
     61     private boolean mEnableArtificialDelay;
     62 
     63     FileTunerHal(Context context, File testFile) {
     64         super(context);
     65         mTestFile = testFile;
     66         mGenerator = Utils.createTestRandom();
     67     }
     68 
     69     /**
     70      * Skip Initial parts of the TS file in order to start from specified time position.
     71      *
     72      * @param initialSkipMs initial position from where TS stream should be provided
     73      */
     74     void setInitialSkipMs(long initialSkipMs) {
     75         mInitialSkipMs = initialSkipMs;
     76     }
     77 
     78     @Override
     79     protected boolean openFirstAvailable() {
     80         sIsDeviceOpen = true;
     81         getDeliverySystemTypeFromDevice();
     82         return true;
     83     }
     84 
     85     @Override
     86     public void close() {}
     87 
     88     @Override
     89     protected boolean isDeviceOpen() {
     90         return sIsDeviceOpen;
     91     }
     92 
     93     @Override
     94     protected long getDeviceId() {
     95         return DEVICE_ID;
     96     }
     97 
     98     @Override
     99     protected void nativeFinalize(long deviceId) {
    100         if (deviceId != DEVICE_ID) {
    101             return;
    102         }
    103         mPids.clear();
    104     }
    105 
    106     @Override
    107     protected boolean nativeTune(
    108             long deviceId, int frequency, @ModulationType String modulation, int timeoutMs) {
    109         if (deviceId != DEVICE_ID) {
    110             return false;
    111         }
    112         if (mHasPendingTune) {
    113             return false;
    114         }
    115         closeInputStream();
    116         openInputStream();
    117         if (mAccessFile == null) {
    118             return false;
    119         }
    120 
    121         // Sleeping to simulate calling FE_GET_INFO and FE_SET_FRONTEND.
    122         if (mEnableArtificialDelay) {
    123             SystemClock.sleep(DELAY_IOCTL_SET_FRONTEND);
    124         }
    125         if (mHasPendingTune) {
    126             return false;
    127         }
    128 
    129         // Sleeping to simulate waiting frontend locked.
    130         if (mEnableArtificialDelay) {
    131             SystemClock.sleep(DELAY_IOCTL_WAIT_FRONTEND_LOCKED);
    132         }
    133         if (mHasPendingTune) {
    134             return false;
    135         }
    136         mTotalReadBytes = 0;
    137         mReadStartMs = System.currentTimeMillis();
    138         return true;
    139     }
    140 
    141     @Override
    142     protected void nativeAddPidFilter(long deviceId, int pid, @FilterType int filterType) {
    143         if (deviceId != DEVICE_ID) {
    144             return;
    145         }
    146         mPids.put(pid, true);
    147     }
    148 
    149     @Override
    150     protected void nativeCloseAllPidFilters(long deviceId) {
    151         if (deviceId != DEVICE_ID) {
    152             return;
    153         }
    154         mPids.clear();
    155     }
    156 
    157     @Override
    158     protected void nativeStopTune(long deviceId) {
    159         if (deviceId != DEVICE_ID) {
    160             return;
    161         }
    162         mPids.clear();
    163     }
    164 
    165     @Override
    166     protected int nativeWriteInBuffer(long deviceId, byte[] javaBuffer, int javaBufferSize) {
    167         if (deviceId != DEVICE_ID) {
    168             return 0;
    169         }
    170         if (mEnableArtificialDelay) {
    171             long estimatedReadBytes =
    172                     (long) (TS_READ_BYTES_PER_MS * (System.currentTimeMillis() - mReadStartMs))
    173                             + INITIAL_BUFFERED_TS_BYTES;
    174             if (estimatedReadBytes < mTotalReadBytes) {
    175                 return 0;
    176             }
    177         }
    178         int readSize = readInternal();
    179         if (readSize <= 0) {
    180             closeInputStream();
    181             openInputStream();
    182             if (mAccessFile == null) {
    183                 return -1;
    184             }
    185             readSize = readInternal();
    186         } else {
    187             mTotalReadBytes += readSize;
    188         }
    189 
    190         if (mBuffer[0] != TS_SYNC_BYTE) {
    191             return -1;
    192         }
    193         int filteredSize = 0;
    194         javaBufferSize = (javaBufferSize / TS_PACKET_SIZE) * TS_PACKET_SIZE;
    195         javaBufferSize = (javaBufferSize < TS_PAYLOAD_SIZE) ? javaBufferSize : TS_PAYLOAD_SIZE;
    196         for (int i = 0, destPos = 0;
    197                 i < readSize && destPos + TS_PACKET_SIZE <= javaBufferSize;
    198                 i += TS_PACKET_SIZE) {
    199             if (mBuffer[i] == TS_SYNC_BYTE) {
    200                 int pid = ((mBuffer[i + 1] & 0x1f) << 8) + (mBuffer[i + 2] & 0xff);
    201                 if (mPids.get(pid)) {
    202                     System.arraycopy(mBuffer, i, javaBuffer, destPos, TS_PACKET_SIZE);
    203                     destPos += TS_PACKET_SIZE;
    204                     filteredSize += TS_PACKET_SIZE;
    205                 }
    206             }
    207         }
    208         return filteredSize;
    209     }
    210 
    211     @Override
    212     protected void nativeSetHasPendingTune(long deviceId, boolean hasPendingTune) {
    213         if (deviceId != DEVICE_ID) {
    214             return;
    215         }
    216         mHasPendingTune = hasPendingTune;
    217     }
    218 
    219     @Override
    220     protected int nativeGetDeliverySystemType(long deviceId) {
    221         return DELIVERY_SYSTEM_ATSC;
    222     }
    223 
    224     private int readInternal() {
    225         int readSize;
    226         try {
    227             if (mPacketMissing) {
    228                 mAccessFile.skipBytes(
    229                         mGenerator.nextInt(TS_PACKET_COUNT_PER_PAYLOAD) * TS_PACKET_SIZE);
    230             }
    231             readSize = mAccessFile.read(mBuffer, 0, mBuffer.length);
    232         } catch (IOException e) {
    233             return -1;
    234         }
    235         return readSize;
    236     }
    237 
    238     private void closeInputStream() {
    239         try {
    240             if (mAccessFile != null) {
    241                 mAccessFile.close();
    242             }
    243         } catch (IOException e) {
    244         }
    245         mAccessFile = null;
    246     }
    247 
    248     private void openInputStream() {
    249         try {
    250             mAccessFile = new RandomAccessFile(mTestFile, "r");
    251 
    252             // Since sync frames are located once per 2 seconds, test with various
    253             // starting offsets according to mInitialSkipMs.
    254             long skipBytes = (long) (mInitialSkipMs * TS_READ_BYTES_PER_MS);
    255             skipBytes = skipBytes / TS_PACKET_SIZE * TS_PACKET_SIZE;
    256             mAccessFile.seek(skipBytes);
    257         } catch (IOException e) {
    258             Log.i(TAG, "open input stream failed:" + e);
    259         }
    260     }
    261 
    262     /** Gets the number of built-in tuner devices. Always 1 in this case. */
    263     public static int getNumberOfDevices(Context context) {
    264         return 1;
    265     }
    266 
    267     public void setEnablePacketMissing(boolean packetMissing) {
    268         mPacketMissing = packetMissing;
    269     }
    270 
    271     public void setEnableArtificialDelay(boolean enableArtificialDelay) {
    272         mEnableArtificialDelay = enableArtificialDelay;
    273     }
    274 }
    275