mirror of
https://github.com/samsonjs/media.git
synced 2026-04-05 11:15:46 +00:00
Add support for reading H.265 in MPEG TS.
This commit is contained in:
parent
02d5cb8109
commit
5b186a2aa4
5 changed files with 580 additions and 12 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue