diff options
author | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-02-19 23:30:48 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-02-19 23:30:48 +0000 |
commit | b79a6734552b3f00cdaddb5348f82ffad5303b3e (patch) | |
tree | 8519283dd17dc3075633701968c96aeabd113adb | |
parent | 7e35dcf1c0ca95b5e3c006f63e8ec754235f795d (diff) | |
parent | c790cba30a6011ab41b3395b1a1073d06b55f35f (diff) | |
download | TV-b79a6734552b3f00cdaddb5348f82ffad5303b3e.tar.gz |
Merge "Refactor buffer classes to use Exoplayer V2 classes" am: bf7b5892a4 am: 9593ed39ed am: c790cba30a
Change-Id: Iec27ee56051f535976f6a993c850f07e26f69e62
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/MpegTsPlayerV2.java | 3 | ||||
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/buffer/BufferManager.java | 104 | ||||
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/buffer/DvrStorageManager.java | 100 | ||||
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/buffer/InputBufferPool.java (renamed from tuner/src/com/android/tv/tuner/exoplayer2/buffer/SamplePool.java) | 32 | ||||
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/buffer/MemorySampleBuffer.java | 85 | ||||
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/buffer/PlaybackBufferListener.java | 2 | ||||
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/buffer/RecordingSampleBuffer.java | 72 | ||||
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunk.java | 116 | ||||
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunkIoHelper.java | 198 | ||||
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleQueue.java | 41 | ||||
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer2/buffer/TrickplayStorageManager.java | 4 |
11 files changed, 400 insertions, 357 deletions
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsPlayerV2.java b/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsPlayerV2.java index d27f4878..4fa44c95 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsPlayerV2.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsPlayerV2.java @@ -104,6 +104,9 @@ public class MpegTsPlayerV2 } + public static final int MIN_BUFFER_MS = 0; + public static final int MIN_REBUFFER_MS = 500; + @IntDef({TRACK_TYPE_VIDEO, TRACK_TYPE_AUDIO, TRACK_TYPE_TEXT}) @Retention(RetentionPolicy.SOURCE) public @interface TrackType {} diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/BufferManager.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/BufferManager.java index 0b88ed7f..21b8d485 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/BufferManager.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/BufferManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,9 @@ import android.util.Pair; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.util.CommonUtils; -import com.android.tv.tuner.exoplayer.SampleExtractor; - -import com.google.android.exoplayer.SampleHolder; +import com.android.tv.tuner.exoplayer2.SampleExtractor; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import java.io.File; import java.io.IOException; @@ -43,11 +43,12 @@ import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; /** - * Manages {@link SampleChunk} objects. + * Reads and writes the {@link SampleChunk} objects during playback and DVR. I/O operations are + * handled by {@link StorageManager}. * - * <p>The buffer manager can be disabled, while running, if the write throughput to the associated - * external storage is detected to be lower than a threshold {@code MINIMUM_DISK_WRITE_SPEED_MBPS}". - * This leads to restarting playback flow. + * <p>The buffer manager is enabled for DVR and it can be disabled for playback, while running, if + * the write throughput to the associated external storage is detected to be lower than a threshold + * {@code MINIMUM_DISK_WRITE_SPEED_MBPS}". This leads to restarting playback flow. */ public class BufferManager { private static final String TAG = "BufferManager"; @@ -89,7 +90,12 @@ public class BufferManager { private final AtomicInteger mSpeedCheckCount = new AtomicInteger(); public interface ChunkEvictedListener { - void onChunkEvicted(String id, long createdTimeMs); + /** + * Listener for when {@link SampleChunk} is removed from track. + * + * @param createdTimeMs creation time of the evicted chunk. + */ + void onChunkEvicted(long createdTimeMs); } /** Handles I/O between BufferManager and {@link SampleExtractor}. */ public interface SampleBuffer { @@ -97,13 +103,13 @@ public class BufferManager { /** * Initializes SampleBuffer. * - * @param Ids track identifiers for storage read/write. - * @param mediaFormats meta-data for each track. - * @throws IOException + * @param ids track identifiers for storage read/write. + * @param formats meta-data for each track. + * @throws IOException if an I/O error occurs. */ void init( - @NonNull List<String> Ids, - @NonNull List<com.google.android.exoplayer.MediaFormat> mediaFormats) + @NonNull List<String> ids, + @NonNull List<Format> formats) throws IOException; /** Selects the track {@code index} for reading sample data. */ @@ -121,9 +127,9 @@ public class BufferManager { * @param index track index * @param sample sample to write at storage * @param conditionVariable notifies the completion of writing sample. - * @throws IOException + * @throws IOException if an I/O error occurs. */ - void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) + void writeSample(int index, DecoderInputBuffer sample, ConditionVariable conditionVariable) throws IOException; /** Checks whether storage write speed is slow. */ @@ -131,35 +137,30 @@ public class BufferManager { /** * Handles when write speed is slow. - * - * @throws IOException */ - void handleWriteSpeedSlow() throws IOException; + void handleWriteSpeedSlow(); /** Sets the flag when EoS was reached. */ void setEos(); /** - * Reads the next sample in the track at index {@code track} into {@code sampleHolder}, + * Reads the next sample in the track at index {@code track} into {@code DecoderInputBuffer} * returning {@link com.google.android.exoplayer.SampleSource#SAMPLE_READ} if it is * available. If the next sample is not available, returns {@link * com.google.android.exoplayer.SampleSource#NOTHING_READ}. */ - int readSample(int index, SampleHolder outSample); + int readSample(int index, DecoderInputBuffer outSample); /** Seeks to the specified time in microseconds. */ void seekTo(long positionUs); - /** Returns an estimate of the position up to which data is buffered. */ - long getBufferedPositionUs(); - /** Returns whether there is buffered data. */ - boolean continueBuffering(long positionUs); + boolean continueLoading(long positionUs); /** * Cleans up and releases everything. * - * @throws IOException + * @throws IOException if an I/O error occurs. */ void release() throws IOException; } @@ -174,17 +175,18 @@ public class BufferManager { public final String trackId; /** The {@link MediaFormat} for the specified track. */ - public final MediaFormat format; + // TODO: Refactor to Format. + public final MediaFormat mediaFormat; /** * Creates TrackFormat. * - * @param trackId - * @param format + * @param trackId Track id + * @param mediaFormat Media mediaFormat of track */ - public TrackFormat(String trackId, MediaFormat format) { + public TrackFormat(String trackId, MediaFormat mediaFormat) { this.trackId = trackId; - this.format = format; + this.mediaFormat = mediaFormat; } } @@ -206,8 +208,9 @@ public class BufferManager { /** * Creates a holder for a specific position in the recording. * - * @param positionUs - * @param offset + * @param positionUs Position in the recording + * @param basePositionUs Position of base sample + * @param offset Offset in the recording */ public PositionHolder(long positionUs, long basePositionUs, int offset) { this.positionUs = positionUs; @@ -265,7 +268,7 @@ public class BufferManager { * * @param trackId track name * @return indexes of the specified track - * @throws IOException + * @throws IOException if an I/O error occurs. */ ArrayList<PositionHolder> readIndexFile(String trackId) throws IOException; @@ -274,7 +277,7 @@ public class BufferManager { * * @param formatList {@list List} of TrackFormat * @param isAudio {@code true} if it is for audio track - * @throws IOException + * @throws IOException if an I/O error occurs. */ void writeTrackInfoFiles(List<TrackFormat> formatList, boolean isAudio) throws IOException; @@ -283,7 +286,7 @@ public class BufferManager { * * @param trackName track name * @param index {@link SampleChunk} container - * @throws IOException + * @throws IOException if an I/O error occurs. */ void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index) throws IOException; @@ -296,7 +299,7 @@ public class BufferManager { * @param position position in micro seconds * @param sampleChunk {@link SampleChunk} chunk to be added * @param offset offset - * @throws IOException + * @throws IOException if an I/O error occurs. */ void updateIndexFile( String trackName, int size, long position, SampleChunk sampleChunk, int offset) @@ -373,17 +376,17 @@ public class BufferManager { * * @param id the name of the track * @param positionUs current position to write a sample in micro seconds. - * @param samplePool {@link SamplePool} for the fast creation of samples. + * @param inputBufferPool {@link InputBufferPool} 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 + * @throws IOException if an I/O error occurs. */ public SampleChunk createNewWriteFileIfNeeded( String id, long positionUs, - SamplePool samplePool, + InputBufferPool inputBufferPool, SampleChunk currentChunk, int currentOffset, boolean updateIndexFile) @@ -402,7 +405,7 @@ public class BufferManager { File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs)); SampleChunk sampleChunk = mSampleChunkCreator.createSampleChunk( - samplePool, file, positionUs, mChunkCallback); + inputBufferPool, file, positionUs, mChunkCallback); map.put(positionUs, Pair.create(sampleChunk, 0)); if (updateIndexFile) { mStorageManager.updateIndexFile(id, map.size(), positionUs, sampleChunk, 0); @@ -422,10 +425,11 @@ public class BufferManager { * Loads a track using {@link BufferManager.StorageManager}. * * @param trackId the name of the track. - * @param samplePool {@link SamplePool} for the fast creation of samples. - * @throws IOException + * @param inputBufferPool {@link InputBufferPool} for the fast creation of samples. + * @throws IOException if an I/O error occurs. */ - public void loadTrackFromStorage(String trackId, SamplePool samplePool) throws IOException { + public void loadTrackFromStorage(String trackId, InputBufferPool inputBufferPool) + throws IOException { ArrayList<PositionHolder> keyPositions = mStorageManager.readIndexFile(trackId); long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0).positionUs : 0; @@ -442,7 +446,7 @@ public class BufferManager { if (position.basePositionUs != basePositionUs) { chunk = mSampleChunkCreator.loadSampleChunkFromFile( - samplePool, + inputBufferPool, mStorageManager.getBufferDir(), getFileName(trackId, position.positionUs), position.positionUs, @@ -484,7 +488,7 @@ public class BufferManager { * than */ public void evictChunks(String id, long earlierThanPositionUs) { - SampleChunk chunk = null; + SampleChunk chunk; while ((chunk = mPendingDelete.poll(id, earlierThanPositionUs)) != null) { SampleChunk.IoState.release(chunk, !mStorageManager.isPersistent()); } @@ -545,7 +549,7 @@ public class BufferManager { } ChunkEvictedListener listener = mEvictListeners.get(earliestChunkId); if (listener != null) { - listener.onChunkEvicted(earliestChunkId, earliestChunk.getCreatedTimeMs()); + listener.onChunkEvicted(earliestChunk.getCreatedTimeMs()); } pendingDelete = mPendingDelete.getSize(); } @@ -564,7 +568,7 @@ public class BufferManager { * Reads track information which includes {@link MediaFormat}. * * @return returns all track information which is found by {@link BufferManager.StorageManager}. - * @throws IOException + * @throws IOException if an I/O error occurs. */ public List<TrackFormat> readTrackInfoFiles() throws IOException { List<TrackFormat> trackFormatList = new ArrayList<>(); @@ -581,7 +585,7 @@ public class BufferManager { * * @param audios list of audio track information * @param videos list of audio track information - * @throws IOException + * @throws IOException if an I/O error occurs. */ public void writeMetaFiles(List<TrackFormat> audios, List<TrackFormat> videos) throws IOException { @@ -617,7 +621,7 @@ public class BufferManager { * * @param audios list of audio track information * @param videos list of audio track information - * @throws IOException + * @throws IOException if an I/O error occurs. */ public void writeMetaFilesOnly(List<TrackFormat> audios, List<TrackFormat> videos) throws IOException { diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/DvrStorageManager.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/DvrStorageManager.java index 5549c7fd..0d8a4026 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/DvrStorageManager.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/DvrStorageManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,27 +85,27 @@ public class DvrStorageManager implements BufferManager.StorageManager { return !mIsRecording || mBufferDir.getUsableSpace() >= MIN_BUFFER_BYTES; } - private void readFormatInt(DataInputStream in, MediaFormat format, String key) + private void readFormatInt(DataInputStream in, MediaFormat mediaFormat, String key) throws IOException { int val = in.readInt(); if (val != NO_VALUE) { - format.setInteger(key, val); + mediaFormat.setInteger(key, val); } } - private void readFormatLong(DataInputStream in, MediaFormat format, String key) + private void readFormatLong(DataInputStream in, MediaFormat mediaFormat, String key) throws IOException { long val = in.readLong(); if (val != NO_VALUE_LONG) { - format.setLong(key, val); + mediaFormat.setLong(key, val); } } - private void readFormatFloat(DataInputStream in, MediaFormat format, String key) + private void readFormatFloat(DataInputStream in, MediaFormat mediaFormat, String key) throws IOException { float val = in.readFloat(); if (val != NO_VALUE) { - format.setFloat(key, val); + mediaFormat.setFloat(key, val); } } @@ -119,19 +119,19 @@ public class DvrStorageManager implements BufferManager.StorageManager { return new String(strBytes, StandardCharsets.UTF_8); } - private void readFormatString(DataInputStream in, MediaFormat format, String key) + private void readFormatString(DataInputStream in, MediaFormat mediaFormat, String key) throws IOException { String str = readString(in); if (str != null) { - format.setString(key, str); + mediaFormat.setString(key, str); } } - private void readFormatStringOptional(DataInputStream in, MediaFormat format, String key) { + private void readFormatStringOptional(DataInputStream in, MediaFormat mediaFormat, String key) { try { String str = readString(in); if (str != null) { - format.setString(key, str); + mediaFormat.setString(key, str); } } catch (IOException e) { // Since we are reading optional field, ignore the exception. @@ -152,11 +152,11 @@ public class DvrStorageManager implements BufferManager.StorageManager { return buffer; } - private void readFormatByteBuffer(DataInputStream in, MediaFormat format, String key) + private void readFormatByteBuffer(DataInputStream in, MediaFormat mediaFormat, String key) throws IOException { ByteBuffer buffer = readByteBuffer(in); if (buffer != null) { - format.setByteBuffer(key, buffer); + mediaFormat.setByteBuffer(key, buffer); } } @@ -172,22 +172,22 @@ public class DvrStorageManager implements BufferManager.StorageManager { File file = new File(getBufferDir(), fileName); try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { String name = readString(in); - MediaFormat format = new MediaFormat(); - readFormatString(in, format, MediaFormat.KEY_MIME); - readFormatInt(in, format, MediaFormat.KEY_MAX_INPUT_SIZE); - readFormatInt(in, format, MediaFormat.KEY_WIDTH); - readFormatInt(in, format, MediaFormat.KEY_HEIGHT); - readFormatInt(in, format, MediaFormat.KEY_CHANNEL_COUNT); - readFormatInt(in, format, MediaFormat.KEY_SAMPLE_RATE); - readFormatFloat(in, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); + MediaFormat mediaFormat = new MediaFormat(); + readFormatString(in, mediaFormat, MediaFormat.KEY_MIME); + readFormatInt(in, mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE); + readFormatInt(in, mediaFormat, MediaFormat.KEY_WIDTH); + readFormatInt(in, mediaFormat, MediaFormat.KEY_HEIGHT); + readFormatInt(in, mediaFormat, MediaFormat.KEY_CHANNEL_COUNT); + readFormatInt(in, mediaFormat, MediaFormat.KEY_SAMPLE_RATE); + readFormatFloat(in, mediaFormat, KEY_PIXEL_WIDTH_HEIGHT_RATIO); for (int i = 0; i < 3; ++i) { - readFormatByteBuffer(in, format, "csd-" + i); + readFormatByteBuffer(in, mediaFormat, "csd-" + i); } - readFormatLong(in, format, MediaFormat.KEY_DURATION); + readFormatLong(in, mediaFormat, MediaFormat.KEY_DURATION); // This is optional since language field is added later. - readFormatStringOptional(in, format, MediaFormat.KEY_LANGUAGE); - trackFormatList.add(new BufferManager.TrackFormat(name, format)); + readFormatStringOptional(in, mediaFormat, MediaFormat.KEY_LANGUAGE); + trackFormatList.add(new BufferManager.TrackFormat(name, mediaFormat)); } catch (IOException e) { trackNotFound = true; } @@ -261,28 +261,28 @@ public class DvrStorageManager implements BufferManager.StorageManager { } } - private void writeFormatInt(DataOutputStream out, MediaFormat format, String key) + private void writeFormatInt(DataOutputStream out, MediaFormat mediaFormat, String key) throws IOException { - if (format.containsKey(key)) { - out.writeInt(format.getInteger(key)); + if (mediaFormat.containsKey(key)) { + out.writeInt(mediaFormat.getInteger(key)); } else { out.writeInt(NO_VALUE); } } - private void writeFormatLong(DataOutputStream out, MediaFormat format, String key) + private void writeFormatLong(DataOutputStream out, MediaFormat mediaFormat, String key) throws IOException { - if (format.containsKey(key)) { - out.writeLong(format.getLong(key)); + if (mediaFormat.containsKey(key)) { + out.writeLong(mediaFormat.getLong(key)); } else { out.writeLong(NO_VALUE_LONG); } } - private void writeFormatFloat(DataOutputStream out, MediaFormat format, String key) + private void writeFormatFloat(DataOutputStream out, MediaFormat mediaFormat, String key) throws IOException { - if (format.containsKey(key)) { - out.writeFloat(format.getFloat(key)); + if (mediaFormat.containsKey(key)) { + out.writeFloat(mediaFormat.getFloat(key)); } else { out.writeFloat(NO_VALUE); } @@ -296,10 +296,10 @@ public class DvrStorageManager implements BufferManager.StorageManager { } } - private void writeFormatString(DataOutputStream out, MediaFormat format, String key) + private void writeFormatString(DataOutputStream out, MediaFormat mediaFormat, String key) throws IOException { - if (format.containsKey(key)) { - writeString(out, format.getString(key)); + if (mediaFormat.containsKey(key)) { + writeString(out, mediaFormat.getString(key)); } else { out.writeInt(0); } @@ -317,10 +317,10 @@ public class DvrStorageManager implements BufferManager.StorageManager { } } - private void writeFormatByteBuffer(DataOutputStream out, MediaFormat format, String key) + private void writeFormatByteBuffer(DataOutputStream out, MediaFormat mediaFormat, String key) throws IOException { - if (format.containsKey(key)) { - writeByteBuffer(out, format.getByteBuffer(key)); + if (mediaFormat.containsKey(key)) { + writeByteBuffer(out, mediaFormat.getByteBuffer(key)); } else { out.writeInt(0); } @@ -337,18 +337,18 @@ public class DvrStorageManager implements BufferManager.StorageManager { File file = new File(getBufferDir(), fileName); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { writeString(out, trackFormat.trackId); - writeFormatString(out, trackFormat.format, MediaFormat.KEY_MIME); - writeFormatInt(out, trackFormat.format, MediaFormat.KEY_MAX_INPUT_SIZE); - writeFormatInt(out, trackFormat.format, MediaFormat.KEY_WIDTH); - writeFormatInt(out, trackFormat.format, MediaFormat.KEY_HEIGHT); - writeFormatInt(out, trackFormat.format, MediaFormat.KEY_CHANNEL_COUNT); - writeFormatInt(out, trackFormat.format, MediaFormat.KEY_SAMPLE_RATE); - writeFormatFloat(out, trackFormat.format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); + writeFormatString(out, trackFormat.mediaFormat, MediaFormat.KEY_MIME); + writeFormatInt(out, trackFormat.mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE); + writeFormatInt(out, trackFormat.mediaFormat, MediaFormat.KEY_WIDTH); + writeFormatInt(out, trackFormat.mediaFormat, MediaFormat.KEY_HEIGHT); + writeFormatInt(out, trackFormat.mediaFormat, MediaFormat.KEY_CHANNEL_COUNT); + writeFormatInt(out, trackFormat.mediaFormat, MediaFormat.KEY_SAMPLE_RATE); + writeFormatFloat(out, trackFormat.mediaFormat, KEY_PIXEL_WIDTH_HEIGHT_RATIO); for (int j = 0; j < 3; ++j) { - writeFormatByteBuffer(out, trackFormat.format, "csd-" + j); + writeFormatByteBuffer(out, trackFormat.mediaFormat, "csd-" + j); } - writeFormatLong(out, trackFormat.format, MediaFormat.KEY_DURATION); - writeFormatString(out, trackFormat.format, MediaFormat.KEY_LANGUAGE); + writeFormatLong(out, trackFormat.mediaFormat, MediaFormat.KEY_DURATION); + writeFormatString(out, trackFormat.mediaFormat, MediaFormat.KEY_LANGUAGE); } } } diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SamplePool.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/InputBufferPool.java index 8d4ee20d..b40f4a06 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SamplePool.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/InputBufferPool.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,26 +16,28 @@ package com.android.tv.tuner.exoplayer2.buffer; -import com.google.android.exoplayer.SampleHolder; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; + import java.util.LinkedList; /** Pool of samples to recycle ByteBuffers as much as possible. */ -public class SamplePool { - private final LinkedList<SampleHolder> mSamplePool = new LinkedList<>(); +public class InputBufferPool { + private final LinkedList<DecoderInputBuffer> mInputBufferPool = new LinkedList<>(); /** * Acquires a sample with a buffer larger than size from the pool. Allocate new one or resize an * existing buffer if necessary. */ - public synchronized SampleHolder acquireSample(int size) { - if (mSamplePool.isEmpty()) { - SampleHolder sample = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); + public synchronized DecoderInputBuffer acquireSample(int size) { + if (mInputBufferPool.isEmpty()) { + DecoderInputBuffer sample = + new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); sample.ensureSpaceForWrite(size); return sample; } - SampleHolder smallestSufficientSample = null; - SampleHolder maxSample = mSamplePool.getFirst(); - for (SampleHolder sample : mSamplePool) { + DecoderInputBuffer smallestSufficientSample = null; + DecoderInputBuffer maxSample = mInputBufferPool.getFirst(); + for (DecoderInputBuffer sample : mInputBufferPool) { // Grab the smallest sufficient sample. if (sample.data.capacity() >= size && (smallestSufficientSample == null @@ -48,20 +50,20 @@ public class SamplePool { maxSample = sample; } } - SampleHolder sampleFromPool = smallestSufficientSample; + DecoderInputBuffer sampleFromPool = smallestSufficientSample; // If there's no sufficient sample, grab the maximum sample and resize it to size. if (sampleFromPool == null) { sampleFromPool = maxSample; sampleFromPool.ensureSpaceForWrite(size); } - mSamplePool.remove(sampleFromPool); + mInputBufferPool.remove(sampleFromPool); return sampleFromPool; } /** Releases the sample back to the pool. */ - public synchronized void releaseSample(SampleHolder sample) { - sample.clearData(); - mSamplePool.offerLast(sample); + public synchronized void releaseSample(DecoderInputBuffer sample) { + sample.clear(); + mInputBufferPool.offerLast(sample); } } diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/MemorySampleBuffer.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/MemorySampleBuffer.java index 21c0919a..c9292535 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/MemorySampleBuffer.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/MemorySampleBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,22 +19,21 @@ package com.android.tv.tuner.exoplayer2.buffer; import android.os.ConditionVariable; import android.support.annotation.NonNull; import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.exoplayer.SampleExtractor; -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import java.io.IOException; +import com.android.tv.tuner.exoplayer2.SampleExtractor; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; + import java.util.List; /** - * Handles I/O for {@link SampleExtractor} when physical storage based buffer is not used. Trickplay - * is disabled. + * Handles I/O for {@link SampleExtractor} when Trickplay is disabled. Memory storage based buffer + * is used instead of physical storage based buffer. */ public class MemorySampleBuffer implements BufferManager.SampleBuffer { - private final SamplePool mSamplePool = new SamplePool(); + private final InputBufferPool mInputBufferPool = new InputBufferPool(); private SampleQueue[] mPlayingSampleQueues; - private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US; private volatile boolean mEos; @@ -46,8 +45,7 @@ public class MemorySampleBuffer implements BufferManager.SampleBuffer { } @Override - public synchronized void init( - @NonNull List<String> ids, @NonNull List<MediaFormat> mediaFormats) { + public synchronized void init(@NonNull List<String> ids, @NonNull List<Format> formats) { int trackCount = ids.size(); mPlayingSampleQueues = new SampleQueue[trackCount]; for (int i = 0; i < trackCount; i++) { @@ -68,7 +66,7 @@ public class MemorySampleBuffer implements BufferManager.SampleBuffer { public void selectTrack(int index) { synchronized (this) { if (mPlayingSampleQueues[index] == null) { - mPlayingSampleQueues[index] = new SampleQueue(mSamplePool); + mPlayingSampleQueues[index] = new SampleQueue(mInputBufferPool); } else { mPlayingSampleQueues[index].clear(); } @@ -76,59 +74,36 @@ public class MemorySampleBuffer implements BufferManager.SampleBuffer { } @Override - public void deselectTrack(int index) { - synchronized (this) { - if (mPlayingSampleQueues[index] != null) { - mPlayingSampleQueues[index].clear(); - mPlayingSampleQueues[index] = null; - } - } - } - - @Override - public synchronized long getBufferedPositionUs() { - Long result = null; - for (SampleQueue queue : mPlayingSampleQueues) { - if (queue == null) { - continue; - } - Long lastQueuedSamplePositionUs = queue.getLastQueuedPositionUs(); - if (lastQueuedSamplePositionUs == null) { - // No sample has been queued. - result = mLastBufferedPositionUs; - continue; - } - if (result == null || result > lastQueuedSamplePositionUs) { - result = lastQueuedSamplePositionUs; - } - } - if (result == null) { - return mLastBufferedPositionUs; + public synchronized void deselectTrack(int index) { + if (mPlayingSampleQueues[index] != null) { + mPlayingSampleQueues[index].clear(); + mPlayingSampleQueues[index] = null; } - return (mLastBufferedPositionUs = result); } @Override - public synchronized int readSample(int track, SampleHolder sampleHolder) { + public synchronized int readSample(int track, DecoderInputBuffer sampleHolder) { SampleQueue queue = mPlayingSampleQueues[track]; SoftPreconditions.checkNotNull(queue); - int result = queue == null ? SampleSource.NOTHING_READ : queue.dequeueSample(sampleHolder); - if (result != SampleSource.SAMPLE_READ && reachedEos()) { - return SampleSource.END_OF_STREAM; + int result = queue == null ? C.RESULT_NOTHING_READ : queue.dequeueSample(sampleHolder); + if (result != C.RESULT_BUFFER_READ && reachedEos()) { + return C.RESULT_END_OF_INPUT; } return result; } @Override - public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) - throws IOException { - sample.data.position(0).limit(sample.size); - SampleHolder sampleToQueue = mSamplePool.acquireSample(sample.size); - sampleToQueue.size = sample.size; - sampleToQueue.clearData(); + public void writeSample( + int index, DecoderInputBuffer sample, ConditionVariable conditionVariable) { + int size = sample.data.position(); + sample.data.position(0).limit(size); + DecoderInputBuffer sampleToQueue = mInputBufferPool.acquireSample(size); + sampleToQueue.data.clear(); sampleToQueue.data.put(sample.data); sampleToQueue.timeUs = sample.timeUs; - sampleToQueue.flags = sample.flags; + sampleToQueue.setFlags((sample.isKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0) + | (sample.isDecodeOnly() ? C.BUFFER_FLAG_DECODE_ONLY : 0) + | (sample.isEncrypted() ? C.BUFFER_FLAG_ENCRYPTED : 0)); synchronized (this) { if (mPlayingSampleQueues[index] != null) { @@ -150,7 +125,7 @@ public class MemorySampleBuffer implements BufferManager.SampleBuffer { } @Override - public synchronized boolean continueBuffering(long positionUs) { + public synchronized boolean continueLoading(long positionUs) { for (SampleQueue queue : mPlayingSampleQueues) { if (queue == null) { continue; diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/PlaybackBufferListener.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/PlaybackBufferListener.java index 228ec911..efb5a988 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/PlaybackBufferListener.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/PlaybackBufferListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/RecordingSampleBuffer.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/RecordingSampleBuffer.java index fe2bb464..b7c38654 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/RecordingSampleBuffer.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/RecordingSampleBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,14 +21,13 @@ import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.util.Log; -import com.android.tv.tuner.exoplayer.MpegTsPlayer; -import com.android.tv.tuner.exoplayer.SampleExtractor; +import com.android.tv.tuner.exoplayer2.MpegTsPlayerV2; +import com.android.tv.tuner.exoplayer2.SampleExtractor; -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.util.Assertions; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.util.Assertions; import com.google.auto.factory.AutoFactory; import com.google.auto.factory.Provided; @@ -68,7 +67,7 @@ public class RecordingSampleBuffer private static final long BUFFER_WRITE_TIMEOUT_MS = 10 * 1000; // 10 seconds private static final long BUFFER_NEEDED_US = - 1000L * Math.max(MpegTsPlayer.MIN_BUFFER_MS, MpegTsPlayer.MIN_REBUFFER_MS); + 1000L * Math.max(MpegTsPlayerV2.MIN_BUFFER_MS, MpegTsPlayerV2.MIN_REBUFFER_MS); private final BufferManager mBufferManager; private final PlaybackBufferListener mBufferListener; @@ -78,8 +77,7 @@ public class RecordingSampleBuffer private int mTrackCount; private boolean[] mTrackSelected; private List<SampleQueue> mReadSampleQueues; - private final SamplePool mSamplePool = new SamplePool(); - private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US; + private final InputBufferPool mInputBufferPool = new InputBufferPool(); private long mCurrentPlaybackPositionUs = 0; // An error in I/O thread of {@link SampleChunkIoHelper} will be notified. @@ -108,7 +106,7 @@ public class RecordingSampleBuffer * generated class. */ public interface Factory { - public RecordingSampleBuffer create( + RecordingSampleBuffer create( BufferManager bufferManager, PlaybackBufferListener bufferListener, boolean enableTrickplay, @@ -141,7 +139,7 @@ public class RecordingSampleBuffer } @Override - public void init(@NonNull List<String> ids, @NonNull List<MediaFormat> mediaFormats) + public void init(@NonNull List<String> ids, @NonNull List<Format> formats) throws IOException { mTrackCount = ids.size(); if (mTrackCount <= 0) { @@ -151,9 +149,9 @@ public class RecordingSampleBuffer mReadSampleQueues = new ArrayList<>(); mSampleChunkIoHelper = mSampleChunkIoHelperFactory.create( - ids, mediaFormats, mBufferReason, mBufferManager, mSamplePool, mIoCallback); + ids, formats, mBufferReason, mBufferManager, mInputBufferPool, mIoCallback); for (int i = 0; i < mTrackCount; ++i) { - mReadSampleQueues.add(i, new SampleQueue(mSamplePool)); + mReadSampleQueues.add(i, new SampleQueue(mInputBufferPool)); } mSampleChunkIoHelper.init(); for (int i = 0; i < mTrackCount; ++i) { @@ -180,8 +178,10 @@ public class RecordingSampleBuffer } @Override - public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) - throws IOException { + public void writeSample( + int index, + DecoderInputBuffer sample, + ConditionVariable conditionVariable) throws IOException { mSampleChunkIoHelper.writeSample(index, sample, conditionVariable); if (!conditionVariable.block(BUFFER_WRITE_TIMEOUT_MS)) { @@ -200,7 +200,7 @@ public class RecordingSampleBuffer } @Override - public void handleWriteSpeedSlow() throws IOException { + public void handleWriteSpeedSlow() { if (mBufferReason == BUFFER_REASON_RECORDING) { // Recording does not need to stop because I/O speed is slow temporarily. // If fixed size buffer of TsStreamer overflows, TsDataSource will reach EoS. @@ -233,7 +233,7 @@ public class RecordingSampleBuffer // finish the buffering state. return false; } - SampleHolder sample = mSampleChunkIoHelper.readSample(index); + DecoderInputBuffer sample = mSampleChunkIoHelper.readSample(index); if (sample != null) { queue.queueSample(sample); return true; @@ -242,12 +242,12 @@ public class RecordingSampleBuffer } @Override - public int readSample(int track, SampleHolder outSample) { + public int readSample(int track, DecoderInputBuffer outSample) { Assertions.checkState(mTrackSelected[track]); maybeReadSample(mReadSampleQueues.get(track), track); int result = mReadSampleQueues.get(track).dequeueSample(outSample); - if ((result != SampleSource.SAMPLE_READ && mEos) || mError) { - return SampleSource.END_OF_STREAM; + if ((result != C.RESULT_BUFFER_READ && mEos) || mError) { + return C.RESULT_END_OF_INPUT; } return result; } @@ -260,34 +260,10 @@ public class RecordingSampleBuffer mSampleChunkIoHelper.openRead(i, positionUs); } } - mLastBufferedPositionUs = positionUs; - } - - @Override - public long getBufferedPositionUs() { - Long result = null; - for (int i = 0; i < mTrackCount; ++i) { - if (!mTrackSelected[i]) { - continue; - } - Long lastQueuedSamplePositionUs = mReadSampleQueues.get(i).getLastQueuedPositionUs(); - if (lastQueuedSamplePositionUs == null) { - // No sample has been queued. - result = mLastBufferedPositionUs; - continue; - } - if (result == null || result > lastQueuedSamplePositionUs) { - result = lastQueuedSamplePositionUs; - } - } - if (result == null) { - return mLastBufferedPositionUs; - } - return (mLastBufferedPositionUs = result); } @Override - public boolean continueBuffering(long positionUs) { + public boolean continueLoading(long positionUs) { mCurrentPlaybackPositionUs = positionUs; for (int i = 0; i < mTrackCount; ++i) { if (!mTrackSelected[i]) { @@ -316,7 +292,7 @@ public class RecordingSampleBuffer // onChunkEvictedListener @Override - public void onChunkEvicted(String id, long createdTimeMs) { + public void onChunkEvicted(long createdTimeMs) { if (mBufferListener != null) { mBufferListener.onBufferStartTimeChanged( createdTimeMs + TimeUnit.MICROSECONDS.toMillis(MIN_SEEK_DURATION_US)); diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunk.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunk.java index 34090bc4..1a11f03b 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunk.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunk.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,10 @@ package com.android.tv.tuner.exoplayer2.buffer; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.Log; -import com.google.android.exoplayer.SampleHolder; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; + import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; @@ -34,6 +37,17 @@ public class SampleChunk { private static final String TAG = "SampleChunk"; private static final boolean DEBUG = false; + // The flag values should not be changed. + /** + * This indicates that the (encoded) buffer marked as such contains + * the data for a key frame. + */ + private static final int BUFFER_FLAG_KEY_FRAME = 1; + /** Indicates that a buffer should be decoded but not rendered. */ + private static final int BUFFER_FLAG_DECODE_ONLY = 1 << 31; // 0x80000000 + /** Indicates that a buffer is (at least partially) encrypted. */ + private static final int BUFFER_FLAG_ENCRYPTED = 1 << 30; // 0x40000000 + private final long mCreatedTimeMs; private final long mStartPositionUs; private SampleChunk mNextChunk; @@ -43,7 +57,7 @@ public class SampleChunk { private final File mFile; private final ChunkCallback mChunkCallback; - private final SamplePool mSamplePool; + private final InputBufferPool mInputBufferPool; private RandomAccessFile mAccessFile; private long mWriteOffset; private boolean mWriteFinished; @@ -74,43 +88,46 @@ public class SampleChunk { /** * Returns a newly created SampleChunk to read & write samples. * - * @param samplePool sample allocator + * @param inputBufferPool sample allocator * @param file filename which will be created newly * @param startPositionUs the start position of the earliest sample to be stored * @param chunkCallback for total storage usage change notification */ @VisibleForTesting - public SampleChunk createSampleChunk( - SamplePool samplePool, + SampleChunk createSampleChunk( + InputBufferPool inputBufferPool, File file, long startPositionUs, ChunkCallback chunkCallback) { return new SampleChunk( - samplePool, file, startPositionUs, System.currentTimeMillis(), chunkCallback); + inputBufferPool, + file, + startPositionUs, + System.currentTimeMillis(), + chunkCallback); } /** * Returns a newly created SampleChunk which is backed by an existing file. Created * SampleChunk is read-only. * - * @param samplePool sample allocator + * @param inputBufferPool sample allocator * @param bufferDir the directory where the file to read is located * @param filename the filename which will be read afterwards * @param startPositionUs the start position of the earliest sample in the file * @param chunkCallback for total storage usage change notification * @param prev the previous SampleChunk just before the newly created SampleChunk - * @throws IOException */ SampleChunk loadSampleChunkFromFile( - SamplePool samplePool, + InputBufferPool inputBufferPool, File bufferDir, String filename, long startPositionUs, ChunkCallback chunkCallback, - SampleChunk prev) - throws IOException { + SampleChunk prev) { File file = new File(bufferDir, filename); - SampleChunk chunk = new SampleChunk(samplePool, file, startPositionUs, chunkCallback); + SampleChunk chunk = + new SampleChunk(inputBufferPool, file, startPositionUs, chunkCallback); if (prev != null) { prev.mNextChunk = chunk; } @@ -123,7 +140,7 @@ public class SampleChunk { * I/O operation. */ @VisibleForTesting - public static class IoState { + static class IoState { private SampleChunk mChunk; private long mCurrentOffset; @@ -155,7 +172,7 @@ public class SampleChunk { * Prepares for read I/O operation from a new SampleChunk. * * @param chunk the new SampleChunk to read from - * @throws IOException + * @throws IOException if an I/O error occurs. */ void openRead(SampleChunk chunk, long offset) throws IOException { if (mChunk != null) { @@ -169,7 +186,7 @@ public class SampleChunk { * Prepares for write I/O operation to a new SampleChunk. * * @param chunk the new SampleChunk to write samples afterwards - * @throws IOException + * @throws IOException if an I/O error occurs. */ void openWrite(SampleChunk chunk) throws IOException { if (mChunk != null) { @@ -183,9 +200,9 @@ public class SampleChunk { * Reads a sample if it is available. * * @return Returns a sample if it is available, null otherwise. - * @throws IOException + * @throws IOException if an I/O error occurs. */ - SampleHolder read() throws IOException { + DecoderInputBuffer read() throws IOException { if (mChunk != null && mChunk.isReadFinished(this)) { SampleChunk next = mChunk.mNextChunk; mChunk.closeRead(); @@ -213,9 +230,9 @@ public class SampleChunk { * @param sample to write * @param nextChunk if this is {@code null} writes at the current SampleChunk, otherwise * close current SampleChunk and writes at this - * @throws IOException + * @throws IOException if an I/O error occurs. */ - void write(SampleHolder sample, SampleChunk nextChunk) throws IOException { + void write(DecoderInputBuffer sample, SampleChunk nextChunk) throws IOException { if (mChunk == null) { throw new IOException("mChunk should not be null"); } @@ -234,7 +251,7 @@ public class SampleChunk { /** * Finishes write I/O operation. * - * @throws IOException + * @throws IOException if an I/O error occurs. */ void closeWrite() throws IOException { if (mChunk != null) { @@ -265,26 +282,28 @@ public class SampleChunk { } @VisibleForTesting - protected SampleChunk( - SamplePool samplePool, + SampleChunk( + InputBufferPool inputBufferPool, File file, long startPositionUs, long createdTimeMs, ChunkCallback chunkCallback) { mStartPositionUs = startPositionUs; mCreatedTimeMs = createdTimeMs; - mSamplePool = samplePool; + mInputBufferPool = inputBufferPool; mFile = file; mChunkCallback = chunkCallback; } // Constructor of SampleChunk which is backed by the given existing file. private SampleChunk( - SamplePool samplePool, File file, long startPositionUs, ChunkCallback chunkCallback) - throws IOException { + InputBufferPool inputBufferPool, + File file, + long startPositionUs, + ChunkCallback chunkCallback) { mStartPositionUs = startPositionUs; mCreatedTimeMs = mStartPositionUs / 1000; - mSamplePool = samplePool; + mInputBufferPool = inputBufferPool; mFile = file; mChunkCallback = chunkCallback; mWriteFinished = true; @@ -350,7 +369,7 @@ public class SampleChunk { return mWriteFinished && state.equals(this, mWriteOffset); } - private SampleHolder read(IoState state) throws IOException { + private DecoderInputBuffer read(IoState state) throws IOException { if (mAccessFile == null || state.mChunk != this) { throw new IllegalStateException("Requested read for wrong SampleChunk"); } @@ -367,36 +386,55 @@ public class SampleChunk { } mAccessFile.seek(offset); int size = mAccessFile.readInt(); - SampleHolder sample = mSamplePool.acquireSample(size); - sample.size = size; - sample.flags = mAccessFile.readInt(); + DecoderInputBuffer sample = mInputBufferPool.acquireSample(size); + int flags = mAccessFile.readInt(); + flags = (isKeyFrame(flags) ? C.BUFFER_FLAG_KEY_FRAME : 0) + | (isDecodeOnly(flags) ? C.BUFFER_FLAG_DECODE_ONLY : 0) + | (isEncrypted(flags) ? C.BUFFER_FLAG_ENCRYPTED : 0); + sample.setFlags(flags); sample.timeUs = mAccessFile.readLong(); - sample.clearData(); + sample.data.clear(); sample.data.put( mAccessFile .getChannel() .map( FileChannel.MapMode.READ_ONLY, offset + SAMPLE_HEADER_LENGTH, - sample.size)); - offset += sample.size + SAMPLE_HEADER_LENGTH; + size)); + offset += size + SAMPLE_HEADER_LENGTH; state.mCurrentOffset = offset; return sample; } + private boolean isKeyFrame(int flag) { + return (flag & BUFFER_FLAG_KEY_FRAME) == BUFFER_FLAG_KEY_FRAME; + } + + private boolean isDecodeOnly(int flag) { + return (flag & BUFFER_FLAG_DECODE_ONLY) == BUFFER_FLAG_DECODE_ONLY; + } + + private boolean isEncrypted(int flag) { + return (flag & BUFFER_FLAG_ENCRYPTED) == BUFFER_FLAG_ENCRYPTED; + } + @VisibleForTesting - protected void write(SampleHolder sample, IoState state) throws IOException { + void write(DecoderInputBuffer sample, IoState state) throws IOException { if (mAccessFile == null || mNextChunk != null || !state.equals(this, mWriteOffset)) { throw new IllegalStateException("Requested write for wrong SampleChunk"); } mAccessFile.seek(mWriteOffset); - mAccessFile.writeInt(sample.size); - mAccessFile.writeInt(sample.flags); + int size = sample.data.position(); + mAccessFile.writeInt(size); + int flags = (sample.isKeyFrame() ? BUFFER_FLAG_KEY_FRAME : 0) + | (sample.isDecodeOnly() ? BUFFER_FLAG_DECODE_ONLY : 0) + | (sample.isEncrypted() ? BUFFER_FLAG_ENCRYPTED : 0); + mAccessFile.writeInt(flags); mAccessFile.writeLong(sample.timeUs); - sample.data.position(0).limit(sample.size); + sample.data.position(0).limit(size); mAccessFile.getChannel().position(mWriteOffset + SAMPLE_HEADER_LENGTH).write(sample.data); - mWriteOffset += sample.size + SAMPLE_HEADER_LENGTH; + mWriteOffset += size + SAMPLE_HEADER_LENGTH; state.mCurrentOffset = mWriteOffset; } diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunkIoHelper.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunkIoHelper.java index da3dd725..5a3f6682 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunkIoHelper.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunkIoHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package com.android.tv.tuner.exoplayer2.buffer; -import android.media.MediaCodec; +import android.media.MediaFormat; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; @@ -26,11 +26,13 @@ import android.util.Log; import android.util.Pair; import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer.BufferReason; +import com.android.tv.tuner.exoplayer2.buffer.RecordingSampleBuffer.BufferReason; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.util.MimeTypes; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.mediacodec.MediaFormatUtil; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import com.google.auto.factory.AutoFactory; import java.io.IOException; @@ -62,15 +64,15 @@ public class SampleChunkIoHelper implements Handler.Callback { private final long mSampleChunkDurationUs; private final int mTrackCount; private final List<String> mIds; - private final List<MediaFormat> mMediaFormats; + private final List<Format> mFormats; private final @BufferReason int mBufferReason; private final BufferManager mBufferManager; - private final SamplePool mSamplePool; + private final InputBufferPool mInputBufferPool; private final IoCallback mIoCallback; private Handler mIoHandler; - private final ConcurrentLinkedQueue<SampleHolder> mReadSampleBuffers[]; - private final ConcurrentLinkedQueue<SampleHolder> mHandlerReadSampleBuffers[]; + private final ConcurrentLinkedQueue<DecoderInputBuffer>[] mReadSampleBuffers; + private final ConcurrentLinkedQueue<DecoderInputBuffer>[] mHandlerReadSampleBuffers; private final long[] mWriteIndexEndPositionUs; private final long[] mWriteChunkEndPositionUs; private final SampleChunk.IoState[] mReadIoStates; @@ -96,16 +98,16 @@ public class SampleChunkIoHelper implements Handler.Callback { private static class IoParams { private final int index; private final long positionUs; - private final SampleHolder sample; + private final DecoderInputBuffer sample; private final ConditionVariable conditionVariable; - private final ConcurrentLinkedQueue<SampleHolder> readSampleBuffer; + private final ConcurrentLinkedQueue<DecoderInputBuffer> readSampleBuffer; private IoParams( int index, long positionUs, - SampleHolder sample, + DecoderInputBuffer sample, ConditionVariable conditionVariable, - ConcurrentLinkedQueue<SampleHolder> readSampleBuffer) { + ConcurrentLinkedQueue<DecoderInputBuffer> readSampleBuffer) { this.index = index; this.positionUs = positionUs; this.sample = sample; @@ -121,12 +123,12 @@ public class SampleChunkIoHelper implements Handler.Callback { * generated class. */ public interface Factory { - public SampleChunkIoHelper create( + SampleChunkIoHelper create( List<String> ids, - List<MediaFormat> mediaFormats, + List<Format> formats, @BufferReason int bufferReason, BufferManager bufferManager, - SamplePool samplePool, + InputBufferPool inputBufferPool, IoCallback ioCallback); } @@ -134,26 +136,26 @@ public class SampleChunkIoHelper implements Handler.Callback { * Creates {@link SampleChunk} I/O handler. * * @param ids track names - * @param mediaFormats {@link android.media.MediaFormat} for each track + * @param formats {@link Format} for each track * @param bufferReason reason to be buffered * @param bufferManager manager of {@link SampleChunk} collections - * @param samplePool allocator for a sample + * @param inputBufferPool allocator for a sample * @param ioCallback listeners for I/O events */ @AutoFactory(implementing = Factory.class) public SampleChunkIoHelper( List<String> ids, - List<MediaFormat> mediaFormats, + List<Format> formats, @BufferReason int bufferReason, BufferManager bufferManager, - SamplePool samplePool, + InputBufferPool inputBufferPool, IoCallback ioCallback) { mTrackCount = ids.size(); mIds = ids; - mMediaFormats = mediaFormats; + mFormats = formats; mBufferReason = bufferReason; mBufferManager = bufferManager; - mSamplePool = samplePool; + mInputBufferPool = inputBufferPool; mIoCallback = ioCallback; mReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount]; @@ -182,7 +184,7 @@ public class SampleChunkIoHelper implements Handler.Callback { /** * Prepares and initializes for I/O operations. * - * @throws IOException + * @throws IOException if an I/O error occurs. */ public void init() throws IOException { HandlerThread handlerThread = new HandlerThread(TAG); @@ -190,7 +192,7 @@ public class SampleChunkIoHelper implements Handler.Callback { mIoHandler = new Handler(handlerThread.getLooper(), this); if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK) { for (int i = 0; i < mTrackCount; ++i) { - mBufferManager.loadTrackFromStorage(mIds.get(i), mSamplePool); + mBufferManager.loadTrackFromStorage(mIds.get(i), mInputBufferPool); } mWriteEnded = true; } else { @@ -205,22 +207,23 @@ public class SampleChunkIoHelper implements Handler.Callback { List<BufferManager.TrackFormat> audios = new ArrayList<>(mTrackCount); List<BufferManager.TrackFormat> videos = new ArrayList<>(mTrackCount); for (int i = 0; i < mTrackCount; ++i) { - android.media.MediaFormat format = - mMediaFormats.get(i).getFrameworkMediaFormatV16(); - format.setLong(android.media.MediaFormat.KEY_DURATION, mBufferDurationUs); - if (mMediaFormats.get(i).pixelWidthHeightRatio > 0) { - // MediaFormats doesn't store aspect ratio so updating the width - // to maintain aspect ratio. - format.setInteger( - android.media.MediaFormat.KEY_WIDTH, - (int) - (mMediaFormats.get(i).width - * mMediaFormats.get(i).pixelWidthHeightRatio)); - } - if (MimeTypes.isAudio(mMediaFormats.get(i).mimeType)) { - audios.add(new BufferManager.TrackFormat(mIds.get(i), format)); - } else if (MimeTypes.isVideo(mMediaFormats.get(i).mimeType)) { - videos.add(new BufferManager.TrackFormat(mIds.get(i), format)); + if (MimeTypes.isAudio(mFormats.get(i).sampleMimeType)) { + MediaFormat mediaFormat = getAudioMediaFormat(mFormats.get(i)); + mediaFormat.setLong(MediaFormat.KEY_DURATION, mBufferDurationUs); + audios.add(new BufferManager.TrackFormat(mIds.get(i), mediaFormat)); + } else if (MimeTypes.isVideo(mFormats.get(i).sampleMimeType)) { + MediaFormat mediaFormat = getVideoMediaFormat(mFormats.get(i)); + mediaFormat.setLong(MediaFormat.KEY_DURATION, mBufferDurationUs); + if (mFormats.get(i).pixelWidthHeightRatio != Format.NO_VALUE) { + // MediaFormats doesn't store aspect ratio so updating the width + // to maintain aspect ratio. + mediaFormat.setInteger( + MediaFormat.KEY_WIDTH, + (int) + (mFormats.get(i).width + * mFormats.get(i).pixelWidthHeightRatio)); + } + videos.add(new BufferManager.TrackFormat(mIds.get(i), mediaFormat)); } } mBufferManager.writeMetaFilesOnly(audios, videos); @@ -230,14 +233,50 @@ public class SampleChunkIoHelper implements Handler.Callback { } } + private MediaFormat getVideoMediaFormat(Format format) { + MediaFormat mediaFormat = new MediaFormat(); + // Set mediaFormat parameters that should always be set. + mediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType); + mediaFormat.setInteger(MediaFormat.KEY_WIDTH, format.width); + mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, format.height); + MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData); + // Set mediaFormat parameters that may be unset. + MediaFormatUtil.maybeSetFloat(mediaFormat, MediaFormat.KEY_FRAME_RATE, format.frameRate); + MediaFormatUtil.maybeSetInteger( + mediaFormat, MediaFormat.KEY_ROTATION, format.rotationDegrees); + MediaFormatUtil.maybeSetColorInfo(mediaFormat, format.colorInfo); + MediaFormatUtil.maybeSetInteger( + mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize); + if (Util.SDK_INT >= 23) { + mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */); + } + return mediaFormat; + } + + private MediaFormat getAudioMediaFormat(Format format) { + MediaFormat mediaFormat = new MediaFormat(); + // Set mediaFormat parameters that should always be set. + mediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType); + mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, format.channelCount); + mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, format.sampleRate); + // Set mediaFormat parameters that may be unset. + MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData); + MediaFormatUtil.maybeSetInteger( + mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize); + if (Util.SDK_INT >= 23) { + mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */); + } + return mediaFormat; + } + /** * Reads a sample if it is available. * * @param index track index * @return {@code null} if a sample is not available, otherwise returns a sample */ - public SampleHolder readSample(int index) { - SampleHolder sample = mReadSampleBuffers[index].poll(); + public DecoderInputBuffer readSample(int index) { + DecoderInputBuffer sample = mReadSampleBuffers[index].poll(); mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_READ, index)); return sample; } @@ -248,10 +287,12 @@ public class SampleChunkIoHelper implements Handler.Callback { * @param index track index * @param sample to write * @param conditionVariable which will be wait until the write is finished - * @throws IOException + * @throws IOException if an I/O error occurs. */ - public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) - throws IOException { + public void writeSample( + int index, + DecoderInputBuffer sample, + ConditionVariable conditionVariable) throws IOException { if (mErrorNotified) { throw new IOException("Storage I/O error happened"); } @@ -302,7 +343,7 @@ public class SampleChunkIoHelper implements Handler.Callback { /** * Finishes I/O operations and releases all the resources. * - * @throws IOException + * @throws IOException if an I/O error occurs. */ public void release() throws IOException { if (mIoHandler == null) { @@ -322,22 +363,23 @@ public class SampleChunkIoHelper implements Handler.Callback { List<BufferManager.TrackFormat> audios = new LinkedList<>(); List<BufferManager.TrackFormat> videos = new LinkedList<>(); for (int i = 0; i < mTrackCount; ++i) { - android.media.MediaFormat format = - mMediaFormats.get(i).getFrameworkMediaFormatV16(); - format.setLong(android.media.MediaFormat.KEY_DURATION, mBufferDurationUs); - if (mMediaFormats.get(i).pixelWidthHeightRatio > 0) { - // MediaFormats doesn't store aspect ratio so updating the width - // to maintain aspect ratio. - format.setInteger( - android.media.MediaFormat.KEY_WIDTH, - (int) - (mMediaFormats.get(i).width - * mMediaFormats.get(i).pixelWidthHeightRatio)); - } - if (MimeTypes.isAudio(mMediaFormats.get(i).mimeType)) { - audios.add(new BufferManager.TrackFormat(mIds.get(i), format)); - } else if (MimeTypes.isVideo(mMediaFormats.get(i).mimeType)) { - videos.add(new BufferManager.TrackFormat(mIds.get(i), format)); + if (MimeTypes.isAudio(mFormats.get(i).sampleMimeType)) { + MediaFormat mediaFormat = getAudioMediaFormat(mFormats.get(i)); + mediaFormat.setLong(MediaFormat.KEY_DURATION, mBufferDurationUs); + audios.add(new BufferManager.TrackFormat(mIds.get(i), mediaFormat)); + } else if (MimeTypes.isVideo(mFormats.get(i).sampleMimeType)) { + MediaFormat mediaFormat = getVideoMediaFormat(mFormats.get(i)); + mediaFormat.setLong(MediaFormat.KEY_DURATION, mBufferDurationUs); + if (mFormats.get(i).pixelWidthHeightRatio != Format.NO_VALUE) { + // MediaFormats doesn't store aspect ratio so updating the width + // to maintain aspect ratio. + mediaFormat.setInteger( + MediaFormat.KEY_WIDTH, + (int) + (mFormats.get(i).width + * mFormats.get(i).pixelWidthHeightRatio)); + } + videos.add(new BufferManager.TrackFormat(mIds.get(i), mediaFormat)); } } mBufferManager.writeMetaFiles(audios, videos); @@ -405,9 +447,9 @@ public class SampleChunkIoHelper implements Handler.Callback { mSelectedTracks.add(index); mReadIoStates[index].openRead(readPosition.first, (long) readPosition.second); if (mHandlerReadSampleBuffers[index] != null) { - SampleHolder sample; + DecoderInputBuffer sample; while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) { - mSamplePool.releaseSample(sample); + mInputBufferPool.releaseSample(sample); } } mHandlerReadSampleBuffers[index] = params.readSampleBuffer; @@ -417,21 +459,21 @@ public class SampleChunkIoHelper implements Handler.Callback { private void doOpenWrite(int index) throws IOException { boolean updateIndexFile = (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING) - && (MimeTypes.isVideo(mMediaFormats.get(index).mimeType) - || MimeTypes.isAudio(mMediaFormats.get(index).mimeType)); + && (MimeTypes.isVideo(mFormats.get(index).sampleMimeType) + || MimeTypes.isAudio(mFormats.get(index).sampleMimeType)); SampleChunk chunk = mBufferManager.createNewWriteFileIfNeeded( - mIds.get(index), 0, mSamplePool, null, 0, updateIndexFile); + mIds.get(index), 0, mInputBufferPool, null, 0, updateIndexFile); mWriteIoStates[index].openWrite(chunk); } private void doCloseRead(int index) { mSelectedTracks.remove(index); if (mHandlerReadSampleBuffers[index] != null) { - SampleHolder sample; + DecoderInputBuffer sample; while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) { - mSamplePool.releaseSample(sample); + mInputBufferPool.releaseSample(sample); } } mIoHandler.removeMessages(MSG_READ, index); @@ -454,7 +496,7 @@ public class SampleChunkIoHelper implements Handler.Callback { mIoCallback.onIoReachedEos(); return; } - SampleHolder sample = mReadIoStates[index].read(); + DecoderInputBuffer sample = mReadIoStates[index].read(); if (sample != null) { mHandlerReadSampleBuffers[index].offer(sample); mReadChunkOffset[index] = mReadIoStates[index].getOffset(); @@ -472,11 +514,11 @@ public class SampleChunkIoHelper implements Handler.Callback { } } - public void doUpdateIndex(IoParams params) throws IOException { + private void doUpdateIndex(IoParams params) throws IOException { int index = params.index; mIoHandler.removeMessages(MSG_READ, index); // Update Track from Storage to load new Samples - mBufferManager.loadTrackFromStorage(mIds.get(index), mSamplePool); + mBufferManager.loadTrackFromStorage(mIds.get(index), mInputBufferPool); Pair<SampleChunk, Integer> readPosition = mBufferManager.getReadFile(mIds.get(index), mReadChunkPositionUs[index]); if (readPosition == null) { @@ -500,9 +542,9 @@ public class SampleChunkIoHelper implements Handler.Callback { return; } int index = params.index; - SampleHolder sample = params.sample; + DecoderInputBuffer sample = params.sample; SampleChunk nextChunk = null; - if ((sample.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { + if (sample.isKeyFrame()) { if (sample.timeUs > mBufferDurationUs) { mBufferDurationUs = sample.timeUs; } @@ -514,15 +556,15 @@ public class SampleChunkIoHelper implements Handler.Callback { int currentOffset = (int) mWriteIoStates[params.index].getOffset(); boolean updateIndexFile = (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING) - && (MimeTypes.isVideo(mMediaFormats.get(index).mimeType) + && (MimeTypes.isVideo(mFormats.get(index).sampleMimeType) || MimeTypes.isAudio( - mMediaFormats.get(index).mimeType)); + mFormats.get(index).sampleMimeType)); nextChunk = mBufferManager.createNewWriteFileIfNeeded( mIds.get(index), mWriteIndexEndPositionUs[index], - mSamplePool, + mInputBufferPool, currentChunk, currentOffset, updateIndexFile); diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleQueue.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleQueue.java index 30c0f532..e001849a 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleQueue.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,44 +16,47 @@ package com.android.tv.tuner.exoplayer2.buffer; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; + import java.util.LinkedList; /** A sample queue which reads from the buffer and passes to player pipeline. */ public class SampleQueue { - private final LinkedList<SampleHolder> mQueue = new LinkedList<>(); - private final SamplePool mSamplePool; + private final LinkedList<DecoderInputBuffer> mQueue = new LinkedList<>(); + private final InputBufferPool mInputBufferPool; private Long mLastQueuedPositionUs = null; - public SampleQueue(SamplePool samplePool) { - mSamplePool = samplePool; + public SampleQueue(InputBufferPool inputBufferPool) { + mInputBufferPool = inputBufferPool; } - public void queueSample(SampleHolder sample) { + public void queueSample(DecoderInputBuffer sample) { mQueue.offer(sample); mLastQueuedPositionUs = sample.timeUs; } - public int dequeueSample(SampleHolder sample) { - SampleHolder sampleFromQueue = mQueue.poll(); + public int dequeueSample(DecoderInputBuffer sample) { + DecoderInputBuffer sampleFromQueue = mQueue.poll(); if (sampleFromQueue == null) { - return SampleSource.NOTHING_READ; + return C.RESULT_NOTHING_READ; } - sample.ensureSpaceForWrite(sampleFromQueue.size); - sample.size = sampleFromQueue.size; - sample.flags = sampleFromQueue.flags; + int size = sampleFromQueue.data.position(); + sample.ensureSpaceForWrite(size); + sample.setFlags((sampleFromQueue.isKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0) + | (sampleFromQueue.isDecodeOnly() ? C.BUFFER_FLAG_DECODE_ONLY : 0) + | (sampleFromQueue.isEncrypted() ? C.BUFFER_FLAG_ENCRYPTED : 0)); sample.timeUs = sampleFromQueue.timeUs; - sample.clearData(); - sampleFromQueue.data.position(0).limit(sample.size); + sample.data.clear(); + sampleFromQueue.flip(); sample.data.put(sampleFromQueue.data); - mSamplePool.releaseSample(sampleFromQueue); - return SampleSource.SAMPLE_READ; + mInputBufferPool.releaseSample(sampleFromQueue); + return C.RESULT_BUFFER_READ; } public void clear() { while (!mQueue.isEmpty()) { - mSamplePool.releaseSample(mQueue.poll()); + mInputBufferPool.releaseSample(mQueue.poll()); } mLastQueuedPositionUs = null; } diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/TrickplayStorageManager.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/TrickplayStorageManager.java index ab5d0ddf..b511c6ab 100644 --- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/TrickplayStorageManager.java +++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/TrickplayStorageManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,7 +87,7 @@ public class TrickplayStorageManager implements BufferManager.StorageManager { if (isCancelled()) { return null; } - File files[] = sBufferDir.listFiles(); + File[] files = sBufferDir.listFiles(); if (files == null || files.length == 0) { return null; } |