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