mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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.
|
// Scratch variables to avoid allocations.
|
||||||
private final ParsableByteArray seiWrapper;
|
private final ParsableByteArray seiWrapper;
|
||||||
private int[] scratchEscapePositions;
|
|
||||||
|
|
||||||
public H264Reader(TrackOutput output, SeiReader seiReader, boolean idrKeyframesOnly) {
|
public H264Reader(TrackOutput output, SeiReader seiReader, boolean idrKeyframesOnly) {
|
||||||
super(output);
|
super(output);
|
||||||
|
|
@ -77,7 +76,6 @@ import java.util.List;
|
||||||
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
|
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
|
||||||
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);
|
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);
|
||||||
seiWrapper = new ParsableByteArray();
|
seiWrapper = new ParsableByteArray();
|
||||||
scratchEscapePositions = new int[10];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -191,7 +189,7 @@ import java.util.List;
|
||||||
sps.endNalUnit(discardPadding);
|
sps.endNalUnit(discardPadding);
|
||||||
pps.endNalUnit(discardPadding);
|
pps.endNalUnit(discardPadding);
|
||||||
if (sei.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.reset(sei.nalData, unescapedLength);
|
||||||
seiWrapper.setPosition(4); // NAL prefix and nal_unit() header.
|
seiWrapper.setPosition(4); // NAL prefix and nal_unit() header.
|
||||||
seiReader.consume(seiWrapper, pesTimeUs, true);
|
seiReader.consume(seiWrapper, pesTimeUs, true);
|
||||||
|
|
@ -208,7 +206,7 @@ import java.util.List;
|
||||||
initializationData.add(ppsData);
|
initializationData.add(ppsData);
|
||||||
|
|
||||||
// Unescape and then parse the SPS unit.
|
// Unescape and then parse the SPS unit.
|
||||||
unescapeStream(sps.nalData, sps.nalLength);
|
NalUnitUtil.unescapeStream(sps.nalData, sps.nalLength);
|
||||||
ParsableBitArray bitArray = new ParsableBitArray(sps.nalData);
|
ParsableBitArray bitArray = new ParsableBitArray(sps.nalData);
|
||||||
bitArray.skipBits(32); // NAL header
|
bitArray.skipBits(32); // NAL header
|
||||||
int profileIdc = bitArray.readBits(8);
|
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.
|
* 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 android.util.Log;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -71,7 +70,6 @@ import java.util.Collections;
|
||||||
|
|
||||||
// Scratch variables to avoid allocations.
|
// Scratch variables to avoid allocations.
|
||||||
private final ParsableByteArray seiWrapper;
|
private final ParsableByteArray seiWrapper;
|
||||||
private int[] scratchEscapePositions;
|
|
||||||
|
|
||||||
public H265Reader(TrackOutput output, SeiReader seiReader) {
|
public H265Reader(TrackOutput output, SeiReader seiReader) {
|
||||||
super(output);
|
super(output);
|
||||||
|
|
@ -83,7 +81,6 @@ import java.util.Collections;
|
||||||
prefixSei = new NalUnitTargetBuffer(PREFIX_SEI_NUT, 128);
|
prefixSei = new NalUnitTargetBuffer(PREFIX_SEI_NUT, 128);
|
||||||
suffixSei = new NalUnitTargetBuffer(SUFFIX_SEI_NUT, 128);
|
suffixSei = new NalUnitTargetBuffer(SUFFIX_SEI_NUT, 128);
|
||||||
seiWrapper = new ParsableByteArray();
|
seiWrapper = new ParsableByteArray();
|
||||||
scratchEscapePositions = new int[10];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -189,7 +186,7 @@ import java.util.Collections;
|
||||||
sps.endNalUnit(discardPadding);
|
sps.endNalUnit(discardPadding);
|
||||||
pps.endNalUnit(discardPadding);
|
pps.endNalUnit(discardPadding);
|
||||||
if (prefixSei.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);
|
seiWrapper.reset(prefixSei.nalData, unescapedLength);
|
||||||
|
|
||||||
// Skip the NAL prefix and type.
|
// Skip the NAL prefix and type.
|
||||||
|
|
@ -197,7 +194,7 @@ import java.util.Collections;
|
||||||
seiReader.consume(seiWrapper, pesTimeUs, true);
|
seiReader.consume(seiWrapper, pesTimeUs, true);
|
||||||
}
|
}
|
||||||
if (suffixSei.endNalUnit(discardPadding)) {
|
if (suffixSei.endNalUnit(discardPadding)) {
|
||||||
int unescapedLength = unescapeStream(suffixSei.nalData, suffixSei.nalLength);
|
int unescapedLength = NalUnitUtil.unescapeStream(suffixSei.nalData, suffixSei.nalLength);
|
||||||
seiWrapper.reset(suffixSei.nalData, unescapedLength);
|
seiWrapper.reset(suffixSei.nalData, unescapedLength);
|
||||||
|
|
||||||
// Skip the NAL prefix and type.
|
// 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);
|
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.
|
// 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);
|
ParsableBitArray bitArray = new ParsableBitArray(sps.nalData);
|
||||||
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
|
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
|
||||||
int maxSubLayersMinus1 = bitArray.readBits(3);
|
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. */
|
/** Returns whether the NAL unit is a random access point. */
|
||||||
private static boolean isRandomAccessPoint(int nalUnitType) {
|
private static boolean isRandomAccessPoint(int nalUnitType) {
|
||||||
return nalUnitType == BLA_W_LP || nalUnitType == BLA_W_RADL || nalUnitType == BLA_N_LP
|
return nalUnitType == BLA_W_LP || nalUnitType == BLA_W_RADL || nalUnitType == BLA_N_LP
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
package com.google.android.exoplayer.util;
|
package com.google.android.exoplayer.util;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility methods for handling H.264/AVC and H.265/HEVC NAL units.
|
* Utility methods for handling H.264/AVC and H.265/HEVC NAL units.
|
||||||
|
|
@ -48,6 +49,61 @@ public final class NalUnitUtil {
|
||||||
2f
|
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
|
* Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
|
||||||
* {@code size} bytes preceding the buffer's position.
|
* {@code size} bytes preceding the buffer's position.
|
||||||
|
|
@ -189,6 +245,15 @@ public final class NalUnitUtil {
|
||||||
prefixFlags[2] = false;
|
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
|
* 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.
|
* assumed that the top bit will always be set to zero.
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,18 @@ public class NalUnitUtilTest extends TestCase {
|
||||||
assertPrefixFlagsCleared(prefixFlags);
|
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() {
|
private static byte[] buildTestData() {
|
||||||
byte[] data = new byte[20];
|
byte[] data = new byte[20];
|
||||||
for (int i = 0; i < data.length; i++) {
|
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]);
|
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