Commit 9108dc5b by Oliver Woodman

Merge pull request #9421 from MarcusWichelmann:hevc-sps-parsing

PiperOrigin-RevId: 398749045
parents a9867880 65001cc0
......@@ -27,6 +27,9 @@
* `SubtitleView` no longer implements `TextOutput`. `SubtitleView`
implements `Player.Listener`, so can be registered to a player with
`Player.addListener`.
* Extractors:
* MP4: Correctly handle HEVC tracks with pixel aspect ratios other than 1.
* TS: Correctly handle HEVC tracks with pixel aspect ratios other than 1.
* Downloads and caching:
* Modify `DownloadService` behavior when `DownloadService.getScheduler`
returns `null`, or returns a `Scheduler` that does not support the
......
......@@ -85,29 +85,14 @@ public final class CodecSpecificDataUtil {
"avc1.%02X%02X%02X", profileIdc, constraintsFlagsAndReservedZero2Bits, levelIdc);
}
/**
* Returns an RFC 6381 HEVC codec string based on the SPS NAL unit read from the provided bit
* array. The position of the bit array must be the start of an SPS NALU (nal_unit_header), and
* the position may be modified by this method.
*/
public static String buildHevcCodecStringFromSps(ParsableNalUnitBitArray bitArray) {
// Skip nal_unit_header, sps_video_parameter_set_id, sps_max_sub_layers_minus1 and
// sps_temporal_id_nesting_flag.
bitArray.skipBits(16 + 4 + 3 + 1);
int generalProfileSpace = bitArray.readBits(2);
boolean generalTierFlag = bitArray.readBit();
int generalProfileIdc = bitArray.readBits(5);
int generalProfileCompatibilityFlags = 0;
for (int i = 0; i < 32; i++) {
if (bitArray.readBit()) {
generalProfileCompatibilityFlags |= (1 << i);
}
}
int[] constraintBytes = new int[6];
for (int i = 0; i < constraintBytes.length; ++i) {
constraintBytes[i] = bitArray.readBits(8);
}
int generalLevelIdc = bitArray.readBits(8);
/** Builds an RFC 6381 HEVC codec string using the provided parameters. */
public static String buildHevcCodecString(
int generalProfileSpace,
boolean generalTierFlag,
int generalProfileIdc,
int generalProfileCompatibilityFlags,
int[] constraintBytes,
int generalLevelIdc) {
StringBuilder builder =
new StringBuilder(
Util.formatInvariant(
......
......@@ -131,7 +131,7 @@ public final class NalUnitUtilTest {
assertThat(data.frameNumLength).isEqualTo(4);
assertThat(data.picOrderCntLsbLength).isEqualTo(6);
assertThat(data.seqParameterSetId).isEqualTo(0);
assertThat(data.pixelWidthAspectRatio).isEqualTo(1.0f);
assertThat(data.pixelWidthHeightRatio).isEqualTo(1.0f);
assertThat(data.picOrderCountType).isEqualTo(0);
assertThat(data.separateColorPlaneFlag).isFalse();
}
......
......@@ -92,7 +92,7 @@ import com.google.android.exoplayer2.video.AvcConfig;
.setCodecs(avcConfig.codecs)
.setWidth(avcConfig.width)
.setHeight(avcConfig.height)
.setPixelWidthHeightRatio(avcConfig.pixelWidthAspectRatio)
.setPixelWidthHeightRatio(avcConfig.pixelWidthHeightRatio)
.setInitializationData(avcConfig.initializationData)
.build();
output.format(format);
......
......@@ -1130,7 +1130,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
initializationData = avcConfig.initializationData;
out.nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength;
if (!pixelWidthHeightRatioFromPasp) {
pixelWidthHeightRatio = avcConfig.pixelWidthAspectRatio;
pixelWidthHeightRatio = avcConfig.pixelWidthHeightRatio;
}
codecs = avcConfig.codecs;
} else if (childAtomType == Atom.TYPE_hvcC) {
......@@ -1140,6 +1140,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
HevcConfig hevcConfig = HevcConfig.parse(parent);
initializationData = hevcConfig.initializationData;
out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength;
if (!pixelWidthHeightRatioFromPasp) {
pixelWidthHeightRatio = hevcConfig.pixelWidthHeightRatio;
}
codecs = hevcConfig.codecs;
} else if (childAtomType == Atom.TYPE_dvcC || childAtomType == Atom.TYPE_dvvC) {
@Nullable DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(parent);
......
......@@ -218,7 +218,7 @@ public final class H264Reader implements ElementaryStreamReader {
.setCodecs(codecs)
.setWidth(spsData.width)
.setHeight(spsData.height)
.setPixelWidthHeightRatio(spsData.pixelWidthAspectRatio)
.setPixelWidthHeightRatio(spsData.pixelWidthHeightRatio)
.setInitializationData(initializationData)
.build());
hasOutputFormat = true;
......
......@@ -247,10 +247,20 @@ public final class H265Reader implements ElementaryStreamReader {
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
int maxSubLayersMinus1 = bitArray.readBits(3);
bitArray.skipBit(); // 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 generalProfileSpace = bitArray.readBits(2);
boolean generalTierFlag = bitArray.readBit();
int generalProfileIdc = bitArray.readBits(5);
int generalProfileCompatibilityFlags = 0;
for (int i = 0; i < 32; i++) {
if (bitArray.readBit()) {
generalProfileCompatibilityFlags |= (1 << i);
}
}
int[] constraintBytes = new int[6];
for (int i = 0; i < constraintBytes.length; ++i) {
constraintBytes[i] = bitArray.readBits(8);
}
int generalLevelIdc = bitArray.readBits(8);
int toSkip = 0;
for (int i = 0; i < maxSubLayersMinus1; i++) {
if (bitArray.readBit()) { // sub_layer_profile_present_flag[i]
......@@ -360,10 +370,14 @@ public final class H265Reader implements ElementaryStreamReader {
}
}
// Parse the SPS to derive an RFC 6381 codecs string.
bitArray.reset(sps.nalData, 0, sps.nalLength);
bitArray.skipBits(24); // Skip start code.
String codecs = CodecSpecificDataUtil.buildHevcCodecStringFromSps(bitArray);
String codecs =
CodecSpecificDataUtil.buildHevcCodecString(
generalProfileSpace,
generalTierFlag,
generalProfileIdc,
generalProfileCompatibilityFlags,
constraintBytes,
generalLevelIdc);
return new Format.Builder()
.setId(formatId)
......
......@@ -28,13 +28,6 @@ import java.util.List;
/** AVC configuration data. */
public final class AvcConfig {
public final List<byte[]> initializationData;
public final int nalUnitLengthFieldLength;
public final int width;
public final int height;
public final float pixelWidthAspectRatio;
@Nullable public final String codecs;
/**
* Parses AVC configuration data.
*
......@@ -62,7 +55,7 @@ public final class AvcConfig {
int width = Format.NO_VALUE;
int height = Format.NO_VALUE;
float pixelWidthAspectRatio = 1;
float pixelWidthHeightRatio = 1;
@Nullable String codecs = null;
if (numSequenceParameterSets > 0) {
byte[] sps = initializationData.get(0);
......@@ -71,7 +64,7 @@ public final class AvcConfig {
initializationData.get(0), nalUnitLengthFieldLength, sps.length);
width = spsData.width;
height = spsData.height;
pixelWidthAspectRatio = spsData.pixelWidthAspectRatio;
pixelWidthHeightRatio = spsData.pixelWidthHeightRatio;
codecs =
CodecSpecificDataUtil.buildAvcCodecString(
spsData.profileIdc, spsData.constraintsFlagsAndReservedZero2Bits, spsData.levelIdc);
......@@ -82,25 +75,51 @@ public final class AvcConfig {
nalUnitLengthFieldLength,
width,
height,
pixelWidthAspectRatio,
pixelWidthHeightRatio,
codecs);
} catch (ArrayIndexOutOfBoundsException e) {
throw ParserException.createForMalformedContainer("Error parsing AVC config", e);
}
}
/**
* List of buffers containing the codec-specific data to be provided to the decoder.
*
* @see com.google.android.exoplayer2.Format#initializationData
*/
public final List<byte[]> initializationData;
/** The length of the NAL unit length field in the bitstream's container, in bytes. */
public final int nalUnitLengthFieldLength;
/** The width of each decoded frame, or {@link Format#NO_VALUE} if unknown. */
public final int width;
/** The height of each decoded frame, or {@link Format#NO_VALUE} if unknown. */
public final int height;
/** The pixel width to height ratio. */
public final float pixelWidthHeightRatio;
/**
* An RFC 6381 codecs string representing the video format, or {@code null} if not known.
*
* @see com.google.android.exoplayer2.Format#codecs
*/
@Nullable public final String codecs;
private AvcConfig(
List<byte[]> initializationData,
int nalUnitLengthFieldLength,
int width,
int height,
float pixelWidthAspectRatio,
float pixelWidthHeightRatio,
@Nullable String codecs) {
this.initializationData = initializationData;
this.nalUnitLengthFieldLength = nalUnitLengthFieldLength;
this.width = width;
this.height = height;
this.pixelWidthAspectRatio = pixelWidthAspectRatio;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.codecs = codecs;
}
......
......@@ -16,11 +16,11 @@
package com.google.android.exoplayer2.video;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.ParsableNalUnitBitArray;
import java.util.Collections;
import java.util.List;
......@@ -58,6 +58,9 @@ public final class HevcConfig {
data.setPosition(csdStartPosition);
byte[] buffer = new byte[csdLength];
int bufferPosition = 0;
int width = Format.NO_VALUE;
int height = Format.NO_VALUE;
float pixelWidthHeightRatio = 1;
@Nullable String codecs = null;
for (int i = 0; i < numberOfArrays; i++) {
int nalUnitType = data.readUnsignedByte() & 0x7F; // completeness (1), nal_unit_type (7)
......@@ -74,21 +77,30 @@ public final class HevcConfig {
System.arraycopy(
data.getData(), data.getPosition(), buffer, bufferPosition, nalUnitLength);
if (nalUnitType == SPS_NAL_UNIT_TYPE && j == 0) {
ParsableNalUnitBitArray bitArray =
new ParsableNalUnitBitArray(
buffer,
/* offset= */ bufferPosition,
/* limit= */ bufferPosition + nalUnitLength);
codecs = CodecSpecificDataUtil.buildHevcCodecStringFromSps(bitArray);
NalUnitUtil.H265SpsData spsData =
NalUnitUtil.parseH265SpsNalUnit(
buffer, bufferPosition, bufferPosition + nalUnitLength);
width = spsData.width;
height = spsData.height;
pixelWidthHeightRatio = spsData.pixelWidthHeightRatio;
codecs =
CodecSpecificDataUtil.buildHevcCodecString(
spsData.generalProfileSpace,
spsData.generalTierFlag,
spsData.generalProfileIdc,
spsData.generalProfileCompatibilityFlags,
spsData.constraintBytes,
spsData.generalLevelIdc);
}
bufferPosition += nalUnitLength;
data.skipBytes(nalUnitLength);
}
}
@Nullable
List<byte[]> initializationData = csdLength == 0 ? null : Collections.singletonList(buffer);
return new HevcConfig(initializationData, lengthSizeMinusOne + 1, codecs);
List<byte[]> initializationData =
csdLength == 0 ? Collections.emptyList() : Collections.singletonList(buffer);
return new HevcConfig(
initializationData, lengthSizeMinusOne + 1, width, height, pixelWidthHeightRatio, codecs);
} catch (ArrayIndexOutOfBoundsException e) {
throw ParserException.createForMalformedContainer("Error parsing HEVC config", e);
}
......@@ -97,14 +109,24 @@ public final class HevcConfig {
private static final int SPS_NAL_UNIT_TYPE = 33;
/**
* List of buffers containing the codec-specific data to be provided to the decoder, or {@code
* null} if not known.
* List of buffers containing the codec-specific data to be provided to the decoder.
*
* @see com.google.android.exoplayer2.Format#initializationData
*/
@Nullable public final List<byte[]> initializationData;
public final List<byte[]> initializationData;
/** The length of the NAL unit length field in the bitstream's container, in bytes. */
public final int nalUnitLengthFieldLength;
/** The width of each decoded frame, or {@link Format#NO_VALUE} if unknown. */
public final int width;
/** The height of each decoded frame, or {@link Format#NO_VALUE} if unknown. */
public final int height;
/** The pixel width to height ratio. */
public final float pixelWidthHeightRatio;
/**
* An RFC 6381 codecs string representing the video format, or {@code null} if not known.
*
......@@ -113,11 +135,17 @@ public final class HevcConfig {
@Nullable public final String codecs;
private HevcConfig(
@Nullable List<byte[]> initializationData,
List<byte[]> initializationData,
int nalUnitLengthFieldLength,
int width,
int height,
float pixelWidthHeightRatio,
@Nullable String codecs) {
this.initializationData = initializationData;
this.nalUnitLengthFieldLength = nalUnitLengthFieldLength;
this.width = width;
this.height = height;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.codecs = codecs;
}
}
......@@ -177,7 +177,7 @@ import com.google.common.collect.ImmutableMap;
NalUnitUtil.SpsData spsData =
NalUnitUtil.parseSpsNalUnit(
spsNalDataWithStartCode, NAL_START_CODE.length, spsNalDataWithStartCode.length);
formatBuilder.setPixelWidthHeightRatio(spsData.pixelWidthAspectRatio);
formatBuilder.setPixelWidthHeightRatio(spsData.pixelWidthHeightRatio);
formatBuilder.setHeight(spsData.height);
formatBuilder.setWidth(spsData.width);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment