diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/VorbisBitArrayTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/VorbisBitArrayTest.java index 11238f74f8..17a0fd241e 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/VorbisBitArrayTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/VorbisBitArrayTest.java @@ -269,11 +269,11 @@ public final class VorbisBitArrayTest extends TestCase { assertEquals(10, bitArray.bitsLeft()); assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); - bitArray.readBit(); + bitArray.skipBits(1); assertEquals(9, bitArray.bitsLeft()); assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); - bitArray.readBits(1); + bitArray.skipBits(1); assertEquals(8, bitArray.bitsLeft()); assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagPayloadReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagPayloadReader.java index 5d68ed8fa3..49ab62f490 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagPayloadReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagPayloadReader.java @@ -20,7 +20,6 @@ import com.google.android.exoplayer.Format; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.extractor.TrackOutput; 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.NalUnitUtil; import com.google.android.exoplayer.util.ParsableBitArray; @@ -165,7 +164,7 @@ import java.util.List; ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0)); // Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte). spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1)); - CodecSpecificDataUtil.SpsData sps = CodecSpecificDataUtil.parseSpsNalUnit(spsDataBitArray); + NalUnitUtil.SpsData sps = NalUnitUtil.parseSpsNalUnit(spsDataBitArray); width = sps.width; height = sps.height; pixelWidthAspectRatio = sps.pixelWidthAspectRatio; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java index 6cabab1e02..514e9a7b6c 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java @@ -669,8 +669,7 @@ import java.util.List; ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0)); // Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte). spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1)); - pixelWidthAspectRatio = CodecSpecificDataUtil.parseSpsNalUnit(spsDataBitArray) - .pixelWidthAspectRatio; + pixelWidthAspectRatio = NalUnitUtil.parseSpsNalUnit(spsDataBitArray).pixelWidthAspectRatio; } return new AvcCData(initializationData, nalUnitLengthFieldLength, pixelWidthAspectRatio); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java index 70cc328fef..18cd2291f7 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java @@ -18,13 +18,14 @@ package com.google.android.exoplayer.extractor.ts; import com.google.android.exoplayer.C; import com.google.android.exoplayer.Format; 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.NalUnitUtil; +import com.google.android.exoplayer.util.NalUnitUtil.SpsData; import com.google.android.exoplayer.util.ParsableBitArray; import com.google.android.exoplayer.util.ParsableByteArray; +import android.util.SparseArray; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -34,15 +35,9 @@ import java.util.List; */ /* 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_SPS = 7; // Sequence 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. private boolean hasOutputFormat; @@ -50,29 +45,32 @@ import java.util.List; // State that should be reset on seek. private final SeiReader seiReader; private final boolean[] prefixFlags; - private final IfrParserBuffer ifrParserBuffer; + private final SampleReader sampleReader; private final NalUnitTargetBuffer sps; private final NalUnitTargetBuffer pps; private final NalUnitTargetBuffer sei; - private boolean foundFirstSample; private long totalBytesWritten; // Per packet state that gets reset at the start of each packet. 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. 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); this.seiReader = seiReader; prefixFlags = new boolean[3]; - ifrParserBuffer = allowNonIdrKeyframes ? new IfrParserBuffer() : null; + sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits); sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128); pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128); sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128); @@ -85,10 +83,7 @@ import java.util.List; sps.reset(); pps.reset(); sei.reset(); - if (ifrParserBuffer != null) { - ifrParserBuffer.reset(); - } - foundFirstSample = false; + sampleReader.reset(); totalBytesWritten = 0; } @@ -114,7 +109,7 @@ import java.util.List; if (nalUnitOffset == limit) { // 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; } @@ -125,42 +120,17 @@ import java.util.List; // It may be negative if the NAL unit started in the previously consumed data. int lengthToNalUnit = nalUnitOffset - offset; if (lengthToNalUnit > 0) { - feedNalUnitTargetBuffersData(dataArray, offset, nalUnitOffset); + nalUnitData(dataArray, offset, nalUnitOffset); } - - switch (nalUnitType) { - 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; - } - + int bytesWrittenPastPosition = limit - nalUnitOffset; + long absolutePosition = totalBytesWritten - bytesWrittenPastPosition; // 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 // 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. - feedNalUnitTargetBuffersStart(nalUnitType); + startNalUnit(absolutePosition, nalUnitType, pesTimeUs); // Continue scanning the data. offset = nalUnitOffset + 3; } @@ -172,99 +142,152 @@ import java.util.List; // Do nothing. } - private void feedNalUnitTargetBuffersStart(int nalUnitType) { - if (ifrParserBuffer != null) { - ifrParserBuffer.startNalUnit(nalUnitType); - } - if (!hasOutputFormat) { + private void startNalUnit(long position, int nalUnitType, long pesTimeUs) { + if (!hasOutputFormat || sampleReader.needsSpsPps()) { sps.startNalUnit(nalUnitType); pps.startNalUnit(nalUnitType); } sei.startNalUnit(nalUnitType); + sampleReader.startNalUnit(position, nalUnitType, pesTimeUs); } - private void feedNalUnitTargetBuffersData(byte[] dataArray, int offset, int limit) { - if (ifrParserBuffer != null) { - ifrParserBuffer.appendToNalUnit(dataArray, offset, limit); - } - if (!hasOutputFormat) { + private void nalUnitData(byte[] dataArray, int offset, int limit) { + if (!hasOutputFormat || sampleReader.needsSpsPps()) { sps.appendToNalUnit(dataArray, offset, limit); pps.appendToNalUnit(dataArray, offset, limit); } sei.appendToNalUnit(dataArray, offset, limit); + sampleReader.appendToNalUnit(dataArray, offset, limit); } - private void feedNalUnitTargetEnd(long pesTimeUs, int discardPadding) { - sps.endNalUnit(discardPadding); - pps.endNalUnit(discardPadding); + private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) { + if (!hasOutputFormat || sampleReader.needsSpsPps()) { + sps.endNalUnit(discardPadding); + pps.endNalUnit(discardPadding); + if (!hasOutputFormat) { + if (sps.isCompleted() && pps.isCompleted()) { + List 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)) { int unescapedLength = NalUnitUtil.unescapeStream(sei.nalData, sei.nalLength); seiWrapper.reset(sei.nalData, unescapedLength); seiWrapper.setPosition(4); // NAL prefix and nal_unit() header. seiReader.consume(pesTimeUs, seiWrapper); } + sampleReader.endNalUnit(position, offset); } - private static Format parseMediaFormat(NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) { - List initializationData = new ArrayList<>(); - initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength)); - 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); + private static ParsableBitArray unescape(NalUnitTargetBuffer buffer) { + int length = NalUnitUtil.unescapeStream(buffer.nalData, buffer.nalLength); + ParsableBitArray bitArray = new ParsableBitArray(buffer.nalData, length); bitArray.skipBits(32); // NAL header - SpsData parsedSpsData = CodecSpecificDataUtil.parseSpsNalUnit(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); + return bitArray; } /** - * 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 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 int ifrLength; + private final TrackOutput output; + private final boolean allowNonIdrKeyframes; + private final boolean detectAccessUnits; + private final ParsableBitArray scratch; + private final SparseArray sps; + private final SparseArray 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 int sliceType; + private long nalUnitTimeUs; + private SliceHeaderData previousSliceHeader; + private SliceHeaderData sliceHeader; - public IfrParserBuffer() { - ifrData = new byte[DEFAULT_BUFFER_SIZE]; - scratchSliceType = new ParsableBitArray(ifrData); + // Per sample state that gets reset at the start of each sample. + private boolean readingSample; + 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(); } - /** - * Resets the buffer, clearing any data that it holds. - */ + public boolean needsSpsPps() { + 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() { isFilling = false; - ifrLength = 0; - sliceType = NOT_SET; + readingSample = false; + sliceHeader.clear(); } - /** - * True if enough data was added to the buffer that the slice type was determined. - */ - public boolean isCompleted() { - return sliceType != NOT_SET; - } - - /** - * Invoked to indicate that a NAL unit has started, and if it is an IFR then the buffer will - * start. - */ - public void startNalUnit(int nalUnitType) { - if (nalUnitType == NAL_UNIT_TYPE_IFR) { - reset(); + public void startNalUnit(long position, int type, long pesTimeUs) { + nalUnitType = type; + nalUnitTimeUs = pesTimeUs; + nalUnitStartPosition = position; + 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))) { + // Store the previous header and prepare to populate the new one. + SliceHeaderData newSliceHeader = previousSliceHeader; + previousSliceHeader = sliceHeader; + sliceHeader = newSliceHeader; + sliceHeader.clear(); + bufferLength = 0; isFilling = true; } } @@ -281,38 +304,214 @@ import java.util.List; return; } int readLength = limit - offset; - if (ifrData.length < ifrLength + readLength) { - ifrData = Arrays.copyOf(ifrData, (ifrLength + readLength) * 2); + if (buffer.length < bufferLength + readLength) { + buffer = Arrays.copyOf(buffer, (bufferLength + readLength) * 2); } - System.arraycopy(data, offset, ifrData, ifrLength, readLength); - ifrLength += readLength; + System.arraycopy(data, offset, buffer, bufferLength, readLength); + bufferLength += readLength; - scratchSliceType.reset(ifrData, ifrLength); - scratchSliceType.skipBits(8); - // first_mb_in_slice - int len = scratchSliceType.peekExpGolombCodedNumLength(); - if ((len == -1) || (len > scratchSliceType.bitsLeft())) { - // Not enough yet + scratch.reset(buffer, bufferLength); + if (scratch.bitsLeft() < 8) { return; } + scratch.skipBits(1); // forbidden_zero_bit + int nalRefIdc = scratch.readBits(2); + scratch.skipBits(5); // nal_unit_type - scratchSliceType.skipBits(len); - // slice_type - len = scratchSliceType.peekExpGolombCodedNumLength(); - if ((len == -1) || (len > scratchSliceType.bitsLeft())) { - // Not enough yet + // Read the slice header using the syntax defined in ITU-T Recommendation H.264 (2013) + // subsection 7.3.3. + if (!scratch.canReadExpGolombCodedNum()) { 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; } - /** - * @return the slice type of the IFR. - */ - public int getSliceType() { - return sliceType; + public void endNalUnit(long position, int offset) { + if (nalUnitType == NAL_UNIT_TYPE_AUD + || (detectAccessUnits && sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) { + // If the NAL unit ending is the start of a new sample, output the previous one. + 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)); + } + } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java index 79ad60ad1d..92f8ed3745 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java @@ -64,6 +64,10 @@ import java.util.Collections; // Scratch variables to avoid allocations. 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) { super(output); 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 // is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes // when notifying that the unit has ended. - nalUnitEnd(absolutePosition, bytesWrittenPastPosition, + endNalUnit(absolutePosition, bytesWrittenPastPosition, lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs); // Indicate the start of the next NAL unit. startNalUnit(absolutePosition, bytesWrittenPastPosition, nalUnitType, pesTimeUs); @@ -168,7 +172,7 @@ import java.util.Collections; 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) { sampleReader.endNalUnit(position, offset); } else { @@ -218,10 +222,10 @@ import java.util.Collections; bitArray.skipBits(8); // general_level_idc int toSkip = 0; 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; } - if (bitArray.readBits(1) == 1) { // sub_layer_level_present_flag[i] + if (bitArray.readBit()) { // sub_layer_level_present_flag[i] toSkip += 8; } } @@ -309,7 +313,9 @@ import java.util.Collections; 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) { for (int sizeId = 0; sizeId < 4; sizeId++) { for (int matrixId = 0; matrixId < 6; matrixId += sizeId == 3 ? 3 : 1) { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java index 60ff58d43b..c917952f2a 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java @@ -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_IGNORE_AAC_STREAM = 2; public static final int WORKAROUND_IGNORE_H264_STREAM = 4; + public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8; private static final String TAG = "TsExtractor"; @@ -362,7 +363,8 @@ public final class TsExtractor implements Extractor { pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 ? null : new H264Reader(output.track(TS_STREAM_TYPE_H264), 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; case TS_STREAM_TYPE_H265: pesPayloadReader = new H265Reader(output.track(TS_STREAM_TYPE_H265), diff --git a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java index 70a80d7b11..6ba16f0ee1 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer.util; -import android.util.Log; import android.util.Pair; import java.util.ArrayList; @@ -26,23 +25,6 @@ import java.util.List; */ 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 int AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY = 0xF; @@ -94,8 +76,6 @@ public final class CodecSpecificDataUtil { // Parametric Stereo. private static final int AUDIO_OBJECT_TYPE_PS = 29; - private static final String TAG = "CodecSpecificDataUtil"; - private CodecSpecificDataUtil() {} /** @@ -267,121 +247,4 @@ public final class CodecSpecificDataUtil { 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; - } - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java b/library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java index 7b4caae880..00a9f27ade 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer.util; +import android.util.Log; + import java.nio.ByteBuffer; import java.util.Arrays; @@ -23,6 +25,59 @@ import java.util.Arrays; */ 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. */ 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; } + /** + * 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}. *

@@ -276,6 +459,18 @@ public final class NalUnitUtil { 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() { // Prevent instantiation. } diff --git a/library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java b/library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java index ff47655598..443bfee406 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java +++ b/library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java @@ -178,12 +178,12 @@ public final class ParsableBitArray { } /** - * Peeks the length of an Exp-Golomb-coded integer (signed or unsigned) starting from the current - * offset, returning the length or -1 if the limit is reached. + * Returns whether it is possible to read an Exp-Golomb-coded integer starting from the current + * 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 initialBitOffset = bitOffset; int leadingZeros = 0; @@ -193,7 +193,7 @@ public final class ParsableBitArray { boolean hitLimit = byteOffset == byteLimit; byteOffset = initialByteOffset; bitOffset = initialBitOffset; - return hitLimit ? -1 : leadingZeros * 2 + 1; + return !hitLimit && bitsLeft() >= leadingZeros * 2 + 1; } /**