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.exoplayer.cache; 18 19import android.os.ConditionVariable; 20import android.os.Handler; 21import android.os.Looper; 22import android.os.Message; 23import android.util.Log; 24 25import com.google.android.exoplayer.SampleHolder; 26 27import java.io.File; 28import java.io.FileNotFoundException; 29import java.io.IOException; 30import java.io.RandomAccessFile; 31import java.nio.channels.FileChannel; 32import 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 */ 38public 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 { 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 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 77 public SampleHolder pollSample() { 78 return mSamples.poll(); 79 } 80 81 public void offerSample(SampleHolder sample) { 82 mSamples.offer(sample); 83 } 84 85 public void setCanReadMore(boolean canReadMore) { 86 mCanReadMore = canReadMore; 87 } 88 89 public boolean canReadMore() { 90 return mCanReadMore || !mSamples.isEmpty(); 91 } 92 93 public boolean hasEnoughSamples() { 94 return mSamples.size() > NUM_SAMPLES; 95 } 96 97 public void setSize(long size) { 98 mSize = size; 99 } 100 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 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 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 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 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 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 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 269 private void handleDelete() throws IOException { 270 mDeleteAtEof = true; 271 maybeDelete(); 272 } 273 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 287 private void handleFinishWrite() throws IOException { 288 mCacheListener.onWrite(SampleCache.this); 289 mWriteFinished = true; 290 mRaf.close(); 291 mRaf = null; 292 } 293 294 private void handleResetRead() { 295 mReadOffset = 0; 296 } 297 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 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 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. 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 334 public void resetRead() { 335 mCacheState.setCanReadMore(true); 336 mIoHandler.sendMessageAtFrontOfQueue( 337 mIoHandler.obtainMessage(IoHandlerCallback.MSG_RESET_READ)); 338 } 339 340 private void setNext(SampleCache next) { 341 mNextCache = next; 342 } 343 344 public void finishWrite(SampleCache next) { 345 setNext(next); 346 mIoHandler.sendEmptyMessage(IoHandlerCallback.MSG_FINISH_WRITE); 347 } 348 349 public long getStartPositionUs() { 350 return mStartPositionUs; 351 } 352 353 public SampleCache getNext() { 354 return mNextCache; 355 } 356 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 368 public long getEndPositionUs() { 369 return mEndPositionUs; 370 } 371 372 public long getCreatedTimeMs() { 373 return mCreatedTimeMs; 374 } 375 376 public boolean canReadMore() { 377 return mCacheState.canReadMore(); 378 } 379 380 public SampleHolder maybeReadSample() { 381 SampleHolder sample = mCacheState.pollSample(); 382 mIoHandler.sendEmptyMessage(IoHandlerCallback.MSG_READ); 383 return sample; 384 } 385 386 public void close() { 387 mIoHandler.sendEmptyMessage(IoHandlerCallback.MSG_CLOSE); 388 } 389 390 public void delete() { 391 mIoHandler.sendEmptyMessage(IoHandlerCallback.MSG_DELETE); 392 } 393 394 public void clear() { 395 mIoHandler.sendMessageAtFrontOfQueue(mIoHandler.obtainMessage(IoHandlerCallback.MSG_CLEAR)); 396 } 397 398 public long getSize() { 399 return mCacheState.getSize(); 400 } 401} 402