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, 164 insertions, 116 deletions
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
index eb596e93..112e9dc4 100644
--- a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
+++ b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
@@ -25,13 +25,14 @@ 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;
@@ -59,7 +60,8 @@ 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, SampleChunk>> mChunkMap = new ArrayMap<>();
+ private final Map<String, SortedMap<Long, Pair<SampleChunk, Integer>>> mChunkMap =
+ new ArrayMap<>();
private final Map<String, Long> mStartPositionMap = new ArrayMap<>();
private final Map<String, ChunkEvictedListener> mEvictListeners = new ArrayMap<>();
private final StorageManager mStorageManager;
@@ -77,13 +79,11 @@ 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,6 +174,66 @@ 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 {
@@ -186,11 +246,6 @@ 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
@@ -220,29 +275,27 @@ public class BufferManager {
* Reads track name & {@link MediaFormat} from storage.
*
* @param isAudio {@code true} if it is for audio track
- * @return {@link Pair} of track name & {@link MediaFormat}
- * @throws IOException
+ * @return {@link List} of TrackFormat
*/
- Pair<String, MediaFormat> readTrackInfoFile(boolean isAudio) throws IOException;
+ List<TrackFormat> readTrackInfoFiles(boolean isAudio);
/**
- * Reads sample indexes for each written sample from storage.
+ * Reads key sample positions for each written sample from storage.
*
* @param trackId track name
* @return indexes of the specified track
* @throws IOException
*/
- ArrayList<Long> readIndexFile(String trackId) throws IOException;
+ ArrayList<PositionHolder> readIndexFile(String trackId) throws IOException;
/**
* Writes track information to storage.
*
- * @param trackId track name
- * @param format {@link android.media.MediaFormat} of the track
+ * @param formatList {@list List} of TrackFormat
* @param isAudio {@code true} if it is for audio track
* @throws IOException
*/
- void writeTrackInfoFile(String trackId, MediaFormat format, boolean isAudio)
+ void writeTrackInfoFiles(List<TrackFormat> formatList, boolean isAudio)
throws IOException;
/**
@@ -252,7 +305,7 @@ public class BufferManager {
* @param index {@link SampleChunk} container
* @throws IOException
*/
- void writeIndexFile(String trackName, SortedMap<Long, SampleChunk> index)
+ void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index)
throws IOException;
}
@@ -307,7 +360,6 @@ public class BufferManager {
SampleChunk.SampleChunkCreator sampleChunkCreator) {
mStorageManager = storageManager;
mSampleChunkCreator = sampleChunkCreator;
- clearBuffer(true);
}
public void registerChunkEvictedListener(String id, ChunkEvictedListener listener) {
@@ -318,44 +370,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.
+ * Creates a new {@link SampleChunk} for caching samples if it is needed.
*
* @param id the name of the track
- * @param positionUs starting position of the {@link SampleChunk} in micro seconds.
+ * @param positionUs current position to write a sample 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 createNewWriteFile(String id, long positionUs,
- SamplePool samplePool) throws IOException {
+ public SampleChunk createNewWriteFileIfNeeded(String id, long positionUs, SamplePool samplePool,
+ SampleChunk currentChunk, int currentOffset) throws IOException {
if (!maybeEvictChunk()) {
throw new IOException("Not enough storage space");
}
- SortedMap<Long, SampleChunk> map = mChunkMap.get(id);
+ SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(id);
if (map == null) {
map = new TreeMap<>();
mChunkMap.put(id, map);
mStartPositionMap.put(id, positionUs);
mPendingDelete.init(id);
}
- File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs));
- SampleChunk sampleChunk = mSampleChunkCreator.createSampleChunk(samplePool, file,
- positionUs, mChunkCallback);
- map.put(positionUs, sampleChunk);
- return sampleChunk;
+ 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;
+ }
}
/**
@@ -366,10 +418,10 @@ public class BufferManager {
* @throws IOException
*/
public void loadTrackFromStorage(String trackId, SamplePool samplePool) throws IOException {
- ArrayList<Long> keyPositions = mStorageManager.readIndexFile(trackId);
- long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0) : 0;
+ ArrayList<PositionHolder> keyPositions = mStorageManager.readIndexFile(trackId);
+ long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0).positionUs : 0;
- SortedMap<Long, SampleChunk> map = mChunkMap.get(trackId);
+ SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(trackId);
if (map == null) {
map = new TreeMap<>();
mChunkMap.put(trackId, map);
@@ -377,11 +429,15 @@ public class BufferManager {
mPendingDelete.init(trackId);
}
SampleChunk chunk = null;
- for (long positionUs: keyPositions) {
- chunk = mSampleChunkCreator.loadSampleChunkFromFile(samplePool,
- mStorageManager.getBufferDir(), getFileName(trackId, positionUs), positionUs,
- mChunkCallback, chunk);
- map.put(positionUs, chunk);
+ 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));
}
}
@@ -392,19 +448,19 @@ public class BufferManager {
* @param positionUs the position.
* @return returns the found {@link SampleChunk}.
*/
- public SampleChunk getReadFile(String id, long positionUs) {
- SortedMap<Long, SampleChunk> map = mChunkMap.get(id);
+ public Pair<SampleChunk, Integer> getReadFile(String id, long positionUs) {
+ SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(id);
if (map == null) {
return null;
}
- SampleChunk sampleChunk;
- SortedMap<Long, SampleChunk> headMap = map.headMap(positionUs + 1);
+ Pair<SampleChunk, Integer> ret;
+ SortedMap<Long, Pair<SampleChunk, Integer>> headMap = map.headMap(positionUs + 1);
if (!headMap.isEmpty()) {
- sampleChunk = headMap.get(headMap.lastKey());
+ ret = headMap.get(headMap.lastKey());
} else {
- sampleChunk = map.get(map.firstKey());
+ ret = map.get(map.firstKey());
}
- return sampleChunk;
+ return ret;
}
/**
@@ -439,15 +495,16 @@ public class BufferManager {
// Since chunks are persistent, we cannot evict chunks.
return false;
}
- SortedMap<Long, SampleChunk> earliestChunkMap = null;
+ SortedMap<Long, Pair<SampleChunk, Integer>> earliestChunkMap = null;
SampleChunk earliestChunk = null;
String earliestChunkId = null;
- for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) {
- SortedMap<Long, SampleChunk> map = entry.getValue();
+ for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
+ mChunkMap.entrySet()) {
+ SortedMap<Long, Pair<SampleChunk, Integer>> map = entry.getValue();
if (map.isEmpty()) {
continue;
}
- SampleChunk chunk = map.get(map.firstKey());
+ SampleChunk chunk = map.get(map.firstKey()).first;
if (earliestChunk == null
|| chunk.getCreatedTimeMs() < earliestChunk.getCreatedTimeMs()) {
earliestChunkMap = map;
@@ -473,8 +530,9 @@ public class BufferManager {
}
pendingDelete = mPendingDelete.getSize();
}
- for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) {
- SortedMap<Long, SampleChunk> map = entry.getValue();
+ for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
+ mChunkMap.entrySet()) {
+ SortedMap<Long, Pair<SampleChunk, Integer>> map = entry.getValue();
if (map.isEmpty()) {
continue;
}
@@ -489,70 +547,74 @@ public class BufferManager {
* @return returns all track information which is found by {@link BufferManager.StorageManager}.
* @throws IOException
*/
- 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.
+ 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");
}
- try {
- trackInfos.add(mStorageManager.readTrackInfoFile(true));
- } catch (FileNotFoundException e) {
- // See above catch block.
- }
- return trackInfos;
+ return trackFormatList;
}
/**
* Writes track information and index information for all tracks.
*
- * @param audio audio information.
- * @param video video information.
+ * @param audios list of audio track information
+ * @param videos list of audio track information
* @throws IOException
*/
- public void writeMetaFiles(Pair<String, MediaFormat> audio, Pair<String, MediaFormat> video)
+ public void writeMetaFiles(List<TrackFormat> audios, List<TrackFormat> videos)
throws IOException {
- 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");
+ 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);
}
- mStorageManager.writeIndexFile(audio.first, 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");
+ 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);
}
- 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() {
- mPendingDelete.release();
- for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) {
- for (SampleChunk chunk : entry.getValue().values()) {
- SampleChunk.IoState.release(chunk, !mStorageManager.isPersistent());
+ 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());
+ }
+ }
}
- }
- mChunkMap.clear();
- if (mClosed) {
- clearBuffer(!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());
}
}
@@ -611,20 +673,6 @@ 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.
*/