aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java')
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java280
1 files changed, 116 insertions, 164 deletions
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
index 112e9dc4..eb596e93 100644
--- a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
+++ b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
@@ -25,14 +25,13 @@ import android.util.Log;
import android.util.Pair;
import com.google.android.exoplayer.SampleHolder;
-import com.android.tv.common.SoftPreconditions;
import com.android.tv.tuner.exoplayer.SampleExtractor;
import com.android.tv.util.Utils;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.ConcurrentModificationException;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -60,8 +59,7 @@ public class BufferManager {
private final SampleChunk.SampleChunkCreator mSampleChunkCreator;
// Maps from track name to a map which maps from starting position to {@link SampleChunk}.
- private final Map<String, SortedMap<Long, Pair<SampleChunk, Integer>>> mChunkMap =
- new ArrayMap<>();
+ private final Map<String, SortedMap<Long, SampleChunk>> mChunkMap = new ArrayMap<>();
private final Map<String, Long> mStartPositionMap = new ArrayMap<>();
private final Map<String, ChunkEvictedListener> mEvictListeners = new ArrayMap<>();
private final StorageManager mStorageManager;
@@ -79,11 +77,13 @@ public class BufferManager {
}
};
+ private volatile boolean mClosed = false;
private int mMinSampleSizeForSpeedCheck = MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK;
private long mTotalWriteSize;
private long mTotalWriteTimeNs;
private float mWriteBandwidth = 0.0f;
private volatile int mSpeedCheckCount;
+ private boolean mDisabled = false;
public interface ChunkEvictedListener {
void onChunkEvicted(String id, long createdTimeMs);
@@ -174,66 +174,6 @@ public class BufferManager {
}
/**
- * A Track format which will be loaded and saved from the permanent storage for recordings.
- */
- public static class TrackFormat {
-
- /**
- * The track id for the specified track. The track id will be used as a track identifier
- * for recordings.
- */
- public final String trackId;
-
- /**
- * The {@link MediaFormat} for the specified track.
- */
- public final MediaFormat format;
-
- /**
- * Creates TrackFormat.
- * @param trackId
- * @param format
- */
- public TrackFormat(String trackId, MediaFormat format) {
- this.trackId = trackId;
- this.format = format;
- }
- }
-
- /**
- * A Holder for a sample position which will be loaded from the index file for recordings.
- */
- public static class PositionHolder {
-
- /**
- * The current sample position in microseconds.
- * The position is identical to the PTS(presentation time stamp) of the sample.
- */
- public final long positionUs;
-
- /**
- * Base sample position for the current {@link SampleChunk}.
- */
- public final long basePositionUs;
-
- /**
- * The file offset for the current sample in the current {@link SampleChunk}.
- */
- public final int offset;
-
- /**
- * Creates a holder for a specific position in the recording.
- * @param positionUs
- * @param offset
- */
- public PositionHolder(long positionUs, long basePositionUs, int offset) {
- this.positionUs = positionUs;
- this.basePositionUs = basePositionUs;
- this.offset = offset;
- }
- }
-
- /**
* Storage configuration and policy manager for {@link BufferManager}
*/
public interface StorageManager {
@@ -246,6 +186,11 @@ public class BufferManager {
File getBufferDir();
/**
+ * Cleans up storage.
+ */
+ void clearStorage();
+
+ /**
* Informs whether the storage is used for persistent use. (eg. dvr recording/play)
*
* @return {@code true} if stored files are persistent
@@ -275,27 +220,29 @@ public class BufferManager {
* Reads track name & {@link MediaFormat} from storage.
*
* @param isAudio {@code true} if it is for audio track
- * @return {@link List} of TrackFormat
+ * @return {@link Pair} of track name & {@link MediaFormat}
+ * @throws IOException
*/
- List<TrackFormat> readTrackInfoFiles(boolean isAudio);
+ Pair<String, MediaFormat> readTrackInfoFile(boolean isAudio) throws IOException;
/**
- * Reads key sample positions for each written sample from storage.
+ * Reads sample indexes for each written sample from storage.
*
* @param trackId track name
* @return indexes of the specified track
* @throws IOException
*/
- ArrayList<PositionHolder> readIndexFile(String trackId) throws IOException;
+ ArrayList<Long> readIndexFile(String trackId) throws IOException;
/**
* Writes track information to storage.
*
- * @param formatList {@list List} of TrackFormat
+ * @param trackId track name
+ * @param format {@link android.media.MediaFormat} of the track
* @param isAudio {@code true} if it is for audio track
* @throws IOException
*/
- void writeTrackInfoFiles(List<TrackFormat> formatList, boolean isAudio)
+ void writeTrackInfoFile(String trackId, MediaFormat format, boolean isAudio)
throws IOException;
/**
@@ -305,7 +252,7 @@ public class BufferManager {
* @param index {@link SampleChunk} container
* @throws IOException
*/
- void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index)
+ void writeIndexFile(String trackName, SortedMap<Long, SampleChunk> index)
throws IOException;
}
@@ -360,6 +307,7 @@ public class BufferManager {
SampleChunk.SampleChunkCreator sampleChunkCreator) {
mStorageManager = storageManager;
mSampleChunkCreator = sampleChunkCreator;
+ clearBuffer(true);
}
public void registerChunkEvictedListener(String id, ChunkEvictedListener listener) {
@@ -370,44 +318,44 @@ public class BufferManager {
mEvictListeners.remove(id);
}
+ private void clearBuffer(boolean deleteFiles) {
+ mChunkMap.clear();
+ if (deleteFiles) {
+ mStorageManager.clearStorage();
+ }
+ mBufferSize = 0;
+ }
+
private static String getFileName(String id, long positionUs) {
return String.format(Locale.ENGLISH, "%s_%016x.chunk", id, positionUs);
}
/**
- * Creates a new {@link SampleChunk} for caching samples if it is needed.
+ * Creates a new {@link SampleChunk} for caching samples.
*
* @param id the name of the track
- * @param positionUs current position to write a sample in micro seconds.
+ * @param positionUs starting position of the {@link SampleChunk} in micro seconds.
* @param samplePool {@link SamplePool} for the fast creation of samples.
- * @param currentChunk the current {@link SampleChunk} to write, {@code null} when to create
- * a new {@link SampleChunk}.
- * @param currentOffset the current offset to write.
* @return returns the created {@link SampleChunk}.
* @throws IOException
*/
- public SampleChunk createNewWriteFileIfNeeded(String id, long positionUs, SamplePool samplePool,
- SampleChunk currentChunk, int currentOffset) throws IOException {
+ public SampleChunk createNewWriteFile(String id, long positionUs,
+ SamplePool samplePool) throws IOException {
if (!maybeEvictChunk()) {
throw new IOException("Not enough storage space");
}
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(id);
+ SortedMap<Long, SampleChunk> map = mChunkMap.get(id);
if (map == null) {
map = new TreeMap<>();
mChunkMap.put(id, map);
mStartPositionMap.put(id, positionUs);
mPendingDelete.init(id);
}
- if (currentChunk == null) {
- File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs));
- SampleChunk sampleChunk = mSampleChunkCreator
- .createSampleChunk(samplePool, file, positionUs, mChunkCallback);
- map.put(positionUs, new Pair(sampleChunk, 0));
- return sampleChunk;
- } else {
- map.put(positionUs, new Pair(currentChunk, currentOffset));
- return null;
- }
+ File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs));
+ SampleChunk sampleChunk = mSampleChunkCreator.createSampleChunk(samplePool, file,
+ positionUs, mChunkCallback);
+ map.put(positionUs, sampleChunk);
+ return sampleChunk;
}
/**
@@ -418,10 +366,10 @@ public class BufferManager {
* @throws IOException
*/
public void loadTrackFromStorage(String trackId, SamplePool samplePool) throws IOException {
- ArrayList<PositionHolder> keyPositions = mStorageManager.readIndexFile(trackId);
- long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0).positionUs : 0;
+ ArrayList<Long> keyPositions = mStorageManager.readIndexFile(trackId);
+ long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0) : 0;
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(trackId);
+ SortedMap<Long, SampleChunk> map = mChunkMap.get(trackId);
if (map == null) {
map = new TreeMap<>();
mChunkMap.put(trackId, map);
@@ -429,15 +377,11 @@ public class BufferManager {
mPendingDelete.init(trackId);
}
SampleChunk chunk = null;
- long basePositionUs = -1;
- for (PositionHolder position: keyPositions) {
- if (position.basePositionUs != basePositionUs) {
- chunk = mSampleChunkCreator.loadSampleChunkFromFile(samplePool,
- mStorageManager.getBufferDir(), getFileName(trackId, position.positionUs),
- position.positionUs, mChunkCallback, chunk);
- basePositionUs = position.basePositionUs;
- }
- map.put(position.positionUs, new Pair(chunk, position.offset));
+ for (long positionUs: keyPositions) {
+ chunk = mSampleChunkCreator.loadSampleChunkFromFile(samplePool,
+ mStorageManager.getBufferDir(), getFileName(trackId, positionUs), positionUs,
+ mChunkCallback, chunk);
+ map.put(positionUs, chunk);
}
}
@@ -448,19 +392,19 @@ public class BufferManager {
* @param positionUs the position.
* @return returns the found {@link SampleChunk}.
*/
- public Pair<SampleChunk, Integer> getReadFile(String id, long positionUs) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(id);
+ public SampleChunk getReadFile(String id, long positionUs) {
+ SortedMap<Long, SampleChunk> map = mChunkMap.get(id);
if (map == null) {
return null;
}
- Pair<SampleChunk, Integer> ret;
- SortedMap<Long, Pair<SampleChunk, Integer>> headMap = map.headMap(positionUs + 1);
+ SampleChunk sampleChunk;
+ SortedMap<Long, SampleChunk> headMap = map.headMap(positionUs + 1);
if (!headMap.isEmpty()) {
- ret = headMap.get(headMap.lastKey());
+ sampleChunk = headMap.get(headMap.lastKey());
} else {
- ret = map.get(map.firstKey());
+ sampleChunk = map.get(map.firstKey());
}
- return ret;
+ return sampleChunk;
}
/**
@@ -495,16 +439,15 @@ public class BufferManager {
// Since chunks are persistent, we cannot evict chunks.
return false;
}
- SortedMap<Long, Pair<SampleChunk, Integer>> earliestChunkMap = null;
+ SortedMap<Long, SampleChunk> earliestChunkMap = null;
SampleChunk earliestChunk = null;
String earliestChunkId = null;
- for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
- mChunkMap.entrySet()) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map = entry.getValue();
+ for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) {
+ SortedMap<Long, SampleChunk> map = entry.getValue();
if (map.isEmpty()) {
continue;
}
- SampleChunk chunk = map.get(map.firstKey()).first;
+ SampleChunk chunk = map.get(map.firstKey());
if (earliestChunk == null
|| chunk.getCreatedTimeMs() < earliestChunk.getCreatedTimeMs()) {
earliestChunkMap = map;
@@ -530,9 +473,8 @@ public class BufferManager {
}
pendingDelete = mPendingDelete.getSize();
}
- for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
- mChunkMap.entrySet()) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map = entry.getValue();
+ for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) {
+ SortedMap<Long, SampleChunk> map = entry.getValue();
if (map.isEmpty()) {
continue;
}
@@ -547,74 +489,70 @@ public class BufferManager {
* @return returns all track information which is found by {@link BufferManager.StorageManager}.
* @throws IOException
*/
- public List<TrackFormat> readTrackInfoFiles() throws IOException {
- List<TrackFormat> trackFormatList = new ArrayList<>();
- trackFormatList.addAll(mStorageManager.readTrackInfoFiles(false));
- trackFormatList.addAll(mStorageManager.readTrackInfoFiles(true));
- if (trackFormatList.isEmpty()) {
- throw new IOException("No track information to load");
+ public ArrayList<Pair<String, MediaFormat>> readTrackInfoFiles() throws IOException {
+ ArrayList<Pair<String, MediaFormat>> trackInfos = new ArrayList<>();
+ try {
+ trackInfos.add(mStorageManager.readTrackInfoFile(false));
+ } catch (FileNotFoundException e) {
+ // There can be a single track only recording. (eg. audio-only, video-only)
+ // So the exception should not stop the read.
}
- return trackFormatList;
+ try {
+ trackInfos.add(mStorageManager.readTrackInfoFile(true));
+ } catch (FileNotFoundException e) {
+ // See above catch block.
+ }
+ return trackInfos;
}
/**
* Writes track information and index information for all tracks.
*
- * @param audios list of audio track information
- * @param videos list of audio track information
+ * @param audio audio information.
+ * @param video video information.
* @throws IOException
*/
- public void writeMetaFiles(List<TrackFormat> audios, List<TrackFormat> videos)
+ public void writeMetaFiles(Pair<String, MediaFormat> audio, Pair<String, MediaFormat> video)
throws IOException {
- if (audios.isEmpty() && videos.isEmpty()) {
- throw new IOException("No track information to save");
- }
- if (!audios.isEmpty()) {
- mStorageManager.writeTrackInfoFiles(audios, true);
- for (TrackFormat trackFormat : audios) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map =
- mChunkMap.get(trackFormat.trackId);
- if (map == null) {
- throw new IOException("Audio track index missing");
- }
- mStorageManager.writeIndexFile(trackFormat.trackId, map);
+ if (audio != null) {
+ mStorageManager.writeTrackInfoFile(audio.first, audio.second, true);
+ SortedMap<Long, SampleChunk> map = mChunkMap.get(audio.first);
+ if (map == null) {
+ throw new IOException("Audio track index missing");
}
+ mStorageManager.writeIndexFile(audio.first, map);
}
- if (!videos.isEmpty()) {
- mStorageManager.writeTrackInfoFiles(videos, false);
- for (TrackFormat trackFormat : videos) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map =
- mChunkMap.get(trackFormat.trackId);
- if (map == null) {
- throw new IOException("Video track index missing");
- }
- mStorageManager.writeIndexFile(trackFormat.trackId, map);
+ if (video != null) {
+ mStorageManager.writeTrackInfoFile(video.first, video.second, false);
+ SortedMap<Long, SampleChunk> map = mChunkMap.get(video.first);
+ if (map == null) {
+ throw new IOException("Video track index missing");
}
+ mStorageManager.writeIndexFile(video.first, map);
}
}
/**
+ * Marks it is closed and it is not used anymore.
+ */
+ public void close() {
+ // Clean-up may happen after this is called.
+ mClosed = true;
+ }
+
+ /**
* Releases all the resources.
*/
public void release() {
- try {
- mPendingDelete.release();
- for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
- mChunkMap.entrySet()) {
- SampleChunk toRelease = null;
- for (Pair<SampleChunk, Integer> positions : entry.getValue().values()) {
- if (toRelease != positions.first) {
- toRelease = positions.first;
- SampleChunk.IoState.release(toRelease, !mStorageManager.isPersistent());
- }
- }
+ mPendingDelete.release();
+ for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) {
+ for (SampleChunk chunk : entry.getValue().values()) {
+ SampleChunk.IoState.release(chunk, !mStorageManager.isPersistent());
}
- mChunkMap.clear();
- } catch (ConcurrentModificationException | NullPointerException e) {
- // TODO: remove this after it it confirmed that race condition issues are resolved.
- // b/32492258, b/32373376
- SoftPreconditions.checkState(false, "Exception on BufferManager#release: ",
- e.toString());
+ }
+ mChunkMap.clear();
+ if (mClosed) {
+ clearBuffer(!mStorageManager.isPersistent());
}
}
@@ -673,6 +611,20 @@ public class BufferManager {
}
/**
+ * Marks {@link BufferManager} object disabled to prevent it from the future use.
+ */
+ public void disable() {
+ mDisabled = true;
+ }
+
+ /**
+ * Returns if {@link BufferManager} object is disabled.
+ */
+ public boolean isDisabled() {
+ return mDisabled;
+ }
+
+ /**
* Returns if {@link BufferManager} has checked the write speed,
* which is suitable for Trickplay.
*/