mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Detect sample boundaries in H.264 TSs without AUDs.
Issue: #1263 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117813106
This commit is contained in:
parent
fc716b5711
commit
8353463bf5
9 changed files with 547 additions and 284 deletions
|
|
@ -269,11 +269,11 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||||
assertEquals(10, bitArray.bitsLeft());
|
assertEquals(10, bitArray.bitsLeft());
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
||||||
|
|
||||||
bitArray.readBit();
|
bitArray.skipBits(1);
|
||||||
assertEquals(9, bitArray.bitsLeft());
|
assertEquals(9, bitArray.bitsLeft());
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
||||||
|
|
||||||
bitArray.readBits(1);
|
bitArray.skipBits(1);
|
||||||
assertEquals(8, bitArray.bitsLeft());
|
assertEquals(8, bitArray.bitsLeft());
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.ParserException;
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.NalUnitUtil;
|
import com.google.android.exoplayer.util.NalUnitUtil;
|
||||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||||
|
|
@ -165,7 +164,7 @@ import java.util.List;
|
||||||
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0));
|
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0));
|
||||||
// Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte).
|
// Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte).
|
||||||
spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1));
|
spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1));
|
||||||
CodecSpecificDataUtil.SpsData sps = CodecSpecificDataUtil.parseSpsNalUnit(spsDataBitArray);
|
NalUnitUtil.SpsData sps = NalUnitUtil.parseSpsNalUnit(spsDataBitArray);
|
||||||
width = sps.width;
|
width = sps.width;
|
||||||
height = sps.height;
|
height = sps.height;
|
||||||
pixelWidthAspectRatio = sps.pixelWidthAspectRatio;
|
pixelWidthAspectRatio = sps.pixelWidthAspectRatio;
|
||||||
|
|
|
||||||
|
|
@ -669,8 +669,7 @@ import java.util.List;
|
||||||
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0));
|
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0));
|
||||||
// Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte).
|
// Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte).
|
||||||
spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1));
|
spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1));
|
||||||
pixelWidthAspectRatio = CodecSpecificDataUtil.parseSpsNalUnit(spsDataBitArray)
|
pixelWidthAspectRatio = NalUnitUtil.parseSpsNalUnit(spsDataBitArray).pixelWidthAspectRatio;
|
||||||
.pixelWidthAspectRatio;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AvcCData(initializationData, nalUnitLengthFieldLength, pixelWidthAspectRatio);
|
return new AvcCData(initializationData, nalUnitLengthFieldLength, pixelWidthAspectRatio);
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,14 @@ package com.google.android.exoplayer.extractor.ts;
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.Format;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil.SpsData;
|
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.NalUnitUtil;
|
import com.google.android.exoplayer.util.NalUnitUtil;
|
||||||
|
import com.google.android.exoplayer.util.NalUnitUtil.SpsData;
|
||||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -34,15 +35,9 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
/* package */ final class H264Reader extends ElementaryStreamReader {
|
/* package */ final class H264Reader extends ElementaryStreamReader {
|
||||||
|
|
||||||
private static final int FRAME_TYPE_I = 2;
|
|
||||||
private static final int FRAME_TYPE_ALL_I = 7;
|
|
||||||
|
|
||||||
private static final int NAL_UNIT_TYPE_IFR = 1; // Coded slice of a non-IDR picture
|
|
||||||
private static final int NAL_UNIT_TYPE_IDR = 5; // Coded slice of an IDR picture
|
|
||||||
private static final int NAL_UNIT_TYPE_SEI = 6; // Supplemental enhancement information
|
private static final int NAL_UNIT_TYPE_SEI = 6; // Supplemental enhancement information
|
||||||
private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set
|
private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set
|
||||||
private static final int NAL_UNIT_TYPE_PPS = 8; // Picture parameter set
|
private static final int NAL_UNIT_TYPE_PPS = 8; // Picture parameter set
|
||||||
private static final int NAL_UNIT_TYPE_AUD = 9; // Access unit delimiter
|
|
||||||
|
|
||||||
// State that should not be reset on seek.
|
// State that should not be reset on seek.
|
||||||
private boolean hasOutputFormat;
|
private boolean hasOutputFormat;
|
||||||
|
|
@ -50,29 +45,32 @@ import java.util.List;
|
||||||
// State that should be reset on seek.
|
// State that should be reset on seek.
|
||||||
private final SeiReader seiReader;
|
private final SeiReader seiReader;
|
||||||
private final boolean[] prefixFlags;
|
private final boolean[] prefixFlags;
|
||||||
private final IfrParserBuffer ifrParserBuffer;
|
private final SampleReader sampleReader;
|
||||||
private final NalUnitTargetBuffer sps;
|
private final NalUnitTargetBuffer sps;
|
||||||
private final NalUnitTargetBuffer pps;
|
private final NalUnitTargetBuffer pps;
|
||||||
private final NalUnitTargetBuffer sei;
|
private final NalUnitTargetBuffer sei;
|
||||||
private boolean foundFirstSample;
|
|
||||||
private long totalBytesWritten;
|
private long totalBytesWritten;
|
||||||
|
|
||||||
// Per packet state that gets reset at the start of each packet.
|
// Per packet state that gets reset at the start of each packet.
|
||||||
private long pesTimeUs;
|
private long pesTimeUs;
|
||||||
|
|
||||||
// Per sample state that gets reset at the start of each sample.
|
|
||||||
private boolean isKeyframe;
|
|
||||||
private long samplePosition;
|
|
||||||
private long sampleTimeUs;
|
|
||||||
|
|
||||||
// Scratch variables to avoid allocations.
|
// Scratch variables to avoid allocations.
|
||||||
private final ParsableByteArray seiWrapper;
|
private final ParsableByteArray seiWrapper;
|
||||||
|
|
||||||
public H264Reader(TrackOutput output, SeiReader seiReader, boolean allowNonIdrKeyframes) {
|
/**
|
||||||
|
* @param output A {@link TrackOutput} to which H.264 samples should be written.
|
||||||
|
* @param seiReader A reader for EIA-608 samples in SEI NAL units.
|
||||||
|
* @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as
|
||||||
|
* synchronization samples (key-frames).
|
||||||
|
* @param detectAccessUnits Whether to split the input stream into access units (samples) based on
|
||||||
|
* slice headers. Pass {@code false} if the stream contains access unit delimiters (AUDs).
|
||||||
|
*/
|
||||||
|
public H264Reader(TrackOutput output, SeiReader seiReader, boolean allowNonIdrKeyframes,
|
||||||
|
boolean detectAccessUnits) {
|
||||||
super(output);
|
super(output);
|
||||||
this.seiReader = seiReader;
|
this.seiReader = seiReader;
|
||||||
prefixFlags = new boolean[3];
|
prefixFlags = new boolean[3];
|
||||||
ifrParserBuffer = allowNonIdrKeyframes ? new IfrParserBuffer() : null;
|
sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits);
|
||||||
sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128);
|
sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128);
|
||||||
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);
|
||||||
|
|
@ -85,10 +83,7 @@ import java.util.List;
|
||||||
sps.reset();
|
sps.reset();
|
||||||
pps.reset();
|
pps.reset();
|
||||||
sei.reset();
|
sei.reset();
|
||||||
if (ifrParserBuffer != null) {
|
sampleReader.reset();
|
||||||
ifrParserBuffer.reset();
|
|
||||||
}
|
|
||||||
foundFirstSample = false;
|
|
||||||
totalBytesWritten = 0;
|
totalBytesWritten = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,7 +109,7 @@ import java.util.List;
|
||||||
|
|
||||||
if (nalUnitOffset == limit) {
|
if (nalUnitOffset == limit) {
|
||||||
// We've scanned to the end of the data without finding the start of another NAL unit.
|
// We've scanned to the end of the data without finding the start of another NAL unit.
|
||||||
feedNalUnitTargetBuffersData(dataArray, offset, limit);
|
nalUnitData(dataArray, offset, limit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,42 +120,17 @@ import java.util.List;
|
||||||
// It may be negative if the NAL unit started in the previously consumed data.
|
// It may be negative if the NAL unit started in the previously consumed data.
|
||||||
int lengthToNalUnit = nalUnitOffset - offset;
|
int lengthToNalUnit = nalUnitOffset - offset;
|
||||||
if (lengthToNalUnit > 0) {
|
if (lengthToNalUnit > 0) {
|
||||||
feedNalUnitTargetBuffersData(dataArray, offset, nalUnitOffset);
|
nalUnitData(dataArray, offset, nalUnitOffset);
|
||||||
}
|
}
|
||||||
|
int bytesWrittenPastPosition = limit - nalUnitOffset;
|
||||||
switch (nalUnitType) {
|
long absolutePosition = totalBytesWritten - bytesWrittenPastPosition;
|
||||||
case NAL_UNIT_TYPE_IDR:
|
|
||||||
isKeyframe = true;
|
|
||||||
break;
|
|
||||||
case NAL_UNIT_TYPE_AUD:
|
|
||||||
int bytesWrittenPastNalUnit = limit - nalUnitOffset;
|
|
||||||
if (foundFirstSample) {
|
|
||||||
if (ifrParserBuffer != null && ifrParserBuffer.isCompleted()) {
|
|
||||||
int sliceType = ifrParserBuffer.getSliceType();
|
|
||||||
isKeyframe |= (sliceType == FRAME_TYPE_I || sliceType == FRAME_TYPE_ALL_I);
|
|
||||||
ifrParserBuffer.reset();
|
|
||||||
}
|
|
||||||
if (isKeyframe && !hasOutputFormat && sps.isCompleted() && pps.isCompleted()) {
|
|
||||||
output.format(parseMediaFormat(sps, pps));
|
|
||||||
hasOutputFormat = true;
|
|
||||||
}
|
|
||||||
int flags = isKeyframe ? C.SAMPLE_FLAG_SYNC : 0;
|
|
||||||
int size = (int) (totalBytesWritten - samplePosition) - bytesWrittenPastNalUnit;
|
|
||||||
output.sampleMetadata(sampleTimeUs, flags, size, bytesWrittenPastNalUnit, null);
|
|
||||||
}
|
|
||||||
foundFirstSample = true;
|
|
||||||
samplePosition = totalBytesWritten - bytesWrittenPastNalUnit;
|
|
||||||
sampleTimeUs = pesTimeUs;
|
|
||||||
isKeyframe = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indicate the end of the previous NAL unit. If the length to the start of the next unit
|
// Indicate the end of the previous NAL unit. If the length to the start of the next unit
|
||||||
// is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes
|
// is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes
|
||||||
// when notifying that the unit has ended.
|
// when notifying that the unit has ended.
|
||||||
feedNalUnitTargetEnd(pesTimeUs, lengthToNalUnit < 0 ? -lengthToNalUnit : 0);
|
endNalUnit(absolutePosition, bytesWrittenPastPosition,
|
||||||
|
lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs);
|
||||||
// Indicate the start of the next NAL unit.
|
// Indicate the start of the next NAL unit.
|
||||||
feedNalUnitTargetBuffersStart(nalUnitType);
|
startNalUnit(absolutePosition, nalUnitType, pesTimeUs);
|
||||||
// Continue scanning the data.
|
// Continue scanning the data.
|
||||||
offset = nalUnitOffset + 3;
|
offset = nalUnitOffset + 3;
|
||||||
}
|
}
|
||||||
|
|
@ -172,99 +142,152 @@ import java.util.List;
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
private void feedNalUnitTargetBuffersStart(int nalUnitType) {
|
private void startNalUnit(long position, int nalUnitType, long pesTimeUs) {
|
||||||
if (ifrParserBuffer != null) {
|
if (!hasOutputFormat || sampleReader.needsSpsPps()) {
|
||||||
ifrParserBuffer.startNalUnit(nalUnitType);
|
|
||||||
}
|
|
||||||
if (!hasOutputFormat) {
|
|
||||||
sps.startNalUnit(nalUnitType);
|
sps.startNalUnit(nalUnitType);
|
||||||
pps.startNalUnit(nalUnitType);
|
pps.startNalUnit(nalUnitType);
|
||||||
}
|
}
|
||||||
sei.startNalUnit(nalUnitType);
|
sei.startNalUnit(nalUnitType);
|
||||||
|
sampleReader.startNalUnit(position, nalUnitType, pesTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void feedNalUnitTargetBuffersData(byte[] dataArray, int offset, int limit) {
|
private void nalUnitData(byte[] dataArray, int offset, int limit) {
|
||||||
if (ifrParserBuffer != null) {
|
if (!hasOutputFormat || sampleReader.needsSpsPps()) {
|
||||||
ifrParserBuffer.appendToNalUnit(dataArray, offset, limit);
|
|
||||||
}
|
|
||||||
if (!hasOutputFormat) {
|
|
||||||
sps.appendToNalUnit(dataArray, offset, limit);
|
sps.appendToNalUnit(dataArray, offset, limit);
|
||||||
pps.appendToNalUnit(dataArray, offset, limit);
|
pps.appendToNalUnit(dataArray, offset, limit);
|
||||||
}
|
}
|
||||||
sei.appendToNalUnit(dataArray, offset, limit);
|
sei.appendToNalUnit(dataArray, offset, limit);
|
||||||
|
sampleReader.appendToNalUnit(dataArray, offset, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void feedNalUnitTargetEnd(long pesTimeUs, int discardPadding) {
|
private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {
|
||||||
sps.endNalUnit(discardPadding);
|
if (!hasOutputFormat || sampleReader.needsSpsPps()) {
|
||||||
pps.endNalUnit(discardPadding);
|
sps.endNalUnit(discardPadding);
|
||||||
|
pps.endNalUnit(discardPadding);
|
||||||
|
if (!hasOutputFormat) {
|
||||||
|
if (sps.isCompleted() && pps.isCompleted()) {
|
||||||
|
List<byte[]> initializationData = new ArrayList<>();
|
||||||
|
initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength));
|
||||||
|
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
|
||||||
|
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(unescape(sps));
|
||||||
|
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(unescape(pps));
|
||||||
|
output.format(Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, Format.NO_VALUE,
|
||||||
|
Format.NO_VALUE, spsData.width, spsData.height, Format.NO_VALUE, initializationData,
|
||||||
|
Format.NO_VALUE, spsData.pixelWidthAspectRatio));
|
||||||
|
hasOutputFormat = true;
|
||||||
|
sampleReader.putSps(spsData);
|
||||||
|
sampleReader.putPps(ppsData);
|
||||||
|
sps.reset();
|
||||||
|
pps.reset();
|
||||||
|
}
|
||||||
|
} else if (sps.isCompleted()) {
|
||||||
|
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(unescape(sps));
|
||||||
|
sampleReader.putSps(spsData);
|
||||||
|
sps.reset();
|
||||||
|
} else if (pps.isCompleted()) {
|
||||||
|
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(unescape(pps));
|
||||||
|
sampleReader.putPps(ppsData);
|
||||||
|
pps.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (sei.endNalUnit(discardPadding)) {
|
if (sei.endNalUnit(discardPadding)) {
|
||||||
int unescapedLength = NalUnitUtil.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(pesTimeUs, seiWrapper);
|
seiReader.consume(pesTimeUs, seiWrapper);
|
||||||
}
|
}
|
||||||
|
sampleReader.endNalUnit(position, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Format parseMediaFormat(NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) {
|
private static ParsableBitArray unescape(NalUnitTargetBuffer buffer) {
|
||||||
List<byte[]> initializationData = new ArrayList<>();
|
int length = NalUnitUtil.unescapeStream(buffer.nalData, buffer.nalLength);
|
||||||
initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength));
|
ParsableBitArray bitArray = new ParsableBitArray(buffer.nalData, length);
|
||||||
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
|
|
||||||
|
|
||||||
// Unescape and parse the SPS unit.
|
|
||||||
NalUnitUtil.unescapeStream(sps.nalData, sps.nalLength);
|
|
||||||
ParsableBitArray bitArray = new ParsableBitArray(sps.nalData);
|
|
||||||
bitArray.skipBits(32); // NAL header
|
bitArray.skipBits(32); // NAL header
|
||||||
SpsData parsedSpsData = CodecSpecificDataUtil.parseSpsNalUnit(bitArray);
|
return bitArray;
|
||||||
|
|
||||||
return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, Format.NO_VALUE,
|
|
||||||
Format.NO_VALUE, parsedSpsData.width, parsedSpsData.height, Format.NO_VALUE,
|
|
||||||
initializationData, Format.NO_VALUE, parsedSpsData.pixelWidthAspectRatio);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A buffer specifically for IFR units that can be used to parse the IFR's slice type.
|
* Consumes a stream of NAL units and outputs samples.
|
||||||
*/
|
*/
|
||||||
private static final class IfrParserBuffer {
|
private static final class SampleReader {
|
||||||
|
|
||||||
private static final int DEFAULT_BUFFER_SIZE = 128;
|
private static final int DEFAULT_BUFFER_SIZE = 128;
|
||||||
private static final int NOT_SET = -1;
|
|
||||||
|
|
||||||
private final ParsableBitArray scratchSliceType;
|
private static final int NAL_UNIT_TYPE_NON_IDR = 1; // Coded slice of a non-IDR picture
|
||||||
|
private static final int NAL_UNIT_TYPE_PARTITION_A = 2; // Coded slice data partition A
|
||||||
|
private static final int NAL_UNIT_TYPE_IDR = 5; // Coded slice of an IDR picture
|
||||||
|
private static final int NAL_UNIT_TYPE_AUD = 9; // Access unit delimiter
|
||||||
|
|
||||||
private byte[] ifrData;
|
private final TrackOutput output;
|
||||||
private int ifrLength;
|
private final boolean allowNonIdrKeyframes;
|
||||||
|
private final boolean detectAccessUnits;
|
||||||
|
private final ParsableBitArray scratch;
|
||||||
|
private final SparseArray<NalUnitUtil.SpsData> sps;
|
||||||
|
private final SparseArray<NalUnitUtil.PpsData> pps;
|
||||||
|
|
||||||
|
private byte[] buffer;
|
||||||
|
private int bufferLength;
|
||||||
|
|
||||||
|
// Per NAL unit state. A sample consists of one or more NAL units.
|
||||||
|
private int nalUnitType;
|
||||||
|
private long nalUnitStartPosition;
|
||||||
private boolean isFilling;
|
private boolean isFilling;
|
||||||
private int sliceType;
|
private long nalUnitTimeUs;
|
||||||
|
private SliceHeaderData previousSliceHeader;
|
||||||
|
private SliceHeaderData sliceHeader;
|
||||||
|
|
||||||
public IfrParserBuffer() {
|
// Per sample state that gets reset at the start of each sample.
|
||||||
ifrData = new byte[DEFAULT_BUFFER_SIZE];
|
private boolean readingSample;
|
||||||
scratchSliceType = new ParsableBitArray(ifrData);
|
private long samplePosition;
|
||||||
|
private long sampleTimeUs;
|
||||||
|
private boolean sampleIsKeyframe;
|
||||||
|
|
||||||
|
public SampleReader(TrackOutput output, boolean allowNonIdrKeyframes,
|
||||||
|
boolean detectAccessUnits) {
|
||||||
|
this.output = output;
|
||||||
|
this.allowNonIdrKeyframes = allowNonIdrKeyframes;
|
||||||
|
this.detectAccessUnits = detectAccessUnits;
|
||||||
|
sps = new SparseArray<>();
|
||||||
|
pps = new SparseArray<>();
|
||||||
|
previousSliceHeader = new SliceHeaderData();
|
||||||
|
sliceHeader = new SliceHeaderData();
|
||||||
|
scratch = new ParsableBitArray();
|
||||||
|
buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean needsSpsPps() {
|
||||||
* Resets the buffer, clearing any data that it holds.
|
return detectAccessUnits;
|
||||||
*/
|
}
|
||||||
|
|
||||||
|
public void putSps(NalUnitUtil.SpsData spsData) {
|
||||||
|
sps.append(spsData.seqParameterSetId, spsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putPps(NalUnitUtil.PpsData ppsData) {
|
||||||
|
pps.append(ppsData.picParameterSetId, ppsData);
|
||||||
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
isFilling = false;
|
isFilling = false;
|
||||||
ifrLength = 0;
|
readingSample = false;
|
||||||
sliceType = NOT_SET;
|
sliceHeader.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void startNalUnit(long position, int type, long pesTimeUs) {
|
||||||
* True if enough data was added to the buffer that the slice type was determined.
|
nalUnitType = type;
|
||||||
*/
|
nalUnitTimeUs = pesTimeUs;
|
||||||
public boolean isCompleted() {
|
nalUnitStartPosition = position;
|
||||||
return sliceType != NOT_SET;
|
if ((allowNonIdrKeyframes && nalUnitType == NAL_UNIT_TYPE_NON_IDR)
|
||||||
}
|
|| (detectAccessUnits && (nalUnitType == NAL_UNIT_TYPE_IDR
|
||||||
|
|| nalUnitType == NAL_UNIT_TYPE_NON_IDR
|
||||||
/**
|
|| nalUnitType == NAL_UNIT_TYPE_PARTITION_A))) {
|
||||||
* Invoked to indicate that a NAL unit has started, and if it is an IFR then the buffer will
|
// Store the previous header and prepare to populate the new one.
|
||||||
* start.
|
SliceHeaderData newSliceHeader = previousSliceHeader;
|
||||||
*/
|
previousSliceHeader = sliceHeader;
|
||||||
public void startNalUnit(int nalUnitType) {
|
sliceHeader = newSliceHeader;
|
||||||
if (nalUnitType == NAL_UNIT_TYPE_IFR) {
|
sliceHeader.clear();
|
||||||
reset();
|
bufferLength = 0;
|
||||||
isFilling = true;
|
isFilling = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -281,38 +304,214 @@ import java.util.List;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int readLength = limit - offset;
|
int readLength = limit - offset;
|
||||||
if (ifrData.length < ifrLength + readLength) {
|
if (buffer.length < bufferLength + readLength) {
|
||||||
ifrData = Arrays.copyOf(ifrData, (ifrLength + readLength) * 2);
|
buffer = Arrays.copyOf(buffer, (bufferLength + readLength) * 2);
|
||||||
}
|
}
|
||||||
System.arraycopy(data, offset, ifrData, ifrLength, readLength);
|
System.arraycopy(data, offset, buffer, bufferLength, readLength);
|
||||||
ifrLength += readLength;
|
bufferLength += readLength;
|
||||||
|
|
||||||
scratchSliceType.reset(ifrData, ifrLength);
|
scratch.reset(buffer, bufferLength);
|
||||||
scratchSliceType.skipBits(8);
|
if (scratch.bitsLeft() < 8) {
|
||||||
// first_mb_in_slice
|
|
||||||
int len = scratchSliceType.peekExpGolombCodedNumLength();
|
|
||||||
if ((len == -1) || (len > scratchSliceType.bitsLeft())) {
|
|
||||||
// Not enough yet
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
scratch.skipBits(1); // forbidden_zero_bit
|
||||||
|
int nalRefIdc = scratch.readBits(2);
|
||||||
|
scratch.skipBits(5); // nal_unit_type
|
||||||
|
|
||||||
scratchSliceType.skipBits(len);
|
// Read the slice header using the syntax defined in ITU-T Recommendation H.264 (2013)
|
||||||
// slice_type
|
// subsection 7.3.3.
|
||||||
len = scratchSliceType.peekExpGolombCodedNumLength();
|
if (!scratch.canReadExpGolombCodedNum()) {
|
||||||
if ((len == -1) || (len > scratchSliceType.bitsLeft())) {
|
|
||||||
// Not enough yet
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sliceType = scratchSliceType.readUnsignedExpGolombCodedInt();
|
scratch.readUnsignedExpGolombCodedInt(); // first_mb_in_slice
|
||||||
|
if (!scratch.canReadExpGolombCodedNum()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int sliceType = scratch.readUnsignedExpGolombCodedInt();
|
||||||
|
if (!detectAccessUnits) {
|
||||||
|
// There are AUDs in the stream so the rest of the header can be ignored.
|
||||||
|
isFilling = false;
|
||||||
|
sliceHeader.setSliceType(sliceType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!scratch.canReadExpGolombCodedNum()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int picParameterSetId = scratch.readUnsignedExpGolombCodedInt();
|
||||||
|
if (pps.indexOfKey(picParameterSetId) < 0) {
|
||||||
|
// We have not seen the PPS yet, so don't try to parse the slice header.
|
||||||
|
isFilling = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NalUnitUtil.PpsData ppsData = pps.get(picParameterSetId);
|
||||||
|
NalUnitUtil.SpsData spsData = sps.get(ppsData.seqParameterSetId);
|
||||||
|
if (spsData.separateColorPlaneFlag) {
|
||||||
|
if (scratch.bitsLeft() < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scratch.skipBits(2); // colour_plane_id
|
||||||
|
}
|
||||||
|
if (scratch.bitsLeft() < spsData.frameNumLength) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean fieldPicFlag = false;
|
||||||
|
boolean bottomFieldFlagPresent = false;
|
||||||
|
boolean bottomFieldFlag = false;
|
||||||
|
int frameNum = scratch.readBits(spsData.frameNumLength);
|
||||||
|
if (!spsData.frameMbsOnlyFlag) {
|
||||||
|
if (scratch.bitsLeft() < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fieldPicFlag = scratch.readBit();
|
||||||
|
if (fieldPicFlag) {
|
||||||
|
if (scratch.bitsLeft() < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bottomFieldFlag = scratch.readBit();
|
||||||
|
bottomFieldFlagPresent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean idrPicFlag = nalUnitType == NAL_UNIT_TYPE_IDR;
|
||||||
|
int idrPicId = 0;
|
||||||
|
if (idrPicFlag) {
|
||||||
|
if (!scratch.canReadExpGolombCodedNum()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
idrPicId = scratch.readUnsignedExpGolombCodedInt();
|
||||||
|
}
|
||||||
|
int picOrderCntLsb = 0;
|
||||||
|
int deltaPicOrderCntBottom = 0;
|
||||||
|
int deltaPicOrderCnt0 = 0;
|
||||||
|
int deltaPicOrderCnt1 = 0;
|
||||||
|
if (spsData.picOrderCountType == 0) {
|
||||||
|
if (scratch.bitsLeft() < spsData.picOrderCntLsbLength) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
picOrderCntLsb = scratch.readBits(spsData.picOrderCntLsbLength);
|
||||||
|
if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) {
|
||||||
|
if (!scratch.canReadExpGolombCodedNum()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deltaPicOrderCntBottom = scratch.readSignedExpGolombCodedInt();
|
||||||
|
}
|
||||||
|
} else if (spsData.picOrderCountType == 1
|
||||||
|
&& !spsData.deltaPicOrderAlwaysZeroFlag) {
|
||||||
|
if (!scratch.canReadExpGolombCodedNum()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deltaPicOrderCnt0 = scratch.readSignedExpGolombCodedInt();
|
||||||
|
if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) {
|
||||||
|
if (!scratch.canReadExpGolombCodedNum()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deltaPicOrderCnt1 = scratch.readSignedExpGolombCodedInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sliceHeader.setAll(spsData, nalRefIdc, sliceType, frameNum, picParameterSetId, fieldPicFlag,
|
||||||
|
bottomFieldFlagPresent, bottomFieldFlag, idrPicFlag, idrPicId, picOrderCntLsb,
|
||||||
|
deltaPicOrderCntBottom, deltaPicOrderCnt0, deltaPicOrderCnt1);
|
||||||
isFilling = false;
|
isFilling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void endNalUnit(long position, int offset) {
|
||||||
* @return the slice type of the IFR.
|
if (nalUnitType == NAL_UNIT_TYPE_AUD
|
||||||
*/
|
|| (detectAccessUnits && sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) {
|
||||||
public int getSliceType() {
|
// If the NAL unit ending is the start of a new sample, output the previous one.
|
||||||
return sliceType;
|
if (readingSample) {
|
||||||
|
int nalUnitLength = (int) (position - nalUnitStartPosition);
|
||||||
|
outputSample(offset + nalUnitLength);
|
||||||
|
}
|
||||||
|
samplePosition = nalUnitStartPosition;
|
||||||
|
sampleTimeUs = nalUnitTimeUs;
|
||||||
|
sampleIsKeyframe = false;
|
||||||
|
readingSample = true;
|
||||||
|
}
|
||||||
|
sampleIsKeyframe |= nalUnitType == NAL_UNIT_TYPE_IDR || (allowNonIdrKeyframes
|
||||||
|
&& nalUnitType == NAL_UNIT_TYPE_NON_IDR && sliceHeader.isISlice());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void outputSample(int offset) {
|
||||||
|
int flags = sampleIsKeyframe ? C.SAMPLE_FLAG_SYNC : 0;
|
||||||
|
int size = (int) (nalUnitStartPosition - samplePosition);
|
||||||
|
output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SliceHeaderData {
|
||||||
|
|
||||||
|
private static final int SLICE_TYPE_I = 2;
|
||||||
|
private static final int SLICE_TYPE_ALL_I = 7;
|
||||||
|
|
||||||
|
private boolean isComplete;
|
||||||
|
private boolean hasSliceType;
|
||||||
|
|
||||||
|
private SpsData spsData;
|
||||||
|
private int nalRefIdc;
|
||||||
|
private int sliceType;
|
||||||
|
private int frameNum;
|
||||||
|
private int picParameterSetId;
|
||||||
|
private boolean fieldPicFlag;
|
||||||
|
private boolean bottomFieldFlagPresent;
|
||||||
|
private boolean bottomFieldFlag;
|
||||||
|
private boolean idrPicFlag;
|
||||||
|
private int idrPicId;
|
||||||
|
private int picOrderCntLsb;
|
||||||
|
private int deltaPicOrderCntBottom;
|
||||||
|
private int deltaPicOrderCnt0;
|
||||||
|
private int deltaPicOrderCnt1;
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
hasSliceType = false;
|
||||||
|
isComplete = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSliceType(int sliceType) {
|
||||||
|
this.sliceType = sliceType;
|
||||||
|
hasSliceType = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAll(SpsData spsData, int nalRefIdc, int sliceType, int frameNum,
|
||||||
|
int picParameterSetId, boolean fieldPicFlag, boolean bottomFieldFlagPresent,
|
||||||
|
boolean bottomFieldFlag, boolean idrPicFlag, int idrPicId, int picOrderCntLsb,
|
||||||
|
int deltaPicOrderCntBottom, int deltaPicOrderCnt0, int deltaPicOrderCnt1) {
|
||||||
|
this.spsData = spsData;
|
||||||
|
this.nalRefIdc = nalRefIdc;
|
||||||
|
this.sliceType = sliceType;
|
||||||
|
this.frameNum = frameNum;
|
||||||
|
this.picParameterSetId = picParameterSetId;
|
||||||
|
this.fieldPicFlag = fieldPicFlag;
|
||||||
|
this.bottomFieldFlagPresent = bottomFieldFlagPresent;
|
||||||
|
this.bottomFieldFlag = bottomFieldFlag;
|
||||||
|
this.idrPicFlag = idrPicFlag;
|
||||||
|
this.idrPicId = idrPicId;
|
||||||
|
this.picOrderCntLsb = picOrderCntLsb;
|
||||||
|
this.deltaPicOrderCntBottom = deltaPicOrderCntBottom;
|
||||||
|
this.deltaPicOrderCnt0 = deltaPicOrderCnt0;
|
||||||
|
this.deltaPicOrderCnt1 = deltaPicOrderCnt1;
|
||||||
|
isComplete = true;
|
||||||
|
hasSliceType = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isISlice() {
|
||||||
|
return hasSliceType && (sliceType == SLICE_TYPE_ALL_I || sliceType == SLICE_TYPE_I);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) {
|
||||||
|
// See ISO 14496-10 subsection 7.4.1.2.4.
|
||||||
|
return isComplete && (!other.isComplete || frameNum != other.frameNum
|
||||||
|
|| picParameterSetId != other.picParameterSetId || fieldPicFlag != other.fieldPicFlag
|
||||||
|
|| (bottomFieldFlagPresent && other.bottomFieldFlagPresent
|
||||||
|
&& bottomFieldFlag != other.bottomFieldFlag)
|
||||||
|
|| (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0))
|
||||||
|
|| (spsData.picOrderCountType == 0 && other.spsData.picOrderCountType == 0
|
||||||
|
&& (picOrderCntLsb != other.picOrderCntLsb
|
||||||
|
|| deltaPicOrderCntBottom != other.deltaPicOrderCntBottom))
|
||||||
|
|| (spsData.picOrderCountType == 1 && other.spsData.picOrderCountType == 1
|
||||||
|
&& (deltaPicOrderCnt0 != other.deltaPicOrderCnt0
|
||||||
|
|| deltaPicOrderCnt1 != other.deltaPicOrderCnt1))
|
||||||
|
|| idrPicFlag != other.idrPicFlag
|
||||||
|
|| (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,10 @@ import java.util.Collections;
|
||||||
// Scratch variables to avoid allocations.
|
// Scratch variables to avoid allocations.
|
||||||
private final ParsableByteArray seiWrapper;
|
private final ParsableByteArray seiWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param output A {@link TrackOutput} to which H.265 samples should be written.
|
||||||
|
* @param seiReader A reader for EIA-608 samples in SEI NAL units.
|
||||||
|
*/
|
||||||
public H265Reader(TrackOutput output, SeiReader seiReader) {
|
public H265Reader(TrackOutput output, SeiReader seiReader) {
|
||||||
super(output);
|
super(output);
|
||||||
this.seiReader = seiReader;
|
this.seiReader = seiReader;
|
||||||
|
|
@ -130,7 +134,7 @@ import java.util.Collections;
|
||||||
// Indicate the end of the previous NAL unit. If the length to the start of the next unit
|
// Indicate the end of the previous NAL unit. If the length to the start of the next unit
|
||||||
// is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes
|
// is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes
|
||||||
// when notifying that the unit has ended.
|
// when notifying that the unit has ended.
|
||||||
nalUnitEnd(absolutePosition, bytesWrittenPastPosition,
|
endNalUnit(absolutePosition, bytesWrittenPastPosition,
|
||||||
lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs);
|
lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs);
|
||||||
// Indicate the start of the next NAL unit.
|
// Indicate the start of the next NAL unit.
|
||||||
startNalUnit(absolutePosition, bytesWrittenPastPosition, nalUnitType, pesTimeUs);
|
startNalUnit(absolutePosition, bytesWrittenPastPosition, nalUnitType, pesTimeUs);
|
||||||
|
|
@ -168,7 +172,7 @@ import java.util.Collections;
|
||||||
suffixSei.appendToNalUnit(dataArray, offset, limit);
|
suffixSei.appendToNalUnit(dataArray, offset, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void nalUnitEnd(long position, int offset, int discardPadding, long pesTimeUs) {
|
private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {
|
||||||
if (hasOutputFormat) {
|
if (hasOutputFormat) {
|
||||||
sampleReader.endNalUnit(position, offset);
|
sampleReader.endNalUnit(position, offset);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -218,10 +222,10 @@ import java.util.Collections;
|
||||||
bitArray.skipBits(8); // general_level_idc
|
bitArray.skipBits(8); // general_level_idc
|
||||||
int toSkip = 0;
|
int toSkip = 0;
|
||||||
for (int i = 0; i < maxSubLayersMinus1; i++) {
|
for (int i = 0; i < maxSubLayersMinus1; i++) {
|
||||||
if (bitArray.readBits(1) == 1) { // sub_layer_profile_present_flag[i]
|
if (bitArray.readBit()) { // sub_layer_profile_present_flag[i]
|
||||||
toSkip += 89;
|
toSkip += 89;
|
||||||
}
|
}
|
||||||
if (bitArray.readBits(1) == 1) { // sub_layer_level_present_flag[i]
|
if (bitArray.readBit()) { // sub_layer_level_present_flag[i]
|
||||||
toSkip += 8;
|
toSkip += 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -309,7 +313,9 @@ import java.util.Collections;
|
||||||
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio);
|
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Skips scaling_list_data(). See H.265/HEVC (2014) 7.3.4. */
|
/**
|
||||||
|
* Skips scaling_list_data(). See H.265/HEVC (2014) 7.3.4.
|
||||||
|
*/
|
||||||
private static void skipScalingList(ParsableBitArray bitArray) {
|
private static void skipScalingList(ParsableBitArray bitArray) {
|
||||||
for (int sizeId = 0; sizeId < 4; sizeId++) {
|
for (int sizeId = 0; sizeId < 4; sizeId++) {
|
||||||
for (int matrixId = 0; matrixId < 6; matrixId += sizeId == 3 ? 3 : 1) {
|
for (int matrixId = 0; matrixId < 6; matrixId += sizeId == 3 ? 3 : 1) {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ public final class TsExtractor implements Extractor {
|
||||||
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1;
|
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1;
|
||||||
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2;
|
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2;
|
||||||
public static final int WORKAROUND_IGNORE_H264_STREAM = 4;
|
public static final int WORKAROUND_IGNORE_H264_STREAM = 4;
|
||||||
|
public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8;
|
||||||
|
|
||||||
private static final String TAG = "TsExtractor";
|
private static final String TAG = "TsExtractor";
|
||||||
|
|
||||||
|
|
@ -362,7 +363,8 @@ public final class TsExtractor implements Extractor {
|
||||||
pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 ? null
|
pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 ? null
|
||||||
: new H264Reader(output.track(TS_STREAM_TYPE_H264),
|
: new H264Reader(output.track(TS_STREAM_TYPE_H264),
|
||||||
new SeiReader(output.track(TS_STREAM_TYPE_EIA608)),
|
new SeiReader(output.track(TS_STREAM_TYPE_EIA608)),
|
||||||
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0);
|
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0,
|
||||||
|
(workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0);
|
||||||
break;
|
break;
|
||||||
case TS_STREAM_TYPE_H265:
|
case TS_STREAM_TYPE_H265:
|
||||||
pesPayloadReader = new H265Reader(output.track(TS_STREAM_TYPE_H265),
|
pesPayloadReader = new H265Reader(output.track(TS_STREAM_TYPE_H265),
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.util;
|
package com.google.android.exoplayer.util;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -26,23 +25,6 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public final class CodecSpecificDataUtil {
|
public final class CodecSpecificDataUtil {
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds data parsed from a sequence parameter set NAL unit.
|
|
||||||
*/
|
|
||||||
public static final class SpsData {
|
|
||||||
|
|
||||||
public final int width;
|
|
||||||
public final int height;
|
|
||||||
public final float pixelWidthAspectRatio;
|
|
||||||
|
|
||||||
public SpsData(int width, int height, float pixelWidthAspectRatio) {
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.pixelWidthAspectRatio = pixelWidthAspectRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
|
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
|
||||||
|
|
||||||
private static final int AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY = 0xF;
|
private static final int AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY = 0xF;
|
||||||
|
|
@ -94,8 +76,6 @@ public final class CodecSpecificDataUtil {
|
||||||
// Parametric Stereo.
|
// Parametric Stereo.
|
||||||
private static final int AUDIO_OBJECT_TYPE_PS = 29;
|
private static final int AUDIO_OBJECT_TYPE_PS = 29;
|
||||||
|
|
||||||
private static final String TAG = "CodecSpecificDataUtil";
|
|
||||||
|
|
||||||
private CodecSpecificDataUtil() {}
|
private CodecSpecificDataUtil() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -267,121 +247,4 @@ public final class CodecSpecificDataUtil {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an SPS NAL unit.
|
|
||||||
*
|
|
||||||
* @param bitArray A {@link ParsableBitArray} containing the SPS data. The position must to set
|
|
||||||
* to the start of the data (i.e. the first bit of the profile_idc field).
|
|
||||||
* @return A parsed representation of the SPS data.
|
|
||||||
*/
|
|
||||||
public static SpsData parseSpsNalUnit(ParsableBitArray bitArray) {
|
|
||||||
int profileIdc = bitArray.readBits(8);
|
|
||||||
bitArray.skipBits(16); // constraint bits (6), reserved (2) and level_idc (8)
|
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // seq_parameter_set_id
|
|
||||||
|
|
||||||
int chromaFormatIdc = 1; // Default is 4:2:0
|
|
||||||
if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244
|
|
||||||
|| profileIdc == 44 || profileIdc == 83 || profileIdc == 86 || profileIdc == 118
|
|
||||||
|| profileIdc == 128 || profileIdc == 138) {
|
|
||||||
chromaFormatIdc = bitArray.readUnsignedExpGolombCodedInt();
|
|
||||||
if (chromaFormatIdc == 3) {
|
|
||||||
bitArray.skipBits(1); // separate_colour_plane_flag
|
|
||||||
}
|
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8
|
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8
|
|
||||||
bitArray.skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
|
||||||
boolean seqScalingMatrixPresentFlag = bitArray.readBit();
|
|
||||||
if (seqScalingMatrixPresentFlag) {
|
|
||||||
int limit = (chromaFormatIdc != 3) ? 8 : 12;
|
|
||||||
for (int i = 0; i < limit; i++) {
|
|
||||||
boolean seqScalingListPresentFlag = bitArray.readBit();
|
|
||||||
if (seqScalingListPresentFlag) {
|
|
||||||
skipScalingList(bitArray, i < 6 ? 16 : 64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // log2_max_frame_num_minus4
|
|
||||||
long picOrderCntType = bitArray.readUnsignedExpGolombCodedInt();
|
|
||||||
if (picOrderCntType == 0) {
|
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // log2_max_pic_order_cnt_lsb_minus4
|
|
||||||
} else if (picOrderCntType == 1) {
|
|
||||||
bitArray.skipBits(1); // delta_pic_order_always_zero_flag
|
|
||||||
bitArray.readSignedExpGolombCodedInt(); // offset_for_non_ref_pic
|
|
||||||
bitArray.readSignedExpGolombCodedInt(); // offset_for_top_to_bottom_field
|
|
||||||
long numRefFramesInPicOrderCntCycle = bitArray.readUnsignedExpGolombCodedInt();
|
|
||||||
for (int i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // offset_for_ref_frame[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // max_num_ref_frames
|
|
||||||
bitArray.skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
|
||||||
|
|
||||||
int picWidthInMbs = bitArray.readUnsignedExpGolombCodedInt() + 1;
|
|
||||||
int picHeightInMapUnits = bitArray.readUnsignedExpGolombCodedInt() + 1;
|
|
||||||
boolean frameMbsOnlyFlag = bitArray.readBit();
|
|
||||||
int frameHeightInMbs = (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits;
|
|
||||||
if (!frameMbsOnlyFlag) {
|
|
||||||
bitArray.skipBits(1); // mb_adaptive_frame_field_flag
|
|
||||||
}
|
|
||||||
|
|
||||||
bitArray.skipBits(1); // direct_8x8_inference_flag
|
|
||||||
int frameWidth = picWidthInMbs * 16;
|
|
||||||
int frameHeight = frameHeightInMbs * 16;
|
|
||||||
boolean frameCroppingFlag = bitArray.readBit();
|
|
||||||
if (frameCroppingFlag) {
|
|
||||||
int frameCropLeftOffset = bitArray.readUnsignedExpGolombCodedInt();
|
|
||||||
int frameCropRightOffset = bitArray.readUnsignedExpGolombCodedInt();
|
|
||||||
int frameCropTopOffset = bitArray.readUnsignedExpGolombCodedInt();
|
|
||||||
int frameCropBottomOffset = bitArray.readUnsignedExpGolombCodedInt();
|
|
||||||
int cropUnitX, cropUnitY;
|
|
||||||
if (chromaFormatIdc == 0) {
|
|
||||||
cropUnitX = 1;
|
|
||||||
cropUnitY = 2 - (frameMbsOnlyFlag ? 1 : 0);
|
|
||||||
} else {
|
|
||||||
int subWidthC = (chromaFormatIdc == 3) ? 1 : 2;
|
|
||||||
int subHeightC = (chromaFormatIdc == 1) ? 2 : 1;
|
|
||||||
cropUnitX = subWidthC;
|
|
||||||
cropUnitY = subHeightC * (2 - (frameMbsOnlyFlag ? 1 : 0));
|
|
||||||
}
|
|
||||||
frameWidth -= (frameCropLeftOffset + frameCropRightOffset) * cropUnitX;
|
|
||||||
frameHeight -= (frameCropTopOffset + frameCropBottomOffset) * cropUnitY;
|
|
||||||
}
|
|
||||||
|
|
||||||
float pixelWidthHeightRatio = 1;
|
|
||||||
boolean vuiParametersPresentFlag = bitArray.readBit();
|
|
||||||
if (vuiParametersPresentFlag) {
|
|
||||||
boolean aspectRatioInfoPresentFlag = bitArray.readBit();
|
|
||||||
if (aspectRatioInfoPresentFlag) {
|
|
||||||
int aspectRatioIdc = bitArray.readBits(8);
|
|
||||||
if (aspectRatioIdc == NalUnitUtil.EXTENDED_SAR) {
|
|
||||||
int sarWidth = bitArray.readBits(16);
|
|
||||||
int sarHeight = bitArray.readBits(16);
|
|
||||||
if (sarWidth != 0 && sarHeight != 0) {
|
|
||||||
pixelWidthHeightRatio = (float) sarWidth / sarHeight;
|
|
||||||
}
|
|
||||||
} else if (aspectRatioIdc < NalUnitUtil.ASPECT_RATIO_IDC_VALUES.length) {
|
|
||||||
pixelWidthHeightRatio = NalUnitUtil.ASPECT_RATIO_IDC_VALUES[aspectRatioIdc];
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Unexpected aspect_ratio_idc value: " + aspectRatioIdc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SpsData(frameWidth, frameHeight, pixelWidthHeightRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void skipScalingList(ParsableBitArray bitArray, int size) {
|
|
||||||
int lastScale = 8;
|
|
||||||
int nextScale = 8;
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
if (nextScale != 0) {
|
|
||||||
int deltaScale = bitArray.readSignedExpGolombCodedInt();
|
|
||||||
nextScale = (lastScale + deltaScale + 256) % 256;
|
|
||||||
}
|
|
||||||
lastScale = (nextScale == 0) ? lastScale : nextScale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.util;
|
package com.google.android.exoplayer.util;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
@ -23,6 +25,59 @@ import java.util.Arrays;
|
||||||
*/
|
*/
|
||||||
public final class NalUnitUtil {
|
public final class NalUnitUtil {
|
||||||
|
|
||||||
|
private static final String TAG = "NalUnitUtil";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds data parsed from a sequence parameter set NAL unit.
|
||||||
|
*/
|
||||||
|
public static final class SpsData {
|
||||||
|
|
||||||
|
public final int seqParameterSetId;
|
||||||
|
public final int width;
|
||||||
|
public final int height;
|
||||||
|
public final float pixelWidthAspectRatio;
|
||||||
|
public final boolean separateColorPlaneFlag;
|
||||||
|
public final boolean frameMbsOnlyFlag;
|
||||||
|
public final int frameNumLength;
|
||||||
|
public final int picOrderCountType;
|
||||||
|
public final int picOrderCntLsbLength;
|
||||||
|
public final boolean deltaPicOrderAlwaysZeroFlag;
|
||||||
|
|
||||||
|
public SpsData(int seqParameterSetId, int width, int height, float pixelWidthAspectRatio,
|
||||||
|
boolean separateColorPlaneFlag, boolean frameMbsOnlyFlag, int frameNumLength,
|
||||||
|
int picOrderCountType, int picOrderCntLsbLength, boolean deltaPicOrderAlwaysZeroFlag) {
|
||||||
|
this.seqParameterSetId = seqParameterSetId;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.pixelWidthAspectRatio = pixelWidthAspectRatio;
|
||||||
|
this.separateColorPlaneFlag = separateColorPlaneFlag;
|
||||||
|
this.frameMbsOnlyFlag = frameMbsOnlyFlag;
|
||||||
|
this.frameNumLength = frameNumLength;
|
||||||
|
this.picOrderCountType = picOrderCountType;
|
||||||
|
this.picOrderCntLsbLength = picOrderCntLsbLength;
|
||||||
|
this.deltaPicOrderAlwaysZeroFlag = deltaPicOrderAlwaysZeroFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds data parsed from a picture parameter set NAL unit.
|
||||||
|
*/
|
||||||
|
public static final class PpsData {
|
||||||
|
|
||||||
|
public final int picParameterSetId;
|
||||||
|
public final int seqParameterSetId;
|
||||||
|
public final boolean bottomFieldPicOrderInFramePresentFlag;
|
||||||
|
|
||||||
|
public PpsData(int picParameterSetId, int seqParameterSetId,
|
||||||
|
boolean bottomFieldPicOrderInFramePresentFlag) {
|
||||||
|
this.picParameterSetId = picParameterSetId;
|
||||||
|
this.seqParameterSetId = seqParameterSetId;
|
||||||
|
this.bottomFieldPicOrderInFramePresentFlag = bottomFieldPicOrderInFramePresentFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/** Four initial bytes that must prefix NAL units for decoding. */
|
/** Four initial bytes that must prefix NAL units for decoding. */
|
||||||
public static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
|
public static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
|
||||||
|
|
||||||
|
|
@ -177,6 +232,134 @@ public final class NalUnitUtil {
|
||||||
return (data[offset + 3] & 0x7E) >> 1;
|
return (data[offset + 3] & 0x7E) >> 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an SPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection
|
||||||
|
* 7.3.2.1.1.
|
||||||
|
*
|
||||||
|
* @param data A {@link ParsableBitArray} containing the SPS data. The position must to set to the
|
||||||
|
* start of the data (i.e. the first bit of the profile_idc field).
|
||||||
|
* @return A parsed representation of the SPS data.
|
||||||
|
*/
|
||||||
|
public static SpsData parseSpsNalUnit(ParsableBitArray data) {
|
||||||
|
int profileIdc = data.readBits(8);
|
||||||
|
data.skipBits(16); // constraint bits (6), reserved (2) and level_idc (8)
|
||||||
|
int seqParameterSetId = data.readUnsignedExpGolombCodedInt();
|
||||||
|
|
||||||
|
int chromaFormatIdc = 1; // Default is 4:2:0
|
||||||
|
boolean separateColorPlaneFlag = false;
|
||||||
|
if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244
|
||||||
|
|| profileIdc == 44 || profileIdc == 83 || profileIdc == 86 || profileIdc == 118
|
||||||
|
|| profileIdc == 128 || profileIdc == 138) {
|
||||||
|
chromaFormatIdc = data.readUnsignedExpGolombCodedInt();
|
||||||
|
if (chromaFormatIdc == 3) {
|
||||||
|
separateColorPlaneFlag = data.readBit();
|
||||||
|
}
|
||||||
|
data.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8
|
||||||
|
data.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8
|
||||||
|
data.skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
||||||
|
boolean seqScalingMatrixPresentFlag = data.readBit();
|
||||||
|
if (seqScalingMatrixPresentFlag) {
|
||||||
|
int limit = (chromaFormatIdc != 3) ? 8 : 12;
|
||||||
|
for (int i = 0; i < limit; i++) {
|
||||||
|
boolean seqScalingListPresentFlag = data.readBit();
|
||||||
|
if (seqScalingListPresentFlag) {
|
||||||
|
skipScalingList(data, i < 6 ? 16 : 64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int frameNumLength = data.readUnsignedExpGolombCodedInt() + 4; // log2_max_frame_num_minus4 + 4
|
||||||
|
int picOrderCntType = data.readUnsignedExpGolombCodedInt();
|
||||||
|
int picOrderCntLsbLength = 0;
|
||||||
|
boolean deltaPicOrderAlwaysZeroFlag = false;
|
||||||
|
if (picOrderCntType == 0) {
|
||||||
|
// log2_max_pic_order_cnt_lsb_minus4 + 4
|
||||||
|
picOrderCntLsbLength = data.readUnsignedExpGolombCodedInt() + 4;
|
||||||
|
} else if (picOrderCntType == 1) {
|
||||||
|
deltaPicOrderAlwaysZeroFlag = data.readBit(); // delta_pic_order_always_zero_flag
|
||||||
|
data.readSignedExpGolombCodedInt(); // offset_for_non_ref_pic
|
||||||
|
data.readSignedExpGolombCodedInt(); // offset_for_top_to_bottom_field
|
||||||
|
long numRefFramesInPicOrderCntCycle = data.readUnsignedExpGolombCodedInt();
|
||||||
|
for (int i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
||||||
|
data.readUnsignedExpGolombCodedInt(); // offset_for_ref_frame[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.readUnsignedExpGolombCodedInt(); // max_num_ref_frames
|
||||||
|
data.skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
||||||
|
|
||||||
|
int picWidthInMbs = data.readUnsignedExpGolombCodedInt() + 1;
|
||||||
|
int picHeightInMapUnits = data.readUnsignedExpGolombCodedInt() + 1;
|
||||||
|
boolean frameMbsOnlyFlag = data.readBit();
|
||||||
|
int frameHeightInMbs = (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits;
|
||||||
|
if (!frameMbsOnlyFlag) {
|
||||||
|
data.skipBits(1); // mb_adaptive_frame_field_flag
|
||||||
|
}
|
||||||
|
|
||||||
|
data.skipBits(1); // direct_8x8_inference_flag
|
||||||
|
int frameWidth = picWidthInMbs * 16;
|
||||||
|
int frameHeight = frameHeightInMbs * 16;
|
||||||
|
boolean frameCroppingFlag = data.readBit();
|
||||||
|
if (frameCroppingFlag) {
|
||||||
|
int frameCropLeftOffset = data.readUnsignedExpGolombCodedInt();
|
||||||
|
int frameCropRightOffset = data.readUnsignedExpGolombCodedInt();
|
||||||
|
int frameCropTopOffset = data.readUnsignedExpGolombCodedInt();
|
||||||
|
int frameCropBottomOffset = data.readUnsignedExpGolombCodedInt();
|
||||||
|
int cropUnitX, cropUnitY;
|
||||||
|
if (chromaFormatIdc == 0) {
|
||||||
|
cropUnitX = 1;
|
||||||
|
cropUnitY = 2 - (frameMbsOnlyFlag ? 1 : 0);
|
||||||
|
} else {
|
||||||
|
int subWidthC = (chromaFormatIdc == 3) ? 1 : 2;
|
||||||
|
int subHeightC = (chromaFormatIdc == 1) ? 2 : 1;
|
||||||
|
cropUnitX = subWidthC;
|
||||||
|
cropUnitY = subHeightC * (2 - (frameMbsOnlyFlag ? 1 : 0));
|
||||||
|
}
|
||||||
|
frameWidth -= (frameCropLeftOffset + frameCropRightOffset) * cropUnitX;
|
||||||
|
frameHeight -= (frameCropTopOffset + frameCropBottomOffset) * cropUnitY;
|
||||||
|
}
|
||||||
|
|
||||||
|
float pixelWidthHeightRatio = 1;
|
||||||
|
boolean vuiParametersPresentFlag = data.readBit();
|
||||||
|
if (vuiParametersPresentFlag) {
|
||||||
|
boolean aspectRatioInfoPresentFlag = data.readBit();
|
||||||
|
if (aspectRatioInfoPresentFlag) {
|
||||||
|
int aspectRatioIdc = data.readBits(8);
|
||||||
|
if (aspectRatioIdc == NalUnitUtil.EXTENDED_SAR) {
|
||||||
|
int sarWidth = data.readBits(16);
|
||||||
|
int sarHeight = data.readBits(16);
|
||||||
|
if (sarWidth != 0 && sarHeight != 0) {
|
||||||
|
pixelWidthHeightRatio = (float) sarWidth / sarHeight;
|
||||||
|
}
|
||||||
|
} else if (aspectRatioIdc < NalUnitUtil.ASPECT_RATIO_IDC_VALUES.length) {
|
||||||
|
pixelWidthHeightRatio = NalUnitUtil.ASPECT_RATIO_IDC_VALUES[aspectRatioIdc];
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Unexpected aspect_ratio_idc value: " + aspectRatioIdc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SpsData(seqParameterSetId, frameWidth, frameHeight, pixelWidthHeightRatio,
|
||||||
|
separateColorPlaneFlag, frameMbsOnlyFlag, frameNumLength, picOrderCntType,
|
||||||
|
picOrderCntLsbLength, deltaPicOrderAlwaysZeroFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a PPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection
|
||||||
|
* 7.3.2.2.
|
||||||
|
*
|
||||||
|
* @param data A {@link ParsableBitArray} containing the PPS data. The position must to set to the
|
||||||
|
* start of the data (i.e. the first bit of the pic_parameter_set_id field).
|
||||||
|
* @return A parsed representation of the PPS data.
|
||||||
|
*/
|
||||||
|
public static PpsData parsePpsNalUnit(ParsableBitArray data) {
|
||||||
|
int picParameterSetId = data.readUnsignedExpGolombCodedInt();
|
||||||
|
int seqParameterSetId = data.readUnsignedExpGolombCodedInt();
|
||||||
|
data.skipBits(1); // entropy_coding_mode_flag
|
||||||
|
boolean bottomFieldPicOrderInFramePresentFlag = data.readBit();
|
||||||
|
return new PpsData(picParameterSetId, seqParameterSetId, bottomFieldPicOrderInFramePresentFlag);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the first NAL unit in {@code data}.
|
* Finds the first NAL unit in {@code data}.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -276,6 +459,18 @@ public final class NalUnitUtil {
|
||||||
return limit;
|
return limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void skipScalingList(ParsableBitArray bitArray, int size) {
|
||||||
|
int lastScale = 8;
|
||||||
|
int nextScale = 8;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
if (nextScale != 0) {
|
||||||
|
int deltaScale = bitArray.readSignedExpGolombCodedInt();
|
||||||
|
nextScale = (lastScale + deltaScale + 256) % 256;
|
||||||
|
}
|
||||||
|
lastScale = (nextScale == 0) ? lastScale : nextScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private NalUnitUtil() {
|
private NalUnitUtil() {
|
||||||
// Prevent instantiation.
|
// Prevent instantiation.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -178,12 +178,12 @@ public final class ParsableBitArray {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Peeks the length of an Exp-Golomb-coded integer (signed or unsigned) starting from the current
|
* Returns whether it is possible to read an Exp-Golomb-coded integer starting from the current
|
||||||
* offset, returning the length or -1 if the limit is reached.
|
* offset. The offset is not modified.
|
||||||
*
|
*
|
||||||
* @return The length of the Exp-Golob-coded integer, or -1.
|
* @return Whether it is possible to read an Exp-Golomb-coded integer.
|
||||||
*/
|
*/
|
||||||
public int peekExpGolombCodedNumLength() {
|
public boolean canReadExpGolombCodedNum() {
|
||||||
int initialByteOffset = byteOffset;
|
int initialByteOffset = byteOffset;
|
||||||
int initialBitOffset = bitOffset;
|
int initialBitOffset = bitOffset;
|
||||||
int leadingZeros = 0;
|
int leadingZeros = 0;
|
||||||
|
|
@ -193,7 +193,7 @@ public final class ParsableBitArray {
|
||||||
boolean hitLimit = byteOffset == byteLimit;
|
boolean hitLimit = byteOffset == byteLimit;
|
||||||
byteOffset = initialByteOffset;
|
byteOffset = initialByteOffset;
|
||||||
bitOffset = initialBitOffset;
|
bitOffset = initialBitOffset;
|
||||||
return hitLimit ? -1 : leadingZeros * 2 + 1;
|
return !hitLimit && bitsLeft() >= leadingZeros * 2 + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue