mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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
This commit is contained in:
parent
f3e9166a4e
commit
6362dfeb98
2 changed files with 145 additions and 43 deletions
|
|
@ -91,6 +91,13 @@ public class DefaultTrackOutputTest extends TestCase {
|
||||||
inputBuffer = null;
|
inputBuffer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testDisableReleasesAllocations() {
|
||||||
|
writeTestData();
|
||||||
|
assertAllocationCount(10);
|
||||||
|
trackOutput.disable();
|
||||||
|
assertAllocationCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
public void testReadWithoutWrite() {
|
public void testReadWithoutWrite() {
|
||||||
assertNoSamplesToRead(null);
|
assertNoSamplesToRead(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor;
|
package com.google.android.exoplayer2.extractor;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.FormatHolder;
|
import com.google.android.exoplayer2.FormatHolder;
|
||||||
|
|
@ -26,7 +27,6 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -57,15 +57,16 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||||
|
|
||||||
private final Allocator allocator;
|
private final Allocator allocator;
|
||||||
private final int allocationLength;
|
private final int allocationLength;
|
||||||
|
|
||||||
private final SampleMetadataQueue metadataQueue;
|
private final SampleMetadataQueue metadataQueue;
|
||||||
private final LinkedBlockingDeque<Allocation> dataQueue;
|
|
||||||
private final SampleExtrasHolder extrasHolder;
|
private final SampleExtrasHolder extrasHolder;
|
||||||
private final ParsableByteArray scratch;
|
private final ParsableByteArray scratch;
|
||||||
private final AtomicInteger state;
|
private final AtomicInteger state;
|
||||||
|
|
||||||
|
// References into the linked list of allocations.
|
||||||
|
private AllocationNode firstAllocationNode;
|
||||||
|
private AllocationNode writeAllocationNode;
|
||||||
|
|
||||||
// Accessed only by the consuming thread.
|
// Accessed only by the consuming thread.
|
||||||
private long totalBytesDropped;
|
|
||||||
private Format downstreamFormat;
|
private Format downstreamFormat;
|
||||||
|
|
||||||
// Accessed only by the loading thread (or the consuming thread when there is no loading thread).
|
// 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 Format lastUnadjustedFormat;
|
||||||
private long sampleOffsetUs;
|
private long sampleOffsetUs;
|
||||||
private long totalBytesWritten;
|
private long totalBytesWritten;
|
||||||
private Allocation lastAllocation;
|
|
||||||
private int lastAllocationOffset;
|
private int lastAllocationOffset;
|
||||||
private boolean pendingSplice;
|
private boolean pendingSplice;
|
||||||
private UpstreamFormatChangedListener upstreamFormatChangeListener;
|
private UpstreamFormatChangedListener upstreamFormatChangeListener;
|
||||||
|
|
@ -85,11 +85,12 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||||
this.allocator = allocator;
|
this.allocator = allocator;
|
||||||
allocationLength = allocator.getIndividualAllocationLength();
|
allocationLength = allocator.getIndividualAllocationLength();
|
||||||
metadataQueue = new SampleMetadataQueue();
|
metadataQueue = new SampleMetadataQueue();
|
||||||
dataQueue = new LinkedBlockingDeque<>();
|
|
||||||
extrasHolder = new SampleExtrasHolder();
|
extrasHolder = new SampleExtrasHolder();
|
||||||
scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE);
|
scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE);
|
||||||
state = new AtomicInteger();
|
state = new AtomicInteger();
|
||||||
lastAllocationOffset = allocationLength;
|
lastAllocationOffset = allocationLength;
|
||||||
|
firstAllocationNode = new AllocationNode(0, allocationLength);
|
||||||
|
writeAllocationNode = firstAllocationNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by the consuming thread, but only when there is no loading thread.
|
// 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.
|
* @param absolutePosition The absolute position (inclusive) from which to discard data.
|
||||||
*/
|
*/
|
||||||
private void dropUpstreamFrom(long absolutePosition) {
|
private void dropUpstreamFrom(long absolutePosition) {
|
||||||
int relativePosition = (int) (absolutePosition - totalBytesDropped);
|
if (absolutePosition == firstAllocationNode.startPosition) {
|
||||||
// Calculate the index of the allocation containing the position, and the offset within it.
|
clearAllocationNodes(firstAllocationNode);
|
||||||
int allocationIndex = relativePosition / allocationLength;
|
firstAllocationNode = new AllocationNode(absolutePosition, allocationLength);
|
||||||
int allocationOffset = relativePosition % allocationLength;
|
writeAllocationNode = firstAllocationNode;
|
||||||
// We want to discard any allocations after the one at allocationIdnex.
|
} else {
|
||||||
int allocationDiscardCount = dataQueue.size() - allocationIndex - 1;
|
AllocationNode newWriteAllocationNode = firstAllocationNode;
|
||||||
if (allocationOffset == 0) {
|
AllocationNode currentNode = firstAllocationNode.next;
|
||||||
// If the allocation at allocationIndex is empty, we should discard that one too.
|
while (absolutePosition > currentNode.startPosition) {
|
||||||
allocationDiscardCount++;
|
newWriteAllocationNode = currentNode;
|
||||||
|
currentNode = currentNode.next;
|
||||||
}
|
}
|
||||||
// Discard the allocations.
|
clearAllocationNodes(currentNode);
|
||||||
for (int i = 0; i < allocationDiscardCount; i++) {
|
writeAllocationNode = newWriteAllocationNode;
|
||||||
allocator.release(dataQueue.removeLast());
|
writeAllocationNode.next = new AllocationNode(writeAllocationNode.endPosition,
|
||||||
|
allocationLength);
|
||||||
|
lastAllocationOffset = (int) (absolutePosition - writeAllocationNode.startPosition);
|
||||||
}
|
}
|
||||||
// Update lastAllocation and lastAllocationOffset to reflect the new position.
|
|
||||||
lastAllocation = dataQueue.peekLast();
|
|
||||||
lastAllocationOffset = allocationOffset == 0 ? allocationLength : allocationOffset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by the consuming thread.
|
// Called by the consuming thread.
|
||||||
|
|
@ -384,14 +385,18 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||||
*/
|
*/
|
||||||
private void readData(long absolutePosition, ByteBuffer target, int length) {
|
private void readData(long absolutePosition, ByteBuffer target, int length) {
|
||||||
int remaining = length;
|
int remaining = length;
|
||||||
while (remaining > 0) {
|
|
||||||
dropDownstreamTo(absolutePosition);
|
dropDownstreamTo(absolutePosition);
|
||||||
int positionInAllocation = (int) (absolutePosition - totalBytesDropped);
|
while (remaining > 0) {
|
||||||
|
int positionInAllocation = (int) (absolutePosition - firstAllocationNode.startPosition);
|
||||||
int toCopy = Math.min(remaining, allocationLength - positionInAllocation);
|
int toCopy = Math.min(remaining, allocationLength - positionInAllocation);
|
||||||
Allocation allocation = dataQueue.peek();
|
Allocation allocation = firstAllocationNode.allocation;
|
||||||
target.put(allocation.data, allocation.translateOffset(positionInAllocation), toCopy);
|
target.put(allocation.data, allocation.translateOffset(positionInAllocation), toCopy);
|
||||||
absolutePosition += toCopy;
|
absolutePosition += toCopy;
|
||||||
remaining -= 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) {
|
private void readData(long absolutePosition, byte[] target, int length) {
|
||||||
int bytesRead = 0;
|
int bytesRead = 0;
|
||||||
while (bytesRead < length) {
|
|
||||||
dropDownstreamTo(absolutePosition);
|
dropDownstreamTo(absolutePosition);
|
||||||
int positionInAllocation = (int) (absolutePosition - totalBytesDropped);
|
while (bytesRead < length) {
|
||||||
|
int positionInAllocation = (int) (absolutePosition - firstAllocationNode.startPosition);
|
||||||
int toCopy = Math.min(length - bytesRead, allocationLength - positionInAllocation);
|
int toCopy = Math.min(length - bytesRead, allocationLength - positionInAllocation);
|
||||||
Allocation allocation = dataQueue.peek();
|
Allocation allocation = firstAllocationNode.allocation;
|
||||||
System.arraycopy(allocation.data, allocation.translateOffset(positionInAllocation), target,
|
System.arraycopy(allocation.data, allocation.translateOffset(positionInAllocation), target,
|
||||||
bytesRead, toCopy);
|
bytesRead, toCopy);
|
||||||
absolutePosition += toCopy;
|
absolutePosition += toCopy;
|
||||||
bytesRead += 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.
|
* @param absolutePosition The absolute position up to which allocations can be discarded.
|
||||||
*/
|
*/
|
||||||
private void dropDownstreamTo(long absolutePosition) {
|
private void dropDownstreamTo(long absolutePosition) {
|
||||||
int relativePosition = (int) (absolutePosition - totalBytesDropped);
|
while (absolutePosition >= firstAllocationNode.endPosition) {
|
||||||
int allocationIndex = relativePosition / allocationLength;
|
allocator.release(firstAllocationNode.allocation);
|
||||||
for (int i = 0; i < allocationIndex; i++) {
|
firstAllocationNode = firstAllocationNode.clear();
|
||||||
allocator.release(dataQueue.remove());
|
|
||||||
totalBytesDropped += allocationLength;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -481,8 +488,9 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
length = prepareForAppend(length);
|
length = prepareForAppend(length);
|
||||||
int bytesAppended = input.read(lastAllocation.data,
|
Allocation writeAllocation = writeAllocationNode.allocation;
|
||||||
lastAllocation.translateOffset(lastAllocationOffset), length);
|
int bytesAppended = input.read(writeAllocation.data,
|
||||||
|
writeAllocation.translateOffset(lastAllocationOffset), length);
|
||||||
if (bytesAppended == C.RESULT_END_OF_INPUT) {
|
if (bytesAppended == C.RESULT_END_OF_INPUT) {
|
||||||
if (allowEndOfInput) {
|
if (allowEndOfInput) {
|
||||||
return C.RESULT_END_OF_INPUT;
|
return C.RESULT_END_OF_INPUT;
|
||||||
|
|
@ -505,7 +513,8 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||||
}
|
}
|
||||||
while (length > 0) {
|
while (length > 0) {
|
||||||
int thisAppendLength = prepareForAppend(length);
|
int thisAppendLength = prepareForAppend(length);
|
||||||
buffer.readBytes(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset),
|
Allocation writeAllocation = writeAllocationNode.allocation;
|
||||||
|
buffer.readBytes(writeAllocation.data, writeAllocation.translateOffset(lastAllocationOffset),
|
||||||
thisAppendLength);
|
thisAppendLength);
|
||||||
lastAllocationOffset += thisAppendLength;
|
lastAllocationOffset += thisAppendLength;
|
||||||
totalBytesWritten += thisAppendLength;
|
totalBytesWritten += thisAppendLength;
|
||||||
|
|
@ -553,13 +562,35 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||||
|
|
||||||
private void clearSampleData() {
|
private void clearSampleData() {
|
||||||
metadataQueue.clearSampleData();
|
metadataQueue.clearSampleData();
|
||||||
allocator.release(dataQueue.toArray(new Allocation[dataQueue.size()]));
|
clearAllocationNodes(firstAllocationNode);
|
||||||
dataQueue.clear();
|
firstAllocationNode = new AllocationNode(0, allocationLength);
|
||||||
allocator.trim();
|
writeAllocationNode = firstAllocationNode;
|
||||||
totalBytesDropped = 0;
|
|
||||||
totalBytesWritten = 0;
|
totalBytesWritten = 0;
|
||||||
lastAllocation = null;
|
|
||||||
lastAllocationOffset = allocationLength;
|
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) {
|
private int prepareForAppend(int length) {
|
||||||
if (lastAllocationOffset == allocationLength) {
|
if (lastAllocationOffset == allocationLength) {
|
||||||
lastAllocationOffset = 0;
|
lastAllocationOffset = 0;
|
||||||
lastAllocation = allocator.allocate();
|
if (writeAllocationNode.wasInitialized) {
|
||||||
dataQueue.add(lastAllocation);
|
writeAllocationNode = writeAllocationNode.next;
|
||||||
|
}
|
||||||
|
writeAllocationNode.initialize(allocator.allocate(),
|
||||||
|
new AllocationNode(writeAllocationNode.endPosition, allocationLength));
|
||||||
}
|
}
|
||||||
return Math.min(length, allocationLength - lastAllocationOffset);
|
return Math.min(length, allocationLength - lastAllocationOffset);
|
||||||
}
|
}
|
||||||
|
|
@ -592,4 +626,65 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||||
return format;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue