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 17 package com.android.usbtuner.exoplayer.cache; 18 19 import android.os.ConditionVariable; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.os.Message; 23 import android.util.Log; 24 25 import com.google.android.exoplayer.SampleHolder; 26 27 import java.io.File; 28 import java.io.FileNotFoundException; 29 import java.io.IOException; 30 import java.io.RandomAccessFile; 31 import java.nio.channels.FileChannel; 32 import java.util.concurrent.ConcurrentLinkedQueue; 33 34 /** 35 * {@link SampleCache} stores samples into file and make them available for read. 36 * This class is thread-safe. 37 */ 38 public class SampleCache { 39 private static final String TAG = "SampleCache"; 40 private static final boolean DEBUG = false; 41 42 private final long mCreatedTimeMs; 43 private final long mStartPositionUs; 44 private long mEndPositionUs = 0; 45 private SampleCache mNextCache = null; 46 private final CacheState mCacheState = new CacheState(); 47 private final Handler mIoHandler; 48 49 public static class SampleCacheFactory { createSampleCache(SamplePool samplePool, File file, long startPositionUs, CacheManager.CacheListener cacheListener, Looper looper)50 public SampleCache createSampleCache(SamplePool samplePool, File file, 51 long startPositionUs, CacheManager.CacheListener cacheListener, 52 Looper looper) throws IOException { 53 return new SampleCache(samplePool, file, startPositionUs, System.currentTimeMillis(), 54 cacheListener, looper); 55 } 56 createSampleCacheFromFile(SamplePool samplePool, File cacheDir, String filename, long startPositionUs, CacheManager.CacheListener cacheListener, Looper looper, SampleCache prev)57 public SampleCache createSampleCacheFromFile(SamplePool samplePool, File cacheDir, 58 String filename, long startPositionUs, CacheManager.CacheListener cacheListener, 59 Looper looper, SampleCache prev) throws IOException { 60 File file = new File(cacheDir, filename); 61 SampleCache cache = 62 new SampleCache(samplePool, file, startPositionUs, cacheListener, looper); 63 if (prev != null) { 64 prev.setNext(cache); 65 } 66 return cache; 67 } 68 } 69 70 private static class CacheState { 71 private static final int NUM_SAMPLES = 3; 72 73 private volatile boolean mCanReadMore = true; 74 private final ConcurrentLinkedQueue<SampleHolder> mSamples = new ConcurrentLinkedQueue<>(); 75 private volatile long mSize = 0; 76 pollSample()77 public SampleHolder pollSample() { 78 return mSamples.poll(); 79 } 80 offerSample(SampleHolder sample)81 public void offerSample(SampleHolder sample) { 82 mSamples.offer(sample); 83 } 84 setCanReadMore(boolean canReadMore)85 public void setCanReadMore(boolean canReadMore) { 86 mCanReadMore = canReadMore; 87 } 88 canReadMore()89 public boolean canReadMore() { 90 return mCanReadMore || !mSamples.isEmpty(); 91 } 92 hasEnoughSamples()93 public boolean hasEnoughSamples() { 94 return mSamples.size() > NUM_SAMPLES; 95 } 96 setSize(long size)97 public void setSize(long size) { 98 mSize = size; 99 } 100 getSize()101 public long getSize() { 102 return mSize; 103 } 104 } 105 106 private class IoHandlerCallback implements Handler.Callback { 107 public static final int MSG_WRITE = 1; 108 public static final int MSG_READ = 2; 109 public static final int MSG_CLOSE = 3; 110 public static final int MSG_DELETE = 4; 111 public static final int MSG_FINISH_WRITE = 5; 112 public static final int MSG_RESET_READ = 6; 113 public static final int MSG_CLEAR = 7; 114 public static final int MSG_OPEN = 8; 115 116 private static final int DELAY_MS = 10; 117 private static final int SAMPLE_HEADER_LENGTH = 16; 118 119 private final File mFile; 120 private final CacheManager.CacheListener mCacheListener; 121 private final SamplePool mSamplePool; 122 private final CacheState mCacheState; 123 private RandomAccessFile mRaf = null; 124 private long mWriteOffset = 0; 125 private long mReadOffset = 0; 126 private boolean mWriteFinished = false; 127 private boolean mDeleteAtEof = false; 128 private boolean mDeleted = false; 129 IoHandlerCallback(File file, CacheManager.CacheListener cacheListener, SamplePool samplePool, CacheState cacheState, boolean fromFile)130 public IoHandlerCallback(File file, CacheManager.CacheListener cacheListener, 131 SamplePool samplePool, CacheState cacheState, boolean fromFile) throws IOException { 132 mFile = file; 133 mCacheListener = cacheListener; 134 mSamplePool = samplePool; 135 mCacheState = cacheState; 136 if (fromFile) { 137 loadFromFile(); 138 } 139 } 140 loadFromFile()141 private void loadFromFile() throws IOException { 142 // "r" is enough 143 try (RandomAccessFile raf = new RandomAccessFile(mFile, "r")) { 144 mWriteFinished = true; 145 mWriteOffset = raf.length(); 146 mCacheState.setSize(mWriteOffset); 147 mCacheListener.onWrite(SampleCache.this); 148 } 149 } 150 151 @Override handleMessage(Message msg)152 public boolean handleMessage(Message msg) { 153 if (mDeleted) { 154 if (DEBUG) { 155 Log.d(TAG, "Ignore access to a deleted cache."); 156 } 157 return true; 158 } 159 try { 160 switch (msg.what) { 161 case MSG_WRITE: 162 handleWrite(msg); 163 return true; 164 case MSG_READ: 165 handleRead(msg); 166 return true; 167 case MSG_CLOSE: 168 handleClose(); 169 return true; 170 case MSG_DELETE: 171 handleDelete(); 172 return true; 173 case MSG_FINISH_WRITE: 174 handleFinishWrite(); 175 return true; 176 case MSG_RESET_READ: 177 handleResetRead(); 178 return true; 179 case MSG_CLEAR: 180 handleClear(msg); 181 return true; 182 case MSG_OPEN: 183 handleOpen(); 184 return true; 185 default: 186 return false; 187 } 188 } catch (IOException e) { 189 Log.e(TAG, "Error while handling file operation", e); 190 return true; 191 } 192 } 193 handleWrite(Message msg)194 private void handleWrite(Message msg) throws IOException { 195 SampleHolder sample = (SampleHolder) ((Object[])msg.obj)[0]; 196 ConditionVariable conditionVariable = (ConditionVariable) ((Object[])msg.obj)[1]; 197 try { 198 mRaf.seek(mWriteOffset); 199 mRaf.writeInt(sample.size); 200 mRaf.writeInt(sample.flags); 201 mRaf.writeLong(sample.timeUs); 202 sample.data.position(0).limit(sample.size); 203 mRaf.getChannel().position(mWriteOffset + SAMPLE_HEADER_LENGTH).write(sample.data); 204 mWriteOffset += sample.size + SAMPLE_HEADER_LENGTH; 205 mCacheState.setSize(mWriteOffset); 206 } finally { 207 conditionVariable.open(); 208 } 209 } 210 handleRead(Message msg)211 private void handleRead(Message msg) throws IOException { 212 msg.getTarget().removeMessages(MSG_READ); 213 if (mCacheState.hasEnoughSamples()) { 214 // If cache has enough samples, try again few moments later hoping that mCacheState 215 // needs a sample by then. 216 msg.getTarget().sendEmptyMessageDelayed(MSG_READ, DELAY_MS); 217 } else if (mReadOffset >= mWriteOffset) { 218 if (mWriteFinished) { 219 if (mRaf != null) { 220 mRaf.close(); 221 mRaf = null; 222 } 223 mCacheState.setCanReadMore(false); 224 maybeDelete(); 225 } else { 226 // Read reached write but write is not finished yet --- wait a few moments to 227 // see if another sample is written. 228 msg.getTarget().sendEmptyMessageDelayed(MSG_READ, DELAY_MS); 229 } 230 } else { 231 if (mRaf == null) { 232 try { 233 mRaf = new RandomAccessFile(mFile, "r"); 234 } catch (FileNotFoundException e) { 235 // Cache can be deleted by installd service. 236 Log.e(TAG, "Failed opening a random access file.", e); 237 mDeleted = true; 238 mCacheListener.onDelete(SampleCache.this); 239 return; 240 } 241 } 242 mRaf.seek(mReadOffset); 243 int size = mRaf.readInt(); 244 SampleHolder sample = mSamplePool.acquireSample(size); 245 sample.size = size; 246 sample.flags = mRaf.readInt(); 247 sample.timeUs = mRaf.readLong(); 248 sample.clearData(); 249 sample.data.put(mRaf.getChannel().map(FileChannel.MapMode.READ_ONLY, 250 mReadOffset + SAMPLE_HEADER_LENGTH, sample.size)); 251 mReadOffset += sample.size + SAMPLE_HEADER_LENGTH; 252 mCacheState.offerSample(sample); 253 msg.getTarget().sendEmptyMessage(MSG_READ); 254 } 255 } 256 handleClose()257 private void handleClose() throws IOException { 258 if (mWriteFinished) { 259 if (mRaf != null) { 260 mRaf.close(); 261 mRaf = null; 262 } 263 mReadOffset = mWriteOffset; 264 mCacheState.setCanReadMore(false); 265 maybeDelete(); 266 } 267 } 268 handleDelete()269 private void handleDelete() throws IOException { 270 mDeleteAtEof = true; 271 maybeDelete(); 272 } 273 maybeDelete()274 private void maybeDelete() throws IOException { 275 if (!mDeleteAtEof || mCacheState.canReadMore()) { 276 return; 277 } 278 if (mRaf != null) { 279 mRaf.close(); 280 mRaf = null; 281 } 282 mFile.delete(); 283 mDeleted = true; 284 mCacheListener.onDelete(SampleCache.this); 285 } 286 handleFinishWrite()287 private void handleFinishWrite() throws IOException { 288 mCacheListener.onWrite(SampleCache.this); 289 mWriteFinished = true; 290 mRaf.close(); 291 mRaf = null; 292 } 293 handleResetRead()294 private void handleResetRead() { 295 mReadOffset = 0; 296 } 297 handleClear(Message msg)298 private void handleClear(Message msg) { 299 msg.getTarget().removeMessages(MSG_READ); 300 SampleHolder sample; 301 while ((sample = mCacheState.pollSample()) != null) { 302 mSamplePool.releaseSample(sample); 303 } 304 } 305 handleOpen()306 private void handleOpen() { 307 try { 308 mRaf = new RandomAccessFile(mFile, "rw"); 309 } catch (FileNotFoundException e) { 310 Log.e(TAG, "Failed opening a random access file.", e); 311 } 312 } 313 } 314 SampleCache(SamplePool samplePool, File file, long startPositionUs, long createdTimeMs, CacheManager.CacheListener cacheListener, Looper looper)315 protected SampleCache(SamplePool samplePool, File file, long startPositionUs, 316 long createdTimeMs, CacheManager.CacheListener cacheListener, Looper looper) 317 throws IOException { 318 mEndPositionUs = mStartPositionUs = startPositionUs; 319 mCreatedTimeMs = createdTimeMs; 320 mIoHandler = new Handler(looper, 321 new IoHandlerCallback(file, cacheListener, samplePool, mCacheState, false)); 322 mIoHandler.sendEmptyMessage(IoHandlerCallback.MSG_OPEN); 323 } 324 325 // Constructor of SampleCache which is backed by the given existing file. SampleCache(SamplePool samplePool, File file, long startPositionUs, CacheManager.CacheListener cacheListener, Looper looper)326 protected SampleCache(SamplePool samplePool, File file, long startPositionUs, 327 CacheManager.CacheListener cacheListener, Looper looper) throws IOException { 328 mCreatedTimeMs = mEndPositionUs = mStartPositionUs = startPositionUs; 329 IoHandlerCallback handlerCallback = 330 new IoHandlerCallback(file, cacheListener, samplePool, mCacheState, true); 331 mIoHandler = new Handler(looper, handlerCallback); 332 } 333 resetRead()334 public void resetRead() { 335 mCacheState.setCanReadMore(true); 336 mIoHandler.sendMessageAtFrontOfQueue( 337 mIoHandler.obtainMessage(IoHandlerCallback.MSG_RESET_READ)); 338 } 339 setNext(SampleCache next)340 private void setNext(SampleCache next) { 341 mNextCache = next; 342 } 343 finishWrite(SampleCache next)344 public void finishWrite(SampleCache next) { 345 setNext(next); 346 mIoHandler.sendEmptyMessage(IoHandlerCallback.MSG_FINISH_WRITE); 347 } 348 getStartPositionUs()349 public long getStartPositionUs() { 350 return mStartPositionUs; 351 } 352 getNext()353 public SampleCache getNext() { 354 return mNextCache; 355 } 356 writeSample(SampleHolder sample, ConditionVariable conditionVariable)357 public void writeSample(SampleHolder sample, ConditionVariable conditionVariable) { 358 if (mNextCache != null) { 359 throw new IllegalStateException( 360 "Called writeSample() even though write is already finished"); 361 } 362 mEndPositionUs = sample.timeUs; 363 conditionVariable.close(); 364 mIoHandler.obtainMessage(IoHandlerCallback.MSG_WRITE, 365 new Object[] { sample, conditionVariable }).sendToTarget(); 366 } 367 getEndPositionUs()368 public long getEndPositionUs() { 369 return mEndPositionUs; 370 } 371 getCreatedTimeMs()372 public long getCreatedTimeMs() { 373 return mCreatedTimeMs; 374 } 375 canReadMore()376 public boolean canReadMore() { 377 return mCacheState.canReadMore(); 378 } 379 maybeReadSample()380 public SampleHolder maybeReadSample() { 381 SampleHolder sample = mCacheState.pollSample(); 382 mIoHandler.sendEmptyMessage(IoHandlerCallback.MSG_READ); 383 return sample; 384 } 385 close()386 public void close() { 387 mIoHandler.sendEmptyMessage(IoHandlerCallback.MSG_CLOSE); 388 } 389 delete()390 public void delete() { 391 mIoHandler.sendEmptyMessage(IoHandlerCallback.MSG_DELETE); 392 } 393 clear()394 public void clear() { 395 mIoHandler.sendMessageAtFrontOfQueue(mIoHandler.obtainMessage(IoHandlerCallback.MSG_CLEAR)); 396 } 397 getSize()398 public long getSize() { 399 return mCacheState.getSize(); 400 } 401 } 402