mirror of
https://github.com/samsonjs/media.git
synced 2026-04-08 11:45:51 +00:00
Further H264/H265 code deduping + fix NAL unescaping.
This commit is contained in:
parent
ae466cc59b
commit
24e897d39f
4 changed files with 107 additions and 111 deletions
|
|
@ -66,7 +66,6 @@ import java.util.List;
|
|||
|
||||
// Scratch variables to avoid allocations.
|
||||
private final ParsableByteArray seiWrapper;
|
||||
private int[] scratchEscapePositions;
|
||||
|
||||
public H264Reader(TrackOutput output, SeiReader seiReader, boolean idrKeyframesOnly) {
|
||||
super(output);
|
||||
|
|
@ -77,7 +76,6 @@ import java.util.List;
|
|||
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
|
||||
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);
|
||||
seiWrapper = new ParsableByteArray();
|
||||
scratchEscapePositions = new int[10];
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -191,7 +189,7 @@ import java.util.List;
|
|||
sps.endNalUnit(discardPadding);
|
||||
pps.endNalUnit(discardPadding);
|
||||
if (sei.endNalUnit(discardPadding)) {
|
||||
int unescapedLength = unescapeStream(sei.nalData, sei.nalLength);
|
||||
int unescapedLength = NalUnitUtil.unescapeStream(sei.nalData, sei.nalLength);
|
||||
seiWrapper.reset(sei.nalData, unescapedLength);
|
||||
seiWrapper.setPosition(4); // NAL prefix and nal_unit() header.
|
||||
seiReader.consume(seiWrapper, pesTimeUs, true);
|
||||
|
|
@ -208,7 +206,7 @@ import java.util.List;
|
|||
initializationData.add(ppsData);
|
||||
|
||||
// Unescape and then parse the SPS unit.
|
||||
unescapeStream(sps.nalData, sps.nalLength);
|
||||
NalUnitUtil.unescapeStream(sps.nalData, sps.nalLength);
|
||||
ParsableBitArray bitArray = new ParsableBitArray(sps.nalData);
|
||||
bitArray.skipBits(32); // NAL header
|
||||
int profileIdc = bitArray.readBits(8);
|
||||
|
|
@ -322,57 +320,6 @@ import java.util.List;
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescapes {@code data} up to the specified limit, replacing occurrences of [0, 0, 3] with
|
||||
* [0, 0]. The unescaped data is returned in-place, with the return value indicating its length.
|
||||
* <p>
|
||||
* See ISO/IEC 14496-10:2005(E) page 36 for more information.
|
||||
*
|
||||
* @param data The data to unescape.
|
||||
* @param limit The limit (exclusive) of the data to unescape.
|
||||
* @return The length of the unescaped data.
|
||||
*/
|
||||
private int unescapeStream(byte[] data, int limit) {
|
||||
int position = 0;
|
||||
int scratchEscapeCount = 0;
|
||||
while (position < limit) {
|
||||
position = findNextUnescapeIndex(data, position, limit);
|
||||
if (position < limit) {
|
||||
if (scratchEscapePositions.length <= scratchEscapeCount) {
|
||||
// Grow scratchEscapePositions to hold a larger number of positions.
|
||||
scratchEscapePositions = Arrays.copyOf(scratchEscapePositions,
|
||||
scratchEscapePositions.length * 2);
|
||||
}
|
||||
scratchEscapePositions[scratchEscapeCount++] = position;
|
||||
position += 3;
|
||||
}
|
||||
}
|
||||
|
||||
int unescapedLength = limit - scratchEscapeCount;
|
||||
int escapedPosition = 0; // The position being read from.
|
||||
int unescapedPosition = 0; // The position being written to.
|
||||
for (int i = 0; i < scratchEscapeCount; i++) {
|
||||
int nextEscapePosition = scratchEscapePositions[i];
|
||||
int copyLength = nextEscapePosition - escapedPosition;
|
||||
System.arraycopy(data, escapedPosition, data, unescapedPosition, copyLength);
|
||||
escapedPosition += copyLength + 3;
|
||||
unescapedPosition += copyLength + 2;
|
||||
}
|
||||
|
||||
int remainingLength = unescapedLength - unescapedPosition;
|
||||
System.arraycopy(data, escapedPosition, data, unescapedPosition, remainingLength);
|
||||
return unescapedLength;
|
||||
}
|
||||
|
||||
private static int findNextUnescapeIndex(byte[] bytes, int offset, int limit) {
|
||||
for (int i = offset; i < limit - 2; i++) {
|
||||
if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x03) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* A buffer specifically for IFR units that can be used to parse the IFR's slice type.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
|
|
@ -71,7 +70,6 @@ import java.util.Collections;
|
|||
|
||||
// Scratch variables to avoid allocations.
|
||||
private final ParsableByteArray seiWrapper;
|
||||
private int[] scratchEscapePositions;
|
||||
|
||||
public H265Reader(TrackOutput output, SeiReader seiReader) {
|
||||
super(output);
|
||||
|
|
@ -83,7 +81,6 @@ import java.util.Collections;
|
|||
prefixSei = new NalUnitTargetBuffer(PREFIX_SEI_NUT, 128);
|
||||
suffixSei = new NalUnitTargetBuffer(SUFFIX_SEI_NUT, 128);
|
||||
seiWrapper = new ParsableByteArray();
|
||||
scratchEscapePositions = new int[10];
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -189,7 +186,7 @@ import java.util.Collections;
|
|||
sps.endNalUnit(discardPadding);
|
||||
pps.endNalUnit(discardPadding);
|
||||
if (prefixSei.endNalUnit(discardPadding)) {
|
||||
int unescapedLength = unescapeStream(prefixSei.nalData, prefixSei.nalLength);
|
||||
int unescapedLength = NalUnitUtil.unescapeStream(prefixSei.nalData, prefixSei.nalLength);
|
||||
seiWrapper.reset(prefixSei.nalData, unescapedLength);
|
||||
|
||||
// Skip the NAL prefix and type.
|
||||
|
|
@ -197,7 +194,7 @@ import java.util.Collections;
|
|||
seiReader.consume(seiWrapper, pesTimeUs, true);
|
||||
}
|
||||
if (suffixSei.endNalUnit(discardPadding)) {
|
||||
int unescapedLength = unescapeStream(suffixSei.nalData, suffixSei.nalLength);
|
||||
int unescapedLength = NalUnitUtil.unescapeStream(suffixSei.nalData, suffixSei.nalLength);
|
||||
seiWrapper.reset(suffixSei.nalData, unescapedLength);
|
||||
|
||||
// Skip the NAL prefix and type.
|
||||
|
|
@ -215,7 +212,7 @@ import java.util.Collections;
|
|||
System.arraycopy(pps.nalData, 0, csd, vps.nalLength + sps.nalLength, pps.nalLength);
|
||||
|
||||
// Unescape and then parse the SPS NAL unit, as per H.265/HEVC (2014) 7.3.2.2.1.
|
||||
unescapeStream(sps.nalData, sps.nalLength);
|
||||
NalUnitUtil.unescapeStream(sps.nalData, sps.nalLength);
|
||||
ParsableBitArray bitArray = new ParsableBitArray(sps.nalData);
|
||||
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
|
||||
int maxSubLayersMinus1 = bitArray.readBits(3);
|
||||
|
|
@ -339,56 +336,6 @@ import java.util.Collections;
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Deduplicate with H264Reader.
|
||||
/**
|
||||
* Unescapes {@code data} up to the specified limit, replacing occurrences of [0, 0, 3] with
|
||||
* [0, 0]. The unescaped data is returned in-place, with the return value indicating its length.
|
||||
*
|
||||
* @param data The data to unescape.
|
||||
* @param limit The limit (exclusive) of the data to unescape.
|
||||
* @return The length of the unescaped data.
|
||||
*/
|
||||
private int unescapeStream(byte[] data, int limit) {
|
||||
int position = 0;
|
||||
int scratchEscapeCount = 0;
|
||||
while (position < limit) {
|
||||
position = findNextUnescapeIndex(data, position, limit);
|
||||
if (position < limit) {
|
||||
if (scratchEscapePositions.length <= scratchEscapeCount) {
|
||||
// Grow scratchEscapePositions to hold a larger number of positions.
|
||||
scratchEscapePositions = Arrays.copyOf(scratchEscapePositions,
|
||||
scratchEscapePositions.length * 2);
|
||||
}
|
||||
scratchEscapePositions[scratchEscapeCount++] = position;
|
||||
position += 3;
|
||||
}
|
||||
}
|
||||
|
||||
int unescapedLength = limit - scratchEscapeCount;
|
||||
int escapedPosition = 0; // The position being read from.
|
||||
int unescapedPosition = 0; // The position being written to.
|
||||
for (int i = 0; i < scratchEscapeCount; i++) {
|
||||
int nextEscapePosition = scratchEscapePositions[i];
|
||||
int copyLength = nextEscapePosition - escapedPosition;
|
||||
System.arraycopy(data, escapedPosition, data, unescapedPosition, copyLength);
|
||||
escapedPosition += copyLength + 3;
|
||||
unescapedPosition += copyLength + 2;
|
||||
}
|
||||
|
||||
int remainingLength = unescapedLength - unescapedPosition;
|
||||
System.arraycopy(data, escapedPosition, data, unescapedPosition, remainingLength);
|
||||
return unescapedLength;
|
||||
}
|
||||
|
||||
private static int findNextUnescapeIndex(byte[] bytes, int offset, int limit) {
|
||||
for (int i = offset; i < limit - 2; i++) {
|
||||
if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x03) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
/** Returns whether the NAL unit is a random access point. */
|
||||
private static boolean isRandomAccessPoint(int nalUnitType) {
|
||||
return nalUnitType == BLA_W_LP || nalUnitType == BLA_W_RADL || nalUnitType == BLA_N_LP
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
package com.google.android.exoplayer.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Utility methods for handling H.264/AVC and H.265/HEVC NAL units.
|
||||
|
|
@ -48,6 +49,61 @@ public final class NalUnitUtil {
|
|||
2f
|
||||
};
|
||||
|
||||
private static final Object scratchEscapePositionsLock = new Object();
|
||||
|
||||
/**
|
||||
* Temporary store for positions of escape codes in {@link #unescapeStream(byte[], int)}. Guarded
|
||||
* by {@link #scratchEscapePositionsLock}.
|
||||
*/
|
||||
private static int[] scratchEscapePositions = new int[10];
|
||||
|
||||
/**
|
||||
* Unescapes {@code data} up to the specified limit, replacing occurrences of [0, 0, 3] with
|
||||
* [0, 0]. The unescaped data is returned in-place, with the return value indicating its length.
|
||||
* <p>
|
||||
* Executions of this method are mutually exclusive, so it should not be called with very large
|
||||
* buffers.
|
||||
*
|
||||
* @param data The data to unescape.
|
||||
* @param limit The limit (exclusive) of the data to unescape.
|
||||
* @return The length of the unescaped data.
|
||||
*/
|
||||
public static int unescapeStream(byte[] data, int limit) {
|
||||
synchronized (scratchEscapePositionsLock) {
|
||||
int position = 0;
|
||||
int scratchEscapeCount = 0;
|
||||
while (position < limit) {
|
||||
position = findNextUnescapeIndex(data, position, limit);
|
||||
if (position < limit) {
|
||||
if (scratchEscapePositions.length <= scratchEscapeCount) {
|
||||
// Grow scratchEscapePositions to hold a larger number of positions.
|
||||
scratchEscapePositions = Arrays.copyOf(scratchEscapePositions,
|
||||
scratchEscapePositions.length * 2);
|
||||
}
|
||||
scratchEscapePositions[scratchEscapeCount++] = position;
|
||||
position += 3;
|
||||
}
|
||||
}
|
||||
|
||||
int unescapedLength = limit - scratchEscapeCount;
|
||||
int escapedPosition = 0; // The position being read from.
|
||||
int unescapedPosition = 0; // The position being written to.
|
||||
for (int i = 0; i < scratchEscapeCount; i++) {
|
||||
int nextEscapePosition = scratchEscapePositions[i];
|
||||
int copyLength = nextEscapePosition - escapedPosition;
|
||||
System.arraycopy(data, escapedPosition, data, unescapedPosition, copyLength);
|
||||
unescapedPosition += copyLength;
|
||||
data[unescapedPosition++] = 0;
|
||||
data[unescapedPosition++] = 0;
|
||||
escapedPosition += copyLength + 3;
|
||||
}
|
||||
|
||||
int remainingLength = unescapedLength - unescapedPosition;
|
||||
System.arraycopy(data, escapedPosition, data, unescapedPosition, remainingLength);
|
||||
return unescapedLength;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
|
||||
* {@code size} bytes preceding the buffer's position.
|
||||
|
|
@ -189,6 +245,15 @@ public final class NalUnitUtil {
|
|||
prefixFlags[2] = false;
|
||||
}
|
||||
|
||||
private static int findNextUnescapeIndex(byte[] bytes, int offset, int limit) {
|
||||
for (int i = offset; i < limit - 2; i++) {
|
||||
if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x03) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
|
||||
* assumed that the top bit will always be set to zero.
|
||||
|
|
|
|||
|
|
@ -110,6 +110,18 @@ public class NalUnitUtilTest extends TestCase {
|
|||
assertPrefixFlagsCleared(prefixFlags);
|
||||
}
|
||||
|
||||
public void testUnescapeDoesNotModifyBuffersWithoutStartCodes() {
|
||||
assertUnescapeDoesNotModify("");
|
||||
assertUnescapeDoesNotModify("0000");
|
||||
assertUnescapeDoesNotModify("172BF38A3C");
|
||||
assertUnescapeDoesNotModify("000004");
|
||||
}
|
||||
|
||||
public void testUnescapeModifiesBuffersWithStartCodes() {
|
||||
assertUnescapeMatchesExpected("00000301", "000001");
|
||||
assertUnescapeMatchesExpected("0000030200000300", "000002000000");
|
||||
}
|
||||
|
||||
private static byte[] buildTestData() {
|
||||
byte[] data = new byte[20];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
|
|
@ -130,4 +142,29 @@ public class NalUnitUtilTest extends TestCase {
|
|||
assertEquals(false, flags[0] || flags[1] || flags[2]);
|
||||
}
|
||||
|
||||
private static void assertUnescapeDoesNotModify(String input) {
|
||||
assertUnescapeMatchesExpected(input, input);
|
||||
}
|
||||
|
||||
private static void assertUnescapeMatchesExpected(String input, String expectedOutput) {
|
||||
byte[] bitstream = getByteArrayForHexString(input);
|
||||
byte[] expectedOutputBitstream = getByteArrayForHexString(expectedOutput);
|
||||
int count = NalUnitUtil.unescapeStream(bitstream, bitstream.length);
|
||||
assertEquals(expectedOutputBitstream.length, count);
|
||||
byte[] outputBitstream = new byte[count];
|
||||
System.arraycopy(bitstream, 0, outputBitstream, 0, count);
|
||||
assertTrue(Arrays.equals(expectedOutputBitstream, outputBitstream));
|
||||
}
|
||||
|
||||
private static byte[] getByteArrayForHexString(String hexString) {
|
||||
int length = hexString.length();
|
||||
Assertions.checkArgument(length % 2 == 0);
|
||||
byte[] result = new byte[length / 2];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = (byte) ((Character.digit(hexString.charAt(i * 2), 16) << 4)
|
||||
+ Character.digit(hexString.charAt(i * 2 + 1), 16));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue