Add ParsableNalUnitBitArray which unescapes while reading.

This replaces calls to unescape except for SEI unescaping.

Use the new ParsableNalUnitBitArray for reading the slice header in HLS
access unit detection and slice_type reading.

Unescape the SPS before parsing in FLV and MP4. Before this change it was
parsed in its original (escaped) form.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=118777869
This commit is contained in:
andrewlewis 2016-04-01 07:33:59 -07:00 committed by Oliver Woodman
parent e4d815d164
commit 3208e20673
9 changed files with 424 additions and 116 deletions

View File

@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer.util; package com.google.android.exoplayer.util;
import static com.google.android.exoplayer.testutil.TestUtil.createByteArray;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -27,6 +29,10 @@ public class NalUnitUtilTest extends TestCase {
private static final int TEST_PARTIAL_NAL_POSITION = 4; private static final int TEST_PARTIAL_NAL_POSITION = 4;
private static final int TEST_NAL_POSITION = 10; private static final int TEST_NAL_POSITION = 10;
private static final byte[] SPS_TEST_DATA = createByteArray(0x00, 0x00, 0x01, 0x67, 0x4D, 0x40,
0x16, 0xEC, 0xA0, 0x50, 0x17, 0xFC, 0xB8, 0x08, 0x80, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00,
0x00, 0x0F, 0x47, 0x8B, 0x16, 0xCB);
private static final int SPS_TEST_DATA_OFFSET = 3;
public void testFindNalUnit() { public void testFindNalUnit() {
byte[] data = buildTestData(); byte[] data = buildTestData();
@ -111,6 +117,21 @@ public class NalUnitUtilTest extends TestCase {
assertPrefixFlagsCleared(prefixFlags); assertPrefixFlagsCleared(prefixFlags);
} }
public void testParseSpsNalUnit() {
NalUnitUtil.SpsData data = NalUnitUtil.parseSpsNalUnit(SPS_TEST_DATA, SPS_TEST_DATA_OFFSET,
SPS_TEST_DATA.length);
assertEquals(640, data.width);
assertEquals(360, data.height);
assertFalse(data.deltaPicOrderAlwaysZeroFlag);
assertTrue(data.frameMbsOnlyFlag);
assertEquals(4, data.frameNumLength);
assertEquals(6, data.picOrderCntLsbLength);
assertEquals(0, data.seqParameterSetId);
assertEquals(1.0f, data.pixelWidthAspectRatio);
assertEquals(0, data.picOrderCountType);
assertFalse(data.separateColorPlaneFlag);
}
public void testUnescapeDoesNotModifyBuffersWithoutStartCodes() { public void testUnescapeDoesNotModifyBuffersWithoutStartCodes() {
assertUnescapeDoesNotModify(""); assertUnescapeDoesNotModify("");
assertUnescapeDoesNotModify("0000"); assertUnescapeDoesNotModify("0000");

View File

@ -0,0 +1,114 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.util;
import static com.google.android.exoplayer.testutil.TestUtil.createByteArray;
import junit.framework.TestCase;
/**
* Tests for {@link ParsableNalUnitBitArray}.
*/
public final class ParsableNalUnitBitArrayTest extends TestCase {
private static final byte[] NO_ESCAPING_TEST_DATA = createByteArray(0, 3, 0, 1, 3, 0, 0);
private static final byte[] ALL_ESCAPING_TEST_DATA = createByteArray(0, 0, 3, 0, 0, 3, 0, 0, 3);
private static final byte[] MIX_TEST_DATA = createByteArray(255, 0, 0, 3, 255, 0, 0, 127);
public void testReadNoEscaping() {
ParsableNalUnitBitArray array =
new ParsableNalUnitBitArray(NO_ESCAPING_TEST_DATA, 0, NO_ESCAPING_TEST_DATA.length);
assertEquals(0x000300, array.readBits(24));
assertEquals(0, array.readBits(7));
assertTrue(array.readBit());
assertEquals(0x030000, array.readBits(24));
assertFalse(array.canReadBits(1));
assertFalse(array.canReadBits(8));
}
public void testReadNoEscapingTruncated() {
ParsableNalUnitBitArray array = new ParsableNalUnitBitArray(NO_ESCAPING_TEST_DATA, 0, 4);
assertTrue(array.canReadBits(32));
array.skipBits(32);
assertFalse(array.canReadBits(1));
try {
array.readBit();
fail();
} catch (Exception e) {
// Expected.
}
}
public void testReadAllEscaping() {
ParsableNalUnitBitArray array =
new ParsableNalUnitBitArray(ALL_ESCAPING_TEST_DATA, 0, ALL_ESCAPING_TEST_DATA.length);
assertTrue(array.canReadBits(48));
assertFalse(array.canReadBits(49));
assertEquals(0, array.readBits(15));
assertFalse(array.readBit());
assertEquals(0, array.readBits(17));
assertEquals(0, array.readBits(15));
}
public void testReadMix() {
ParsableNalUnitBitArray array =
new ParsableNalUnitBitArray(MIX_TEST_DATA, 0, MIX_TEST_DATA.length);
assertTrue(array.canReadBits(56));
assertFalse(array.canReadBits(57));
assertEquals(127, array.readBits(7));
assertEquals(2, array.readBits(2));
assertEquals(3, array.readBits(17));
assertEquals(126, array.readBits(7));
assertEquals(127, array.readBits(23));
assertFalse(array.canReadBits(1));
}
public void testReadExpGolomb() {
ParsableNalUnitBitArray array = new ParsableNalUnitBitArray(createByteArray(0x9E), 0, 1);
assertTrue(array.canReadExpGolombCodedNum());
assertEquals(0, array.readUnsignedExpGolombCodedInt());
assertEquals(6, array.readUnsignedExpGolombCodedInt());
assertEquals(0, array.readUnsignedExpGolombCodedInt());
assertFalse(array.canReadExpGolombCodedNum());
try {
array.readUnsignedExpGolombCodedInt();
fail();
} catch (Exception e) {
// Expected.
}
}
public void testReadExpGolombWithEscaping() {
ParsableNalUnitBitArray array =
new ParsableNalUnitBitArray(createByteArray(0, 0, 3, 128, 0), 0, 5);
assertFalse(array.canReadExpGolombCodedNum());
array.skipBits(1);
assertTrue(array.canReadExpGolombCodedNum());
assertEquals(32767, array.readUnsignedExpGolombCodedInt());
assertFalse(array.canReadBits(1));
}
public void testReset() {
ParsableNalUnitBitArray array = new ParsableNalUnitBitArray(createByteArray(0, 0), 0, 2);
assertFalse(array.canReadExpGolombCodedNum());
assertTrue(array.canReadBits(16));
assertFalse(array.canReadBits(17));
array.reset(createByteArray(0, 0, 3, 0), 0, 4);
assertTrue(array.canReadBits(24));
assertFalse(array.canReadBits(25));
}
}

View File

@ -22,7 +22,6 @@ 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.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.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import java.util.ArrayList; import java.util.ArrayList;
@ -160,14 +159,12 @@ import java.util.List;
int width = Format.NO_VALUE; int width = Format.NO_VALUE;
int height = Format.NO_VALUE; int height = Format.NO_VALUE;
if (numSequenceParameterSets > 0) { if (numSequenceParameterSets > 0) {
// Parse the first sequence parameter set to obtain pixelWidthAspectRatio. byte[] sps = initializationData.get(0);
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0)); NalUnitUtil.SpsData spsData =
// Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte). NalUnitUtil.parseSpsNalUnit(sps, nalUnitLengthFieldLength, sps.length);
spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1)); width = spsData.width;
NalUnitUtil.SpsData sps = NalUnitUtil.parseSpsNalUnit(spsDataBitArray); height = spsData.height;
width = sps.width; pixelWidthAspectRatio = spsData.pixelWidthAspectRatio;
height = sps.height;
pixelWidthAspectRatio = sps.pixelWidthAspectRatio;
} }
return new AvcSequenceHeaderData(initializationData, nalUnitLengthFieldLength, return new AvcSequenceHeaderData(initializationData, nalUnitLengthFieldLength,

View File

@ -24,7 +24,6 @@ import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.CodecSpecificDataUtil; import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.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.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
@ -666,10 +665,9 @@ import java.util.List;
if (numSequenceParameterSets > 0) { if (numSequenceParameterSets > 0) {
// Parse the first sequence parameter set to obtain pixelWidthAspectRatio. // Parse the first sequence parameter set to obtain pixelWidthAspectRatio.
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0)); byte[] sps = initializationData.get(0);
// Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte). pixelWidthAspectRatio = NalUnitUtil
spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1)); .parseSpsNalUnit(sps, nalUnitLengthFieldLength, sps.length).pixelWidthAspectRatio;
pixelWidthAspectRatio = NalUnitUtil.parseSpsNalUnit(spsDataBitArray).pixelWidthAspectRatio;
} }
return new AvcCData(initializationData, nalUnitLengthFieldLength, pixelWidthAspectRatio); return new AvcCData(initializationData, nalUnitLengthFieldLength, pixelWidthAspectRatio);

View File

@ -21,8 +21,8 @@ import com.google.android.exoplayer.extractor.TrackOutput;
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.NalUnitUtil.SpsData;
import com.google.android.exoplayer.util.ParsableBitArray;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.ParsableNalUnitBitArray;
import android.util.SparseArray; import android.util.SparseArray;
@ -169,8 +169,8 @@ import java.util.List;
List<byte[]> initializationData = new ArrayList<>(); List<byte[]> initializationData = new ArrayList<>();
initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength)); initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength));
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength)); initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(unescape(sps)); NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(unescape(pps)); NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);
output.format(Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, Format.NO_VALUE, 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.width, spsData.height, Format.NO_VALUE, initializationData,
Format.NO_VALUE, spsData.pixelWidthAspectRatio)); Format.NO_VALUE, spsData.pixelWidthAspectRatio));
@ -181,11 +181,11 @@ import java.util.List;
pps.reset(); pps.reset();
} }
} else if (sps.isCompleted()) { } else if (sps.isCompleted()) {
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(unescape(sps)); NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
sampleReader.putSps(spsData); sampleReader.putSps(spsData);
sps.reset(); sps.reset();
} else if (pps.isCompleted()) { } else if (pps.isCompleted()) {
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(unescape(pps)); NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);
sampleReader.putPps(ppsData); sampleReader.putPps(ppsData);
pps.reset(); pps.reset();
} }
@ -199,13 +199,6 @@ import java.util.List;
sampleReader.endNalUnit(position, offset); sampleReader.endNalUnit(position, offset);
} }
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
return bitArray;
}
/** /**
* Consumes a stream of NAL units and outputs samples. * Consumes a stream of NAL units and outputs samples.
*/ */
@ -221,9 +214,9 @@ import java.util.List;
private final TrackOutput output; private final TrackOutput output;
private final boolean allowNonIdrKeyframes; private final boolean allowNonIdrKeyframes;
private final boolean detectAccessUnits; private final boolean detectAccessUnits;
private final ParsableBitArray scratch;
private final SparseArray<NalUnitUtil.SpsData> sps; private final SparseArray<NalUnitUtil.SpsData> sps;
private final SparseArray<NalUnitUtil.PpsData> pps; private final SparseArray<NalUnitUtil.PpsData> pps;
private final ParsableNalUnitBitArray bitArray;
private byte[] buffer; private byte[] buffer;
private int bufferLength; private int bufferLength;
@ -251,8 +244,8 @@ import java.util.List;
pps = new SparseArray<>(); pps = new SparseArray<>();
previousSliceHeader = new SliceHeaderData(); previousSliceHeader = new SliceHeaderData();
sliceHeader = new SliceHeaderData(); sliceHeader = new SliceHeaderData();
scratch = new ParsableBitArray();
buffer = new byte[DEFAULT_BUFFER_SIZE]; buffer = new byte[DEFAULT_BUFFER_SIZE];
bitArray = new ParsableNalUnitBitArray(buffer, 0, 0);
reset(); reset();
} }
@ -310,34 +303,34 @@ import java.util.List;
System.arraycopy(data, offset, buffer, bufferLength, readLength); System.arraycopy(data, offset, buffer, bufferLength, readLength);
bufferLength += readLength; bufferLength += readLength;
scratch.reset(buffer, bufferLength); bitArray.reset(buffer, 0, bufferLength);
if (scratch.bitsLeft() < 8) { if (!bitArray.canReadBits(8)) {
return; return;
} }
scratch.skipBits(1); // forbidden_zero_bit bitArray.skipBits(1); // forbidden_zero_bit
int nalRefIdc = scratch.readBits(2); int nalRefIdc = bitArray.readBits(2);
scratch.skipBits(5); // nal_unit_type bitArray.skipBits(5); // nal_unit_type
// Read the slice header using the syntax defined in ITU-T Recommendation H.264 (2013) // Read the slice header using the syntax defined in ITU-T Recommendation H.264 (2013)
// subsection 7.3.3. // subsection 7.3.3.
if (!scratch.canReadExpGolombCodedNum()) { if (!bitArray.canReadExpGolombCodedNum()) {
return; return;
} }
scratch.readUnsignedExpGolombCodedInt(); // first_mb_in_slice bitArray.readUnsignedExpGolombCodedInt(); // first_mb_in_slice
if (!scratch.canReadExpGolombCodedNum()) { if (!bitArray.canReadExpGolombCodedNum()) {
return; return;
} }
int sliceType = scratch.readUnsignedExpGolombCodedInt(); int sliceType = bitArray.readUnsignedExpGolombCodedInt();
if (!detectAccessUnits) { if (!detectAccessUnits) {
// There are AUDs in the stream so the rest of the header can be ignored. // There are AUDs in the stream so the rest of the header can be ignored.
isFilling = false; isFilling = false;
sliceHeader.setSliceType(sliceType); sliceHeader.setSliceType(sliceType);
return; return;
} }
if (!scratch.canReadExpGolombCodedNum()) { if (!bitArray.canReadExpGolombCodedNum()) {
return; return;
} }
int picParameterSetId = scratch.readUnsignedExpGolombCodedInt(); int picParameterSetId = bitArray.readUnsignedExpGolombCodedInt();
if (pps.indexOfKey(picParameterSetId) < 0) { if (pps.indexOfKey(picParameterSetId) < 0) {
// We have not seen the PPS yet, so don't try to parse the slice header. // We have not seen the PPS yet, so don't try to parse the slice header.
isFilling = false; isFilling = false;
@ -346,65 +339,65 @@ import java.util.List;
NalUnitUtil.PpsData ppsData = pps.get(picParameterSetId); NalUnitUtil.PpsData ppsData = pps.get(picParameterSetId);
NalUnitUtil.SpsData spsData = sps.get(ppsData.seqParameterSetId); NalUnitUtil.SpsData spsData = sps.get(ppsData.seqParameterSetId);
if (spsData.separateColorPlaneFlag) { if (spsData.separateColorPlaneFlag) {
if (scratch.bitsLeft() < 2) { if (!bitArray.canReadBits(2)) {
return; return;
} }
scratch.skipBits(2); // colour_plane_id bitArray.skipBits(2); // colour_plane_id
} }
if (scratch.bitsLeft() < spsData.frameNumLength) { if (!bitArray.canReadBits(spsData.frameNumLength)) {
return; return;
} }
boolean fieldPicFlag = false; boolean fieldPicFlag = false;
boolean bottomFieldFlagPresent = false; boolean bottomFieldFlagPresent = false;
boolean bottomFieldFlag = false; boolean bottomFieldFlag = false;
int frameNum = scratch.readBits(spsData.frameNumLength); int frameNum = bitArray.readBits(spsData.frameNumLength);
if (!spsData.frameMbsOnlyFlag) { if (!spsData.frameMbsOnlyFlag) {
if (scratch.bitsLeft() < 1) { if (!bitArray.canReadBits(1)) {
return; return;
} }
fieldPicFlag = scratch.readBit(); fieldPicFlag = bitArray.readBit();
if (fieldPicFlag) { if (fieldPicFlag) {
if (scratch.bitsLeft() < 1) { if (!bitArray.canReadBits(1)) {
return; return;
} }
bottomFieldFlag = scratch.readBit(); bottomFieldFlag = bitArray.readBit();
bottomFieldFlagPresent = true; bottomFieldFlagPresent = true;
} }
} }
boolean idrPicFlag = nalUnitType == NAL_UNIT_TYPE_IDR; boolean idrPicFlag = nalUnitType == NAL_UNIT_TYPE_IDR;
int idrPicId = 0; int idrPicId = 0;
if (idrPicFlag) { if (idrPicFlag) {
if (!scratch.canReadExpGolombCodedNum()) { if (!bitArray.canReadExpGolombCodedNum()) {
return; return;
} }
idrPicId = scratch.readUnsignedExpGolombCodedInt(); idrPicId = bitArray.readUnsignedExpGolombCodedInt();
} }
int picOrderCntLsb = 0; int picOrderCntLsb = 0;
int deltaPicOrderCntBottom = 0; int deltaPicOrderCntBottom = 0;
int deltaPicOrderCnt0 = 0; int deltaPicOrderCnt0 = 0;
int deltaPicOrderCnt1 = 0; int deltaPicOrderCnt1 = 0;
if (spsData.picOrderCountType == 0) { if (spsData.picOrderCountType == 0) {
if (scratch.bitsLeft() < spsData.picOrderCntLsbLength) { if (!bitArray.canReadBits(spsData.picOrderCntLsbLength)) {
return; return;
} }
picOrderCntLsb = scratch.readBits(spsData.picOrderCntLsbLength); picOrderCntLsb = bitArray.readBits(spsData.picOrderCntLsbLength);
if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) { if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) {
if (!scratch.canReadExpGolombCodedNum()) { if (!bitArray.canReadExpGolombCodedNum()) {
return; return;
} }
deltaPicOrderCntBottom = scratch.readSignedExpGolombCodedInt(); deltaPicOrderCntBottom = bitArray.readSignedExpGolombCodedInt();
} }
} else if (spsData.picOrderCountType == 1 } else if (spsData.picOrderCountType == 1
&& !spsData.deltaPicOrderAlwaysZeroFlag) { && !spsData.deltaPicOrderAlwaysZeroFlag) {
if (!scratch.canReadExpGolombCodedNum()) { if (!bitArray.canReadExpGolombCodedNum()) {
return; return;
} }
deltaPicOrderCnt0 = scratch.readSignedExpGolombCodedInt(); deltaPicOrderCnt0 = bitArray.readSignedExpGolombCodedInt();
if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) { if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) {
if (!scratch.canReadExpGolombCodedNum()) { if (!bitArray.canReadExpGolombCodedNum()) {
return; return;
} }
deltaPicOrderCnt1 = scratch.readSignedExpGolombCodedInt(); deltaPicOrderCnt1 = bitArray.readSignedExpGolombCodedInt();
} }
} }
sliceHeader.setAll(spsData, nalRefIdc, sliceType, frameNum, picParameterSetId, fieldPicFlag, sliceHeader.setAll(spsData, nalRefIdc, sliceType, frameNum, picParameterSetId, fieldPicFlag,

View File

@ -20,8 +20,8 @@ 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.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.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.ParsableNalUnitBitArray;
import android.util.Log; import android.util.Log;
@ -210,9 +210,8 @@ import java.util.Collections;
System.arraycopy(sps.nalData, 0, csd, vps.nalLength, sps.nalLength); System.arraycopy(sps.nalData, 0, csd, vps.nalLength, sps.nalLength);
System.arraycopy(pps.nalData, 0, csd, vps.nalLength + sps.nalLength, pps.nalLength); System.arraycopy(pps.nalData, 0, csd, vps.nalLength + sps.nalLength, pps.nalLength);
// Unescape and then parse the SPS NAL unit, as per H.265/HEVC (2014) 7.3.2.2.1. // Parse the SPS NAL unit, as per H.265/HEVC (2014) 7.3.2.2.1.
NalUnitUtil.unescapeStream(sps.nalData, sps.nalLength); ParsableNalUnitBitArray bitArray = new ParsableNalUnitBitArray(sps.nalData, 0, sps.nalLength);
ParsableBitArray bitArray = new ParsableBitArray(sps.nalData);
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
int maxSubLayersMinus1 = bitArray.readBits(3); int maxSubLayersMinus1 = bitArray.readBits(3);
bitArray.skipBits(1); // sps_temporal_id_nesting_flag bitArray.skipBits(1); // sps_temporal_id_nesting_flag
@ -316,7 +315,7 @@ import java.util.Collections;
/** /**
* 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(ParsableNalUnitBitArray 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) {
if (!bitArray.readBit()) { // scaling_list_pred_mode_flag[sizeId][matrixId] if (!bitArray.readBit()) { // scaling_list_pred_mode_flag[sizeId][matrixId]
@ -340,7 +339,7 @@ import java.util.Collections;
* Reads the number of short term reference picture sets in a SPS as ue(v), then skips all of * Reads the number of short term reference picture sets in a SPS as ue(v), then skips all of
* them. See H.265/HEVC (2014) 7.3.7. * them. See H.265/HEVC (2014) 7.3.7.
*/ */
private static void skipShortTermRefPicSets(ParsableBitArray bitArray) { private static void skipShortTermRefPicSets(ParsableNalUnitBitArray bitArray) {
int numShortTermRefPicSets = bitArray.readUnsignedExpGolombCodedInt(); int numShortTermRefPicSets = bitArray.readUnsignedExpGolombCodedInt();
boolean interRefPicSetPredictionFlag = false; boolean interRefPicSetPredictionFlag = false;
int numNegativePics = 0; int numNegativePics = 0;

View File

@ -236,11 +236,14 @@ public final class NalUnitUtil {
* Parses an SPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection * Parses an SPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection
* 7.3.2.1.1. * 7.3.2.1.1.
* *
* @param data A {@link ParsableBitArray} containing the SPS data. The position must to set to the * @param nalData A buffer containing escaped SPS data.
* start of the data (i.e. the first bit of the profile_idc field). * @param nalOffset The offset of the NAL unit header in {@code nalData}.
* @param nalLimit The limit of the NAL unit in {@code nalData}.
* @return A parsed representation of the SPS data. * @return A parsed representation of the SPS data.
*/ */
public static SpsData parseSpsNalUnit(ParsableBitArray data) { public static SpsData parseSpsNalUnit(byte[] nalData, int nalOffset, int nalLimit) {
ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit);
data.skipBits(8); // nal_unit
int profileIdc = data.readBits(8); int profileIdc = data.readBits(8);
data.skipBits(16); // constraint bits (6), reserved (2) and level_idc (8) data.skipBits(16); // constraint bits (6), reserved (2) and level_idc (8)
int seqParameterSetId = data.readUnsignedExpGolombCodedInt(); int seqParameterSetId = data.readUnsignedExpGolombCodedInt();
@ -348,11 +351,14 @@ public final class NalUnitUtil {
* Parses a PPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection * Parses a PPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection
* 7.3.2.2. * 7.3.2.2.
* *
* @param data A {@link ParsableBitArray} containing the PPS data. The position must to set to the * @param nalData A buffer containing escaped PPS data.
* start of the data (i.e. the first bit of the pic_parameter_set_id field). * @param nalOffset The offset of the NAL unit header in {@code nalData}.
* @param nalLimit The limit of the NAL unit in {@code nalData}.
* @return A parsed representation of the PPS data. * @return A parsed representation of the PPS data.
*/ */
public static PpsData parsePpsNalUnit(ParsableBitArray data) { public static PpsData parsePpsNalUnit(byte[] nalData, int nalOffset, int nalLimit) {
ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit);
data.skipBits(8); // nal_unit
int picParameterSetId = data.readUnsignedExpGolombCodedInt(); int picParameterSetId = data.readUnsignedExpGolombCodedInt();
int seqParameterSetId = data.readUnsignedExpGolombCodedInt(); int seqParameterSetId = data.readUnsignedExpGolombCodedInt();
data.skipBits(1); // entropy_coding_mode_flag data.skipBits(1); // entropy_coding_mode_flag
@ -459,7 +465,7 @@ public final class NalUnitUtil {
return limit; return limit;
} }
private static void skipScalingList(ParsableBitArray bitArray, int size) { private static void skipScalingList(ParsableNalUnitBitArray bitArray, int size) {
int lastScale = 8; int lastScale = 8;
int nextScale = 8; int nextScale = 8;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {

View File

@ -177,52 +177,6 @@ public final class ParsableBitArray {
return returnValue; return returnValue;
} }
/**
* Returns whether it is possible to read an Exp-Golomb-coded integer starting from the current
* offset. The offset is not modified.
*
* @return Whether it is possible to read an Exp-Golomb-coded integer.
*/
public boolean canReadExpGolombCodedNum() {
int initialByteOffset = byteOffset;
int initialBitOffset = bitOffset;
int leadingZeros = 0;
while (byteOffset < byteLimit && !readBit()) {
leadingZeros++;
}
boolean hitLimit = byteOffset == byteLimit;
byteOffset = initialByteOffset;
bitOffset = initialBitOffset;
return !hitLimit && bitsLeft() >= leadingZeros * 2 + 1;
}
/**
* Reads an unsigned Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public int readUnsignedExpGolombCodedInt() {
return readExpGolombCodeNum();
}
/**
* Reads an signed Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public int readSignedExpGolombCodedInt() {
int codeNum = readExpGolombCodeNum();
return ((codeNum % 2) == 0 ? -1 : 1) * ((codeNum + 1) / 2);
}
private int readExpGolombCodeNum() {
int leadingZeros = 0;
while (!readBit()) {
leadingZeros++;
}
return (1 << leadingZeros) - 1 + (leadingZeros > 0 ? readBits(leadingZeros) : 0);
}
private void assertValidOffset() { private void assertValidOffset() {
// It is fine for position to be at the end of the array, but no further. // It is fine for position to be at the end of the array, but no further.
Assertions.checkState(byteOffset >= 0 Assertions.checkState(byteOffset >= 0

View File

@ -0,0 +1,226 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.util;
/**
* Wraps a byte array, providing methods that allow it to be read as a NAL unit bitstream.
* <p>
* Whenever the byte sequence [0, 0, 3] appears in the wrapped byte array, it is treated as [0, 0]
* for all reading/skipping operations, which makes the bitstream appear to be unescaped.
*/
public final class ParsableNalUnitBitArray {
private byte[] data;
private int byteLimit;
// The byte offset is never equal to the offset of the 3rd byte in a subsequence [0, 0, 3].
private int byteOffset;
private int bitOffset;
/**
* @param data The data to wrap.
* @param offset The byte offset in {@code data} to start reading from.
* @param limit The byte offset of the end of the bitstream in {@code data}.
*/
public ParsableNalUnitBitArray(byte[] data, int offset, int limit) {
reset(data, offset, limit);
}
/**
* Resets the wrapped data, limit and offset.
*
* @param data The data to wrap.
* @param offset The byte offset in {@code data} to start reading from.
* @param limit The byte offset of the end of the bitstream in {@code data}.
*/
public void reset(byte[] data, int offset, int limit) {
this.data = data;
byteOffset = offset;
byteLimit = limit;
bitOffset = 0;
assertValidOffset();
}
/**
* Skips bits and moves current reading position forward.
*
* @param n The number of bits to skip.
*/
public void skipBits(int n) {
int oldByteOffset = byteOffset;
byteOffset += (n / 8);
bitOffset += (n % 8);
if (bitOffset > 7) {
byteOffset++;
bitOffset -= 8;
}
for (int i = oldByteOffset + 1; i <= byteOffset; i++) {
if (shouldSkipByte(i)) {
// Skip the byte and move forward to check three bytes ahead.
byteOffset++;
i += 2;
}
}
assertValidOffset();
}
/**
* Returns whether it is possible to read {@code n} bits starting from the current offset. The
* offset is not modified.
*
* @return Whether it is possible to read {@code n} bits.
*/
public boolean canReadBits(int n) {
int oldByteOffset = byteOffset;
int newByteOffset = byteOffset + (n / 8);
int newBitOffset = bitOffset + (n % 8);
if (newBitOffset > 7) {
newByteOffset++;
newBitOffset -= 8;
}
for (int i = oldByteOffset + 1; i <= newByteOffset && newByteOffset < byteLimit; i++) {
if (shouldSkipByte(i)) {
// Skip the byte and move forward to check three bytes ahead.
newByteOffset++;
i += 2;
}
}
return newByteOffset < byteLimit || (newByteOffset == byteLimit && newBitOffset == 0);
}
/**
* Reads a single bit.
*
* @return True if the bit is set. False otherwise.
*/
public boolean readBit() {
return readBits(1) == 1;
}
/**
* Reads up to 32 bits.
*
* @param numBits The number of bits to read.
* @return An integer whose bottom n bits hold the read data.
*/
public int readBits(int numBits) {
if (numBits == 0) {
return 0;
}
int returnValue = 0;
// Read as many whole bytes as we can.
int wholeBytes = (numBits / 8);
for (int i = 0; i < wholeBytes; i++) {
int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1;
int byteValue;
if (bitOffset != 0) {
byteValue = ((data[byteOffset] & 0xFF) << bitOffset)
| ((data[nextByteOffset] & 0xFF) >>> (8 - bitOffset));
} else {
byteValue = data[byteOffset];
}
numBits -= 8;
returnValue |= (byteValue & 0xFF) << numBits;
byteOffset = nextByteOffset;
}
// Read any remaining bits.
if (numBits > 0) {
int nextBit = bitOffset + numBits;
byte writeMask = (byte) (0xFF >> (8 - numBits));
int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1;
if (nextBit > 8) {
// Combine bits from current byte and next byte.
returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8)
| ((data[nextByteOffset] & 0xFF) >> (16 - nextBit))) & writeMask));
byteOffset = nextByteOffset;
} else {
// Bits to be read only within current byte.
returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask);
if (nextBit == 8) {
byteOffset = nextByteOffset;
}
}
bitOffset = nextBit % 8;
}
assertValidOffset();
return returnValue;
}
/**
* Returns whether it is possible to read an Exp-Golomb-coded integer starting from the current
* offset. The offset is not modified.
*
* @return Whether it is possible to read an Exp-Golomb-coded integer.
*/
public boolean canReadExpGolombCodedNum() {
int initialByteOffset = byteOffset;
int initialBitOffset = bitOffset;
int leadingZeros = 0;
while (byteOffset < byteLimit && !readBit()) {
leadingZeros++;
}
boolean hitLimit = byteOffset == byteLimit;
byteOffset = initialByteOffset;
bitOffset = initialBitOffset;
return !hitLimit && canReadBits(leadingZeros * 2 + 1);
}
/**
* Reads an unsigned Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public int readUnsignedExpGolombCodedInt() {
return readExpGolombCodeNum();
}
/**
* Reads an signed Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public int readSignedExpGolombCodedInt() {
int codeNum = readExpGolombCodeNum();
return ((codeNum % 2) == 0 ? -1 : 1) * ((codeNum + 1) / 2);
}
private int readExpGolombCodeNum() {
int leadingZeros = 0;
while (!readBit()) {
leadingZeros++;
}
return (1 << leadingZeros) - 1 + (leadingZeros > 0 ? readBits(leadingZeros) : 0);
}
private boolean shouldSkipByte(int offset) {
return 2 <= offset && offset < byteLimit && data[offset] == (byte) 0x03
&& data[offset - 2] == (byte) 0x00 && data[offset - 1] == (byte) 0x00;
}
private void assertValidOffset() {
// It is fine for position to be at the end of the array, but no further.
Assertions.checkState(byteOffset >= 0
&& (bitOffset >= 0 && bitOffset < 8)
&& (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0)));
}
}