Add support for reading H.265 in MPEG TS.

This commit is contained in:
Andrew Lewis 2015-06-01 17:42:43 +01:00
parent 02d5cb8109
commit 5b186a2aa4
5 changed files with 580 additions and 12 deletions

View file

@ -77,7 +77,7 @@ import java.util.List;
private final NalUnitTargetBuffer sps;
private final NalUnitTargetBuffer pps;
private final NalUnitTargetBuffer sei;
private boolean writingSample;
private boolean foundFirstSample;
private long totalBytesWritten;
// Per sample state that gets reset at the start of each sample.
@ -111,7 +111,7 @@ import java.util.List;
if (ifrParserBuffer != null) {
ifrParserBuffer.reset();
}
writingSample = false;
foundFirstSample = false;
totalBytesWritten = 0;
}
@ -146,7 +146,7 @@ import java.util.List;
isKeyframe = true;
break;
case NAL_UNIT_TYPE_AUD:
if (writingSample) {
if (foundFirstSample) {
if (ifrParserBuffer != null && ifrParserBuffer.isCompleted()) {
int sliceType = ifrParserBuffer.getSliceType();
isKeyframe |= (sliceType == FRAME_TYPE_I || sliceType == FRAME_TYPE_ALL_I);
@ -158,9 +158,8 @@ import java.util.List;
int flags = isKeyframe ? C.SAMPLE_FLAG_SYNC : 0;
int size = (int) (totalBytesWritten - samplePosition) - bytesWrittenPastNalUnit;
output.sampleMetadata(sampleTimeUs, flags, size, bytesWrittenPastNalUnit, null);
writingSample = false;
}
writingSample = true;
foundFirstSample = true;
samplePosition = totalBytesWritten - bytesWrittenPastNalUnit;
sampleTimeUs = pesTimeUs;
isKeyframe = false;
@ -215,6 +214,7 @@ import java.util.List;
if (sei.endNalUnit(discardPadding)) {
int unescapedLength = unescapeStream(sei.nalData, sei.nalLength);
seiWrapper.reset(sei.nalData, unescapedLength);
seiWrapper.setPosition(4); // NAL prefix and nal_unit() header.
seiReader.consume(seiWrapper, pesTimeUs, true);
}
}
@ -385,7 +385,7 @@ import java.util.List;
return unescapedLength;
}
private int findNextUnescapeIndex(byte[] bytes, int offset, int limit) {
private static int findNextUnescapeIndex(byte[] bytes, int offset, int limit) {
for (int i = offset; i < limit - 2; i++) {
if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x03) {
return i;

View file

@ -0,0 +1,555 @@
/*
* 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.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.NalUnitUtil;
import com.google.android.exoplayer.util.ParsableBitArray;
import com.google.android.exoplayer.util.ParsableByteArray;
import android.util.Log;
import java.util.Arrays;
import java.util.Collections;
/**
* Parses a continuous H.265 byte stream and extracts individual frames.
*/
/* package */ class H265Reader extends ElementaryStreamReader {
private static final String TAG = "H265Reader";
// nal_unit_type values from H.265/HEVC (2014) Table 7-1.
private static final int BLA_W_LP = 16;
private static final int BLA_W_RADL = 17;
private static final int BLA_N_LP = 18;
private static final int IDR_W_RADL = 19;
private static final int IDR_N_LP = 20;
private static final int CRA_NUT = 21;
private static final int VPS_NUT = 32;
private static final int SPS_NUT = 33;
private static final int PPS_NUT = 34;
private static final int PREFIX_SEI_NUT = 39;
private static final int SUFFIX_SEI_NUT = 40;
// TODO: Deduplicate with H264Reader.
private static final int EXTENDED_SAR = 0xFF;
private static final float[] ASPECT_RATIO_IDC_VALUES = new float[] {
1f /* Unspecified. Assume square */,
1f,
12f / 11f,
10f / 11f,
16f / 11f,
40f / 33f,
24f / 11f,
20f / 11f,
32f / 11f,
80f / 33f,
18f / 11f,
15f / 11f,
64f / 33f,
160f / 99f,
4f / 3f,
3f / 2f,
2f
};
// State that should not be reset on seek.
private boolean hasOutputFormat;
// State that should be reset on seek.
private final SeiReader seiReader;
private final boolean[] prefixFlags;
private final NalUnitTargetBuffer vps;
private final NalUnitTargetBuffer sps;
private final NalUnitTargetBuffer pps;
private final NalUnitTargetBuffer prefixSei;
private final NalUnitTargetBuffer suffixSei; // TODO: Are both needed?
private boolean foundFirstSample;
private long totalBytesWritten;
// 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;
private int[] scratchEscapePositions;
public H265Reader(TrackOutput output, SeiReader seiReader) {
super(output);
this.seiReader = seiReader;
prefixFlags = new boolean[3];
vps = new NalUnitTargetBuffer(VPS_NUT, 128);
sps = new NalUnitTargetBuffer(SPS_NUT, 128);
pps = new NalUnitTargetBuffer(PPS_NUT, 128);
prefixSei = new NalUnitTargetBuffer(PREFIX_SEI_NUT, 128);
suffixSei = new NalUnitTargetBuffer(SUFFIX_SEI_NUT, 128);
seiWrapper = new ParsableByteArray();
scratchEscapePositions = new int[10];
}
@Override
public void seek() {
seiReader.seek();
NalUnitUtil.clearPrefixFlags(prefixFlags);
vps.reset();
sps.reset();
pps.reset();
prefixSei.reset();
suffixSei.reset();
foundFirstSample = false;
totalBytesWritten = 0;
}
@Override
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
while (data.bytesLeft() > 0) {
int offset = data.getPosition();
int limit = data.limit();
byte[] dataArray = data.data;
// Append the data to the buffer.
totalBytesWritten += data.bytesLeft();
output.sampleData(data, data.bytesLeft());
// Scan the appended data, processing NAL units as they are encountered
while (offset < limit) {
int nextNalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags);
if (nextNalUnitOffset < limit) {
// We've seen the start of a NAL unit.
// This is the length to the start of the unit. It may be negative if the NAL unit
// actually started in previously consumed data.
int lengthToNalUnit = nextNalUnitOffset - offset;
if (lengthToNalUnit > 0) {
feedNalUnitTargetBuffersData(dataArray, offset, nextNalUnitOffset);
}
int nalUnitType = NalUnitUtil.getH265NalUnitType(dataArray, nextNalUnitOffset);
int bytesWrittenPastNalUnit = limit - nextNalUnitOffset;
isKeyframe |= isRandomAccessPoint(nalUnitType);
// Output sample data for VCL NAL units.
if (isInVcl(nalUnitType)) {
if (foundFirstSample) {
if (isKeyframe && !hasOutputFormat && vps.isCompleted() && sps.isCompleted()
&& pps.isCompleted()) {
parseMediaFormat(vps, sps, pps);
}
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;
}
// If the length to the start of the 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);
// Notify the start of the next NAL unit.
feedNalUnitTargetBuffersStart(nalUnitType);
// Continue scanning the data.
offset = nextNalUnitOffset + 3;
} else {
feedNalUnitTargetBuffersData(dataArray, offset, limit);
offset = limit;
}
}
}
}
@Override
public void packetFinished() {
// Do nothing.
}
private void feedNalUnitTargetBuffersStart(int nalUnitType) {
if (!hasOutputFormat) {
vps.startNalUnit(nalUnitType);
sps.startNalUnit(nalUnitType);
pps.startNalUnit(nalUnitType);
}
prefixSei.startNalUnit(nalUnitType);
suffixSei.startNalUnit(nalUnitType);
}
private void feedNalUnitTargetBuffersData(byte[] dataArray, int offset, int limit) {
if (!hasOutputFormat) {
vps.appendToNalUnit(dataArray, offset, limit);
sps.appendToNalUnit(dataArray, offset, limit);
pps.appendToNalUnit(dataArray, offset, limit);
}
prefixSei.appendToNalUnit(dataArray, offset, limit);
suffixSei.appendToNalUnit(dataArray, offset, limit);
}
private void feedNalUnitTargetEnd(long pesTimeUs, int discardPadding) {
vps.endNalUnit(discardPadding);
sps.endNalUnit(discardPadding);
pps.endNalUnit(discardPadding);
if (prefixSei.endNalUnit(discardPadding)) {
int unescapedLength = unescapeStream(prefixSei.nalData, prefixSei.nalLength);
seiWrapper.reset(prefixSei.nalData, unescapedLength);
// Skip the NAL prefix and type.
seiWrapper.skipBytes(5);
seiReader.consume(seiWrapper, pesTimeUs, true);
}
if (suffixSei.endNalUnit(discardPadding)) {
int unescapedLength = unescapeStream(suffixSei.nalData, suffixSei.nalLength);
seiWrapper.reset(suffixSei.nalData, unescapedLength);
// Skip the NAL prefix and type.
seiWrapper.skipBytes(5);
seiReader.consume(seiWrapper, pesTimeUs, true);
}
}
private void parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTargetBuffer sps,
NalUnitTargetBuffer pps) {
// Build codec-specific data.
byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength);
System.arraycopy(sps.nalData, 0, csd, vps.nalLength, sps.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.
unescapeStream(sps.nalData, sps.nalLength);
ParsableBitArray bitArray = new ParsableBitArray(sps.nalData);
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
int maxSubLayersMinus1 = bitArray.readBits(3);
bitArray.skipBits(1); // sps_temporal_id_nesting_flag
// profile_tier_level(1, sps_max_sub_layers_minus1)
bitArray.skipBits(88); // if (profilePresentFlag) {...}
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]
toSkip += 89;
}
if (bitArray.readBits(1) == 1) { // sub_layer_level_present_flag[i]
toSkip += 8;
}
}
bitArray.skipBits(toSkip);
if (maxSubLayersMinus1 > 0) {
bitArray.skipBits(2 * (8 - maxSubLayersMinus1));
}
bitArray.readUnsignedExpGolombCodedInt(); // sps_seq_parameter_set_id
int chromaFormatIdc = bitArray.readUnsignedExpGolombCodedInt();
if (chromaFormatIdc == 3) {
bitArray.skipBits(1); // separate_colour_plane_flag
}
int picWidthInLumaSamples = bitArray.readUnsignedExpGolombCodedInt();
int picHeightInLumaSamples = bitArray.readUnsignedExpGolombCodedInt();
if (bitArray.readBit()) { // conformance_window_flag
int confWinLeftOffset = bitArray.readUnsignedExpGolombCodedInt();
int confWinRightOffset = bitArray.readUnsignedExpGolombCodedInt();
int confWinTopOffset = bitArray.readUnsignedExpGolombCodedInt();
int confWinBottomOffset = bitArray.readUnsignedExpGolombCodedInt();
// H.265/HEVC (2014) Table 6-1
int subWidthC = chromaFormatIdc == 1 || chromaFormatIdc == 2 ? 2 : 1;
int subHeightC = chromaFormatIdc == 1 ? 2 : 1;
picWidthInLumaSamples -= subWidthC * (confWinLeftOffset + confWinRightOffset);
picHeightInLumaSamples -= subHeightC * (confWinTopOffset + confWinBottomOffset);
}
bitArray.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8
bitArray.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8
int log2MaxPicOrderCntLsbMinus4 = bitArray.readUnsignedExpGolombCodedInt();
// for (i = sps_sub_layer_ordering_info_present_flag ? 0 : sps_max_sub_layers_minus1; ...)
for (int i = bitArray.readBit() ? 0 : maxSubLayersMinus1; i <= maxSubLayersMinus1; i++) {
bitArray.readUnsignedExpGolombCodedInt(); // sps_max_dec_pic_buffering_minus1[i]
bitArray.readUnsignedExpGolombCodedInt(); // sps_max_num_reorder_pics[i]
bitArray.readUnsignedExpGolombCodedInt(); // sps_max_latency_increase_plus1[i]
}
bitArray.readUnsignedExpGolombCodedInt(); // log2_min_luma_coding_block_size_minus3
bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_luma_coding_block_size
bitArray.readUnsignedExpGolombCodedInt(); // log2_min_luma_transform_block_size_minus2
bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_luma_transform_block_size
bitArray.readUnsignedExpGolombCodedInt(); // max_transform_hierarchy_depth_inter
bitArray.readUnsignedExpGolombCodedInt(); // max_transform_hierarchy_depth_intra
// if (scaling_list_enabled_flag) { if (sps_scaling_list_data_present_flag) {...}}
if (bitArray.readBit() && bitArray.readBit()) {
skipScalingList(bitArray);
}
bitArray.skipBits(2); // amp_enabled_flag (1), sample_adaptive_offset_enabled_flag (1)
if (bitArray.readBit()) { // pcm_enabled_flag
// pcm_sample_bit_depth_luma_minus1 (4), pcm_sample_bit_depth_chroma_minus1 (4)
bitArray.skipBits(4);
bitArray.readUnsignedExpGolombCodedInt(); // log2_min_pcm_luma_coding_block_size_minus3
bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_pcm_luma_coding_block_size
bitArray.skipBits(1); // pcm_loop_filter_disabled_flag
}
// Skips all short term reference picture sets.
skipShortTermRefPicSets(bitArray);
if (bitArray.readBit()) { // long_term_ref_pics_present_flag
// num_long_term_ref_pics_sps
for (int i = 0; i < bitArray.readUnsignedExpGolombCodedInt(); i++) {
int ltRefPicPocLsbSpsLength = log2MaxPicOrderCntLsbMinus4 + 4;
// lt_ref_pic_poc_lsb_sps[i], used_by_curr_pic_lt_sps_flag[i]
bitArray.skipBits(ltRefPicPocLsbSpsLength + 1);
}
}
bitArray.skipBits(2); // sps_temporal_mvp_enabled_flag, strong_intra_smoothing_enabled_flag
float pixelWidthHeightRatio = 1;
if (bitArray.readBit()) { // vui_parameters_present_flag
if (bitArray.readBit()) { // aspect_ratio_info_present_flag
int aspectRatioIdc = bitArray.readBits(8);
if (aspectRatioIdc == EXTENDED_SAR) {
int sarWidth = bitArray.readBits(16);
int sarHeight = bitArray.readBits(16);
if (sarWidth != 0 && sarHeight != 0) {
pixelWidthHeightRatio = (float) sarWidth / sarHeight;
}
} else if (aspectRatioIdc < ASPECT_RATIO_IDC_VALUES.length) {
pixelWidthHeightRatio = ASPECT_RATIO_IDC_VALUES[aspectRatioIdc];
} else {
Log.w(TAG, "Unexpected aspect_ratio_idc value: " + aspectRatioIdc);
}
}
}
output.format(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H265, MediaFormat.NO_VALUE,
C.UNKNOWN_TIME_US, picWidthInLumaSamples, picHeightInLumaSamples, pixelWidthHeightRatio,
Collections.singletonList(csd)));
hasOutputFormat = true;
}
/** Skips scaling_list_data(). See H.265/HEVC (2014) 7.3.4. */
private void skipScalingList(ParsableBitArray bitArray) {
for (int sizeId = 0; sizeId < 4; sizeId++) {
for (int matrixId = 0; matrixId < 6; matrixId += sizeId == 3 ? 3 : 1) {
if (!bitArray.readBit()) { // scaling_list_pred_mode_flag[sizeId][matrixId]
// scaling_list_pred_matrix_id_delta[sizeId][matrixId]
bitArray.readUnsignedExpGolombCodedInt();
} else {
int coefNum = Math.min(64, 1 << (4 + sizeId << 1));
if (sizeId > 1) {
// scaling_list_dc_coef_minus8[sizeId 2][matrixId]
bitArray.readSignedExpGolombCodedInt();
}
for (int i = 0; i < coefNum; i++) {
bitArray.readSignedExpGolombCodedInt(); // scaling_list_delta_coef
}
}
}
}
}
// TODO: Deduplicate with H264Reader.
/**
* Unescapes {@code data} up to the specified limit, replacing occurrences of [0, 0, 3] with
* [0, 0]. The unescaped data is returned in-place, with the return value indicating its length.
*
* @param data The data to unescape.
* @param limit The limit (exclusive) of the data to unescape.
* @return The length of the unescaped data.
*/
private int unescapeStream(byte[] data, int limit) {
int position = 0;
int scratchEscapeCount = 0;
while (position < limit) {
position = findNextUnescapeIndex(data, position, limit);
if (position < limit) {
if (scratchEscapePositions.length <= scratchEscapeCount) {
// Grow scratchEscapePositions to hold a larger number of positions.
scratchEscapePositions = Arrays.copyOf(scratchEscapePositions,
scratchEscapePositions.length * 2);
}
scratchEscapePositions[scratchEscapeCount++] = position;
position += 3;
}
}
int unescapedLength = limit - scratchEscapeCount;
int escapedPosition = 0; // The position being read from.
int unescapedPosition = 0; // The position being written to.
for (int i = 0; i < scratchEscapeCount; i++) {
int nextEscapePosition = scratchEscapePositions[i];
int copyLength = nextEscapePosition - escapedPosition;
System.arraycopy(data, escapedPosition, data, unescapedPosition, copyLength);
escapedPosition += copyLength + 3;
unescapedPosition += copyLength + 2;
}
int remainingLength = unescapedLength - unescapedPosition;
System.arraycopy(data, escapedPosition, data, unescapedPosition, remainingLength);
return unescapedLength;
}
private static int findNextUnescapeIndex(byte[] bytes, int offset, int limit) {
for (int i = offset; i < limit - 2; i++) {
if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x03) {
return i;
}
}
return limit;
}
/** Returns whether the NAL unit is a random access point. */
private static boolean isRandomAccessPoint(int nalUnitType) {
return nalUnitType == BLA_W_LP || nalUnitType == BLA_W_RADL || nalUnitType == BLA_N_LP
|| nalUnitType == IDR_W_RADL || nalUnitType == IDR_N_LP || nalUnitType == CRA_NUT;
}
/** Returns whether the NAL unit is in the video coding layer. */
private static boolean isInVcl(int nalUnitType) {
return nalUnitType <= VPS_NUT;
}
/**
* 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.
*/
private static void skipShortTermRefPicSets(ParsableBitArray bitArray) {
int numShortTermRefPicSets = bitArray.readUnsignedExpGolombCodedInt();
boolean interRefPicSetPredictionFlag = false;
int numNegativePics = 0;
int numPositivePics = 0;
// As this method applies in a SPS, the only element of NumDeltaPocs accessed is the previous
// one, so we just keep track of that rather than storing the whole array.
// RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1) and delta_idx_minus1 is always zero in SPS.
int previousNumDeltaPocs = 0;
for (int stRpsIdx = 0; stRpsIdx < numShortTermRefPicSets; stRpsIdx++) {
if (stRpsIdx != 0) {
interRefPicSetPredictionFlag = bitArray.readBit();
}
if (interRefPicSetPredictionFlag) {
bitArray.skipBits(1); // delta_rps_sign
bitArray.readUnsignedExpGolombCodedInt(); // abs_delta_rps_minus1
for (int j = 0; j <= previousNumDeltaPocs; j++) {
if (bitArray.readBit()) { // used_by_curr_pic_flag[j]
bitArray.skipBits(1); // use_delta_flag[j]
}
}
} else {
numNegativePics = bitArray.readUnsignedExpGolombCodedInt();
numPositivePics = bitArray.readUnsignedExpGolombCodedInt();
previousNumDeltaPocs = numNegativePics + numPositivePics;
for (int i = 0; i < numNegativePics; i++) {
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s0_minus1[i]
bitArray.skipBits(1); // used_by_curr_pic_s0_flag[i]
}
for (int i = 0; i < numPositivePics; i++) {
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s1_minus1[i]
bitArray.skipBits(1); // used_by_curr_pic_s1_flag[i]
}
}
}
}
// TODO: Deduplicate with H264Reader.NalUnitTargetBuffer.
/**
* A buffer that fills itself with data corresponding to a specific NAL unit, as it is
* encountered in the stream.
*/
private static final class NalUnitTargetBuffer {
private final int targetType;
private boolean isFilling;
private boolean isCompleted;
public byte[] nalData;
public int nalLength;
public NalUnitTargetBuffer(int targetType, int initialCapacity) {
this.targetType = targetType;
nalData = new byte[5 + initialCapacity];
nalData[2] = 1;
}
/**
* Resets the buffer, clearing any data that it holds.
*/
public void reset() {
isFilling = false;
isCompleted = false;
}
/**
* True if the buffer currently holds a complete NAL unit of the target type.
*/
public boolean isCompleted() {
return isCompleted;
}
/**
* Invoked to indicate that a NAL unit has started.
*
* @param type The type of the NAL unit.
*/
public void startNalUnit(int type) {
Assertions.checkState(!isFilling);
isFilling = type == targetType;
if (isFilling) {
nalLength = 3;
isCompleted = false;
}
}
/**
* Invoked to pass stream data. The data passed should not include 4 byte NAL unit prefixes.
*
* @param data Holds the data being passed.
* @param offset The offset of the data in {@code data}.
* @param limit The limit (exclusive) of the data in {@code data}.
*/
public void appendToNalUnit(byte[] data, int offset, int limit) {
if (!isFilling) {
return;
}
int readLength = limit - offset;
if (nalData.length < nalLength + readLength) {
nalData = Arrays.copyOf(nalData, (nalLength + readLength) * 2);
}
System.arraycopy(data, offset, nalData, nalLength, readLength);
nalLength += readLength;
}
/**
* Invoked to indicate that a NAL unit has ended.
*
* @param discardPadding The number of excess bytes that were passed to
* {@link #appendToNalUnit(byte[], int, int)}, which should be discarded.
* @return True if the ended NAL unit is of the target type. False otherwise.
*/
public boolean endNalUnit(int discardPadding) {
if (!isFilling) {
return false;
}
nalLength -= discardPadding;
isFilling = false;
isCompleted = true;
return true;
}
}
}

View file

@ -41,9 +41,6 @@ import com.google.android.exoplayer.util.ParsableByteArray;
@Override
public void consume(ParsableByteArray seiBuffer, long pesTimeUs, boolean startOfPacket) {
// Skip the NAL prefix and type.
seiBuffer.skipBytes(4);
int b;
while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) {
// Parse payload type.

View file

@ -46,6 +46,7 @@ public final class TsExtractor implements Extractor, SeekMap {
private static final int TS_STREAM_TYPE_ATSC_AC3 = 0x81;
private static final int TS_STREAM_TYPE_ATSC_E_AC3 = 0x87;
private static final int TS_STREAM_TYPE_H264 = 0x1B;
private static final int TS_STREAM_TYPE_H265 = 0x24;
private static final int TS_STREAM_TYPE_ID3 = 0x15;
private static final int TS_STREAM_TYPE_EIA608 = 0x100; // 0xFF + 1
@ -361,9 +362,12 @@ public final class TsExtractor implements Extractor, SeekMap {
pesPayloadReader = new Ac3Reader(output.track(streamType));
break;
case TS_STREAM_TYPE_H264:
SeiReader seiReader = new SeiReader(output.track(TS_STREAM_TYPE_EIA608));
pesPayloadReader = new H264Reader(output.track(TS_STREAM_TYPE_H264), seiReader,
idrKeyframesOnly);
pesPayloadReader = new H264Reader(output.track(TS_STREAM_TYPE_H264),
new SeiReader(output.track(TS_STREAM_TYPE_EIA608)), idrKeyframesOnly);
break;
case TS_STREAM_TYPE_H265:
pesPayloadReader = new H265Reader(output.track(TS_STREAM_TYPE_H265),
new SeiReader(output.track(TS_STREAM_TYPE_EIA608)));
break;
case TS_STREAM_TYPE_ID3:
pesPayloadReader = id3Reader;

View file

@ -64,6 +64,18 @@ public final class NalUnitUtil {
return data[offset + 3] & 0x1F;
}
/**
* Gets the type of the H.265 NAL unit in {@code data} that starts at {@code offset}.
*
* @param data The data to search.
* @param offset The start offset of a NAL unit. Must lie between {@code -3} (inclusive) and
* {@code data.length - 3} (exclusive).
* @return The type of the unit.
*/
public static int getH265NalUnitType(byte[] data, int offset) {
return (data[offset + 3] & 0x7E) >> 1;
}
/**
* Finds the first NAL unit in {@code data}.
* <p>