From 6362dfeb986b9f672c745349f674feba96c8ac12 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 13 Jun 2017 06:43:20 -0700 Subject: [PATCH] Replace LinkedBlockingDeque with our own linked list This will allow us to maintain a reference to the middle of the queue, which is necessary to efficiently support decoupling the read position from the start of the buffer. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=158839336 --- .../extractor/DefaultTrackOutputTest.java | 7 + .../extractor/DefaultTrackOutput.java | 181 +++++++++++++----- 2 files changed, 145 insertions(+), 43 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultTrackOutputTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultTrackOutputTest.java index bffba73070..9c3c22ed87 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultTrackOutputTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultTrackOutputTest.java @@ -91,6 +91,13 @@ public class DefaultTrackOutputTest extends TestCase { inputBuffer = null; } + public void testDisableReleasesAllocations() { + writeTestData(); + assertAllocationCount(10); + trackOutput.disable(); + assertAllocationCount(0); + } + public void testReadWithoutWrite() { assertNoSamplesToRead(null); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java index d627f3fe3c..c768b06277 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; @@ -26,7 +27,6 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.EOFException; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicInteger; /** @@ -57,15 +57,16 @@ public final class DefaultTrackOutput implements TrackOutput { private final Allocator allocator; private final int allocationLength; - private final SampleMetadataQueue metadataQueue; - private final LinkedBlockingDeque dataQueue; private final SampleExtrasHolder extrasHolder; private final ParsableByteArray scratch; private final AtomicInteger state; + // References into the linked list of allocations. + private AllocationNode firstAllocationNode; + private AllocationNode writeAllocationNode; + // Accessed only by the consuming thread. - private long totalBytesDropped; private Format downstreamFormat; // Accessed only by the loading thread (or the consuming thread when there is no loading thread). @@ -73,7 +74,6 @@ public final class DefaultTrackOutput implements TrackOutput { private Format lastUnadjustedFormat; private long sampleOffsetUs; private long totalBytesWritten; - private Allocation lastAllocation; private int lastAllocationOffset; private boolean pendingSplice; private UpstreamFormatChangedListener upstreamFormatChangeListener; @@ -85,11 +85,12 @@ public final class DefaultTrackOutput implements TrackOutput { this.allocator = allocator; allocationLength = allocator.getIndividualAllocationLength(); metadataQueue = new SampleMetadataQueue(); - dataQueue = new LinkedBlockingDeque<>(); extrasHolder = new SampleExtrasHolder(); scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE); state = new AtomicInteger(); lastAllocationOffset = allocationLength; + firstAllocationNode = new AllocationNode(0, allocationLength); + writeAllocationNode = firstAllocationNode; } // Called by the consuming thread, but only when there is no loading thread. @@ -149,23 +150,23 @@ public final class DefaultTrackOutput implements TrackOutput { * @param absolutePosition The absolute position (inclusive) from which to discard data. */ private void dropUpstreamFrom(long absolutePosition) { - int relativePosition = (int) (absolutePosition - totalBytesDropped); - // Calculate the index of the allocation containing the position, and the offset within it. - int allocationIndex = relativePosition / allocationLength; - int allocationOffset = relativePosition % allocationLength; - // We want to discard any allocations after the one at allocationIdnex. - int allocationDiscardCount = dataQueue.size() - allocationIndex - 1; - if (allocationOffset == 0) { - // If the allocation at allocationIndex is empty, we should discard that one too. - allocationDiscardCount++; + if (absolutePosition == firstAllocationNode.startPosition) { + clearAllocationNodes(firstAllocationNode); + firstAllocationNode = new AllocationNode(absolutePosition, allocationLength); + writeAllocationNode = firstAllocationNode; + } else { + AllocationNode newWriteAllocationNode = firstAllocationNode; + AllocationNode currentNode = firstAllocationNode.next; + while (absolutePosition > currentNode.startPosition) { + newWriteAllocationNode = currentNode; + currentNode = currentNode.next; + } + clearAllocationNodes(currentNode); + writeAllocationNode = newWriteAllocationNode; + writeAllocationNode.next = new AllocationNode(writeAllocationNode.endPosition, + allocationLength); + lastAllocationOffset = (int) (absolutePosition - writeAllocationNode.startPosition); } - // Discard the allocations. - for (int i = 0; i < allocationDiscardCount; i++) { - allocator.release(dataQueue.removeLast()); - } - // Update lastAllocation and lastAllocationOffset to reflect the new position. - lastAllocation = dataQueue.peekLast(); - lastAllocationOffset = allocationOffset == 0 ? allocationLength : allocationOffset; } // Called by the consuming thread. @@ -384,14 +385,18 @@ public final class DefaultTrackOutput implements TrackOutput { */ private void readData(long absolutePosition, ByteBuffer target, int length) { int remaining = length; + dropDownstreamTo(absolutePosition); while (remaining > 0) { - dropDownstreamTo(absolutePosition); - int positionInAllocation = (int) (absolutePosition - totalBytesDropped); + int positionInAllocation = (int) (absolutePosition - firstAllocationNode.startPosition); int toCopy = Math.min(remaining, allocationLength - positionInAllocation); - Allocation allocation = dataQueue.peek(); + Allocation allocation = firstAllocationNode.allocation; target.put(allocation.data, allocation.translateOffset(positionInAllocation), toCopy); absolutePosition += toCopy; remaining -= toCopy; + if (absolutePosition == firstAllocationNode.endPosition) { + allocator.release(allocation); + firstAllocationNode = firstAllocationNode.clear(); + } } } @@ -404,15 +409,19 @@ public final class DefaultTrackOutput implements TrackOutput { */ private void readData(long absolutePosition, byte[] target, int length) { int bytesRead = 0; + dropDownstreamTo(absolutePosition); while (bytesRead < length) { - dropDownstreamTo(absolutePosition); - int positionInAllocation = (int) (absolutePosition - totalBytesDropped); + int positionInAllocation = (int) (absolutePosition - firstAllocationNode.startPosition); int toCopy = Math.min(length - bytesRead, allocationLength - positionInAllocation); - Allocation allocation = dataQueue.peek(); + Allocation allocation = firstAllocationNode.allocation; System.arraycopy(allocation.data, allocation.translateOffset(positionInAllocation), target, bytesRead, toCopy); absolutePosition += toCopy; bytesRead += toCopy; + if (absolutePosition == firstAllocationNode.endPosition) { + allocator.release(allocation); + firstAllocationNode = firstAllocationNode.clear(); + } } } @@ -423,11 +432,9 @@ public final class DefaultTrackOutput implements TrackOutput { * @param absolutePosition The absolute position up to which allocations can be discarded. */ private void dropDownstreamTo(long absolutePosition) { - int relativePosition = (int) (absolutePosition - totalBytesDropped); - int allocationIndex = relativePosition / allocationLength; - for (int i = 0; i < allocationIndex; i++) { - allocator.release(dataQueue.remove()); - totalBytesDropped += allocationLength; + while (absolutePosition >= firstAllocationNode.endPosition) { + allocator.release(firstAllocationNode.allocation); + firstAllocationNode = firstAllocationNode.clear(); } } @@ -481,8 +488,9 @@ public final class DefaultTrackOutput implements TrackOutput { } try { length = prepareForAppend(length); - int bytesAppended = input.read(lastAllocation.data, - lastAllocation.translateOffset(lastAllocationOffset), length); + Allocation writeAllocation = writeAllocationNode.allocation; + int bytesAppended = input.read(writeAllocation.data, + writeAllocation.translateOffset(lastAllocationOffset), length); if (bytesAppended == C.RESULT_END_OF_INPUT) { if (allowEndOfInput) { return C.RESULT_END_OF_INPUT; @@ -505,7 +513,8 @@ public final class DefaultTrackOutput implements TrackOutput { } while (length > 0) { int thisAppendLength = prepareForAppend(length); - buffer.readBytes(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset), + Allocation writeAllocation = writeAllocationNode.allocation; + buffer.readBytes(writeAllocation.data, writeAllocation.translateOffset(lastAllocationOffset), thisAppendLength); lastAllocationOffset += thisAppendLength; totalBytesWritten += thisAppendLength; @@ -553,13 +562,35 @@ public final class DefaultTrackOutput implements TrackOutput { private void clearSampleData() { metadataQueue.clearSampleData(); - allocator.release(dataQueue.toArray(new Allocation[dataQueue.size()])); - dataQueue.clear(); - allocator.trim(); - totalBytesDropped = 0; + clearAllocationNodes(firstAllocationNode); + firstAllocationNode = new AllocationNode(0, allocationLength); + writeAllocationNode = firstAllocationNode; totalBytesWritten = 0; - lastAllocation = null; lastAllocationOffset = allocationLength; + allocator.trim(); + } + + /** + * Clears allocation nodes starting from {@code fromNode}. + * + * @param fromNode The node from which to clear. + */ + private void clearAllocationNodes(AllocationNode fromNode) { + if (!fromNode.wasInitialized) { + return; + } + // Bulk release allocations for performance (it's significantly faster when using + // DefaultAllocator because the allocator's lock only needs to be acquired and released once) + // [Internal: See b/29542039]. + int allocationCount = (writeAllocationNode.wasInitialized ? 1 : 0) + + ((int) (writeAllocationNode.startPosition - fromNode.startPosition) / allocationLength); + Allocation[] allocationsToRelease = new Allocation[allocationCount]; + AllocationNode currentNode = fromNode; + for (int i = 0; i < allocationsToRelease.length; i++) { + allocationsToRelease[i] = currentNode.allocation; + currentNode = currentNode.clear(); + } + allocator.release(allocationsToRelease); } /** @@ -569,8 +600,11 @@ public final class DefaultTrackOutput implements TrackOutput { private int prepareForAppend(int length) { if (lastAllocationOffset == allocationLength) { lastAllocationOffset = 0; - lastAllocation = allocator.allocate(); - dataQueue.add(lastAllocation); + if (writeAllocationNode.wasInitialized) { + writeAllocationNode = writeAllocationNode.next; + } + writeAllocationNode.initialize(allocator.allocate(), + new AllocationNode(writeAllocationNode.endPosition, allocationLength)); } return Math.min(length, allocationLength - lastAllocationOffset); } @@ -592,4 +626,65 @@ public final class DefaultTrackOutput implements TrackOutput { return format; } + /** + * A node in a linked list of {@link Allocation}s held by the output. + */ + private static final class AllocationNode { + + /** + * The absolute position of the start of the data (inclusive). + */ + public final long startPosition; + /** + * The absolute position of the end of the data (exclusive). + */ + public final long endPosition; + /** + * Whether the node has been initialized. Remains true after {@link #clear()}. + */ + public boolean wasInitialized; + /** + * The {@link Allocation}, or {@code null} if the node is not initialized. + */ + @Nullable public Allocation allocation; + /** + * The next {@link AllocationNode} in the list, or {@code null} if the node has not been + * initialized. Remains set after {@link #clear()}. + */ + @Nullable public AllocationNode next; + + /** + * @param startPosition See {@link #startPosition}. + * @param allocationLength The length of the {@link Allocation} with which this node will be + * initialized. + */ + public AllocationNode(long startPosition, int allocationLength) { + this.startPosition = startPosition; + this.endPosition = startPosition + allocationLength; + } + + /** + * Initializes the node. + * + * @param allocation The node's {@link Allocation}. + * @param next The next {@link AllocationNode}. + */ + public void initialize(Allocation allocation, AllocationNode next) { + this.allocation = allocation; + this.next = next; + wasInitialized = true; + } + + /** + * Clears {@link #allocation}. + * + * @return The next {@link AllocationNode}, for convenience. + */ + public AllocationNode clear() { + allocation = null; + return next; + } + + } + }