Commit c3788c09 by Oliver Woodman

Eliminate memory copy of H264 data through H264 reader.

I think this is the limit of how far we should be pushing complexity
v.s. efficiency. It's a little complicated to understand, but probably
worth it since the H264 bitstream is the majority of the data.

Issue: #278
parent 37e6946c
...@@ -85,7 +85,7 @@ import java.util.Collections; ...@@ -85,7 +85,7 @@ import java.util.Collections;
break; break;
case STATE_READING_SAMPLE: case STATE_READING_SAMPLE:
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
appendSampleData(data, bytesToRead); appendData(data, bytesToRead);
bytesRead += bytesToRead; bytesRead += bytesToRead;
if (bytesRead == sampleSize) { if (bytesRead == sampleSize) {
commitSample(true); commitSample(true);
......
...@@ -35,7 +35,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -35,7 +35,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
startSample(pesTimeUs); startSample(pesTimeUs);
} }
if (writingSample()) { if (writingSample()) {
appendSampleData(data, data.bytesLeft()); appendData(data, data.bytesLeft());
} }
} }
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer.hls.parser; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
...@@ -44,7 +45,6 @@ import java.util.concurrent.ConcurrentLinkedQueue; ...@@ -44,7 +45,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
private long totalBytesWritten; private long totalBytesWritten;
private byte[] lastFragment; private byte[] lastFragment;
private int lastFragmentOffset; private int lastFragmentOffset;
private int pendingSampleSize;
private long pendingSampleTimeUs; private long pendingSampleTimeUs;
private long pendingSampleOffset; private long pendingSampleOffset;
...@@ -141,23 +141,25 @@ import java.util.concurrent.ConcurrentLinkedQueue; ...@@ -141,23 +141,25 @@ import java.util.concurrent.ConcurrentLinkedQueue;
// Called by the loading thread. // Called by the loading thread.
/** /**
* Starts writing the next sample. * Indicates the start point for the next sample.
* *
* @param sampleTimeUs The sample timestamp. * @param sampleTimeUs The sample timestamp.
* @param offset The offset of the sample's data, relative to the total number of bytes written
* to the buffer. Must be negative or zero.
*/ */
public void startSample(long sampleTimeUs) { public void startSample(long sampleTimeUs, int offset) {
Assertions.checkState(offset <= 0);
pendingSampleTimeUs = sampleTimeUs; pendingSampleTimeUs = sampleTimeUs;
pendingSampleOffset = totalBytesWritten; pendingSampleOffset = totalBytesWritten + offset;
pendingSampleSize = 0;
} }
/** /**
* Appends data to the sample currently being written. * Appends data to the rolling buffer.
* *
* @param buffer A buffer containing the data to append. * @param buffer A buffer containing the data to append.
* @param length The length of the data to append. * @param length The length of the data to append.
*/ */
public void appendSampleData(ParsableByteArray buffer, int length) { public void appendData(ParsableByteArray buffer, int length) {
int remainingWriteLength = length; int remainingWriteLength = length;
while (remainingWriteLength > 0) { while (remainingWriteLength > 0) {
if (dataQueue.isEmpty() || lastFragmentOffset == fragmentLength) { if (dataQueue.isEmpty() || lastFragmentOffset == fragmentLength) {
...@@ -171,17 +173,20 @@ import java.util.concurrent.ConcurrentLinkedQueue; ...@@ -171,17 +173,20 @@ import java.util.concurrent.ConcurrentLinkedQueue;
remainingWriteLength -= thisWriteLength; remainingWriteLength -= thisWriteLength;
} }
totalBytesWritten += length; totalBytesWritten += length;
pendingSampleSize += length;
} }
/** /**
* Commits the sample currently being written, making it available for consumption. * Indicates the end point for the current sample, making it available for consumption.
* *
* @param isKeyframe True if the sample being committed is a keyframe. False otherwise. * @param isKeyframe True if the sample being committed is a keyframe. False otherwise.
* @param offset The offset of the first byte after the end of the sample's data, relative to
* the total number of bytes written to the buffer. Must be negative or zero.
*/ */
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
public void commitSample(boolean isKeyframe) { public void commitSample(boolean isKeyframe, int offset) {
infoQueue.commitSample(pendingSampleTimeUs, pendingSampleOffset, pendingSampleSize, Assertions.checkState(offset <= 0);
int sampleSize = (int) (totalBytesWritten + offset - pendingSampleOffset);
infoQueue.commitSample(pendingSampleTimeUs, pendingSampleOffset, sampleSize,
isKeyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0); isKeyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0);
} }
......
...@@ -181,17 +181,25 @@ import android.media.MediaExtractor; ...@@ -181,17 +181,25 @@ import android.media.MediaExtractor;
} }
protected void startSample(long sampleTimeUs) { protected void startSample(long sampleTimeUs) {
startSample(sampleTimeUs, 0);
}
protected void startSample(long sampleTimeUs, int offset) {
writingSample = true; writingSample = true;
largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sampleTimeUs); largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sampleTimeUs);
rollingBuffer.startSample(sampleTimeUs); rollingBuffer.startSample(sampleTimeUs, offset);
} }
protected void appendSampleData(ParsableByteArray buffer, int size) { protected void appendData(ParsableByteArray buffer, int length) {
rollingBuffer.appendSampleData(buffer, size); rollingBuffer.appendData(buffer, length);
} }
protected void commitSample(boolean isKeyframe) { protected void commitSample(boolean isKeyframe) {
rollingBuffer.commitSample(isKeyframe); commitSample(isKeyframe, 0);
}
protected void commitSample(boolean isKeyframe, int offset) {
rollingBuffer.commitSample(isKeyframe, offset);
writingSample = false; writingSample = false;
} }
......
...@@ -42,7 +42,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -42,7 +42,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
int ccDataSize = Eia608Parser.parseHeader(seiBuffer); int ccDataSize = Eia608Parser.parseHeader(seiBuffer);
if (ccDataSize > 0) { if (ccDataSize > 0) {
startSample(pesTimeUs); startSample(pesTimeUs);
appendSampleData(seiBuffer, ccDataSize); appendData(seiBuffer, ccDataSize);
commitSample(true); commitSample(true);
} }
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.mp4; package com.google.android.exoplayer.mp4;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.CodecSpecificDataUtil; import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
...@@ -100,30 +101,66 @@ public final class Mp4Util { ...@@ -100,30 +101,66 @@ public final class Mp4Util {
} }
/** /**
* Like {@link #findNalUnit(byte[], int, int, int)} with {@code type == -1}. * Finds the first NAL unit in {@code data}.
* <p>
* For a NAL unit to be found, its first four bytes must be contained within the part of the
* array being searched.
* *
* @param data The data to search. * @param data The data to search.
* @param startOffset The offset (inclusive) in the data to start the search. * @param startOffset The offset (inclusive) in the data to start the search.
* @param endOffset The offset (exclusive) in the data to end the search. * @param endOffset The offset (exclusive) in the data to end the search.
* @param type The type of the NAL unit to search for, or -1 for any NAL unit.
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found. * @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
*/ */
public static int findNalUnit(byte[] data, int startOffset, int endOffset) { public static int findNalUnit(byte[] data, int startOffset, int endOffset, int type) {
return findNalUnit(data, startOffset, endOffset, -1); return findNalUnit(data, startOffset, endOffset, type, null);
} }
/** /**
* Finds the first NAL unit in {@code data}. * Like {@link #findNalUnit(byte[], int, int, int)}, but supports finding of NAL units across
* array boundaries.
* <p> * <p>
* For a NAL unit to be found, its first four bytes must be contained within the part of the * To use this method, pass the same {@code prefixFlags} parameter to successive calls where the
* array being searched. * data passed represents a contiguous stream. The state maintained in this parameter allows the
* detection of NAL units where the NAL unit prefix spans array boundaries.
* <p>
* Note that when using {@code prefixFlags} the return value may be 3, 2 or 1 less than
* {@code startOffset}, to indicate a NAL unit starting 3, 2 or 1 bytes before the first byte in
* the current array.
* *
* @param data The data to search. * @param data The data to search.
* @param startOffset The offset (inclusive) in the data to start the search. * @param startOffset The offset (inclusive) in the data to start the search.
* @param endOffset The offset (exclusive) in the data to end the search. * @param endOffset The offset (exclusive) in the data to end the search.
* @param type The type of the NAL unit to search for, or -1 for any NAL unit. * @param type The type of the NAL unit to search for, or -1 for any NAL unit.
* @param prefixFlags A boolean array whose first three elements are used to store the state
* required to detect NAL units where the NAL unit prefix spans array boundaries. The array
* must be at least 3 elements long.
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found. * @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
*/ */
public static int findNalUnit(byte[] data, int startOffset, int endOffset, int type) { public static int findNalUnit(byte[] data, int startOffset, int endOffset, int type,
boolean[] prefixFlags) {
int length = endOffset - startOffset;
Assertions.checkState(length >= 0);
if (length == 0) {
return endOffset;
}
if (prefixFlags != null) {
if (prefixFlags[0] && matchesType(data, startOffset, type)) {
clearPrefixFlags(prefixFlags);
return startOffset - 3;
} else if (length > 1 && prefixFlags[1] && data[startOffset] == 1
&& matchesType(data, startOffset + 1, type)) {
clearPrefixFlags(prefixFlags);
return startOffset - 2;
} else if (length > 2 && prefixFlags[2] && data[startOffset] == 0
&& data[startOffset + 1] == 1 && matchesType(data, startOffset + 2, type)) {
clearPrefixFlags(prefixFlags);
return startOffset - 1;
}
}
int limit = endOffset - 2; int limit = endOffset - 2;
// We're looking for the NAL unit start code prefix 0x000001, followed by a byte that matches // We're looking for the NAL unit start code prefix 0x000001, followed by a byte that matches
// the specified type. The value of i tracks the index of the third byte in the four bytes // the specified type. The value of i tracks the index of the third byte in the four bytes
...@@ -132,8 +169,8 @@ public final class Mp4Util { ...@@ -132,8 +169,8 @@ public final class Mp4Util {
if ((data[i] & 0xFE) != 0) { if ((data[i] & 0xFE) != 0) {
// There isn't a NAL prefix here, or at the next two positions. Do nothing and let the // There isn't a NAL prefix here, or at the next two positions. Do nothing and let the
// loop advance the index by three. // loop advance the index by three.
} else if ((data[i - 2] == 0 && data[i - 1] == 0 && data[i] == 1) } else if (data[i - 2] == 0 && data[i - 1] == 0 && data[i] == 1
&& (type == -1 || (type == (data[i + 1] & 0x1F)))) { && matchesType(data, i + 1, type)) {
return i - 2; return i - 2;
} else { } else {
// There isn't a NAL prefix here, but there might be at the next position. We should // There isn't a NAL prefix here, but there might be at the next position. We should
...@@ -141,18 +178,77 @@ public final class Mp4Util { ...@@ -141,18 +178,77 @@ public final class Mp4Util {
i -= 2; i -= 2;
} }
} }
if (prefixFlags != null) {
// True if the last three bytes in the data seen so far are {0,0,1}.
prefixFlags[0] = length > 2
? (data[endOffset - 3] == 0 && data[endOffset - 2] == 0 && data[endOffset - 1] == 1)
: length == 2 ? (prefixFlags[2] && data[endOffset - 2] == 0 && data[endOffset - 1] == 1)
: (prefixFlags[1] && data[endOffset - 1] == 1);
// True if the last three bytes in the data seen so far are {0,0}.
prefixFlags[1] = length > 1 ? data[endOffset - 2] == 0 && data[endOffset - 1] == 0
: prefixFlags[2] && data[endOffset - 1] == 0;
// True if the last three bytes in the data seen so far are {0}.
prefixFlags[2] = data[endOffset - 1] == 0;
}
return endOffset; return endOffset;
} }
/** /**
* Like {@link #findNalUnit(byte[], int, int, int)} with {@code type == -1}.
*
* @param data The data to search.
* @param startOffset The offset (inclusive) in the data to start the search.
* @param endOffset The offset (exclusive) in the data to end the search.
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
*/
public static int findNalUnit(byte[] data, int startOffset, int endOffset) {
return findNalUnit(data, startOffset, endOffset, null);
}
/**
* Like {@link #findNalUnit(byte[], int, int, int, boolean[])} with {@code type == -1}.
*
* @param data The data to search.
* @param startOffset The offset (inclusive) in the data to start the search.
* @param endOffset The offset (exclusive) in the data to end the search.
* @param prefixFlags A boolean array of length at least 3.
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
*/
public static int findNalUnit(byte[] data, int startOffset, int endOffset,
boolean[] prefixFlags) {
return findNalUnit(data, startOffset, endOffset, -1, prefixFlags);
}
/**
* Gets the type of the NAL unit in {@code data} that starts at {@code offset}. * Gets the type of the NAL unit in {@code data} that starts at {@code offset}.
* *
* @param data The data to search. * @param data The data to search.
* @param offset The start offset of a NAL unit. * @param offset The start offset of a NAL unit. Must lie between {@code -3} (inclusive) and
* {@code data.length - 3} (exclusive).
* @return The type of the unit. * @return The type of the unit.
*/ */
public static int getNalUnitType(byte[] data, int offset) { public static int getNalUnitType(byte[] data, int offset) {
return data[offset + 3] & 0x1F; return data[offset + 3] & 0x1F;
} }
/**
* Clears prefix flags, as used by {@link #findNalUnit(byte[], int, int, int, boolean[])}.
*
* @param prefixFlags The flags to clear.
*/
private static void clearPrefixFlags(boolean[] prefixFlags) {
prefixFlags[0] = false;
prefixFlags[1] = false;
prefixFlags[2] = false;
}
/**
* Returns true if the type at {@code offset} is equal to {@code type}, or if {@code type == -1}.
*/
private static boolean matchesType(byte[] data, int offset, int type) {
return type == -1 || (data[offset] & 0x1F) == type;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment