diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java index 4831ec59e2..2492511fb9 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java @@ -38,6 +38,7 @@ public final class NalUnitUtil { public final int width; public final int height; public final float pixelWidthAspectRatio; + public final float frameRate; public final boolean separateColorPlaneFlag; public final boolean frameMbsOnlyFlag; public final int frameNumLength; @@ -53,6 +54,7 @@ public final class NalUnitUtil { int width, int height, float pixelWidthAspectRatio, + float frameRate, boolean separateColorPlaneFlag, boolean frameMbsOnlyFlag, int frameNumLength, @@ -66,6 +68,7 @@ public final class NalUnitUtil { this.width = width; this.height = height; this.pixelWidthAspectRatio = pixelWidthAspectRatio; + this.frameRate = frameRate; this.separateColorPlaneFlag = separateColorPlaneFlag; this.frameMbsOnlyFlag = frameMbsOnlyFlag; this.frameNumLength = frameNumLength; @@ -349,6 +352,7 @@ public final class NalUnitUtil { } float pixelWidthHeightRatio = 1; + float frameRate = -1; boolean vuiParametersPresentFlag = data.readBit(); if (vuiParametersPresentFlag) { boolean aspectRatioInfoPresentFlag = data.readBit(); @@ -366,6 +370,68 @@ public final class NalUnitUtil { Log.w(TAG, "Unexpected aspect_ratio_idc value: " + aspectRatioIdc); } } + // https://www.androidos.net.cn/android/9.0.0_r8/xref/external/mp4parser/isoparser/src/main/java/com/googlecode/mp4parser/h264/model/SeqParameterSet.java + boolean overscan_info_present_flag = data.readBit(); + if (overscan_info_present_flag) { + int overscan_appropriate_flagu = data.readBits(1); + } + boolean video_signal_type_present_flag = data.readBit(); + if (video_signal_type_present_flag) { + int video_format = data.readBits(3); + int video_full_range_flag = data.readBits(1); + boolean colour_description_present_flag = data.readBit(); + if (colour_description_present_flag) { + int colour_primaries = data.readBits(8); + int transfer_characteristics = data.readBits(8); + int matrix_coefficients = data.readBits(8); + } + } + boolean chroma_loc_info_present_flag = data.readBit(); + if (chroma_loc_info_present_flag) { + int chroma_sample_loc_type_top_field = data.readUnsignedExpGolombCodedInt(); + int chroma_sample_loc_type_bottom_field = data.readUnsignedExpGolombCodedInt(); + } else { + boolean timing_info_present_flag = data.readBit(); + if (timing_info_present_flag) { + int num_units_in_tick = data.readBits(32); + int time_scale = data.readBits(32); + frameRate = (float) time_scale / (float) num_units_in_tick; + boolean fixed_frame_rate_flag = data.readBit(); + if (fixed_frame_rate_flag) { + frameRate = frameRate / 2f; + } else if (frameRate > 40f) { + frameRate = frameRate / 2f; + } + } + boolean nal_hrd_parameters_present_flag = data.readBit(); + if (nal_hrd_parameters_present_flag) + readHRDParameters(data); //nalHRDParams + boolean vcl_hrd_parameters_present_flag = data.readBit(); + if (vcl_hrd_parameters_present_flag) + readHRDParameters(data); //vclHRDParams + if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) { + boolean low_delay_hrd_flag = data.readBit(); + } + boolean pic_struct_present_flag = data.readBit(); + boolean bitstream_restriction_flag = data.readBit(); + if (bitstream_restriction_flag) {/* + vuip.bitstreamRestriction = new VUIParameters.BitstreamRestriction(); + vuip.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = reader + .readBool("VUI: motion_vectors_over_pic_boundaries_flag"); + vuip.bitstreamRestriction.max_bytes_per_pic_denom = reader + .readUE("VUI max_bytes_per_pic_denom"); + vuip.bitstreamRestriction.max_bits_per_mb_denom = reader + .readUE("VUI max_bits_per_mb_denom"); + vuip.bitstreamRestriction.log2_max_mv_length_horizontal = reader + .readUE("VUI log2_max_mv_length_horizontal"); + vuip.bitstreamRestriction.log2_max_mv_length_vertical = reader + .readUE("VUI log2_max_mv_length_vertical"); + vuip.bitstreamRestriction.num_reorder_frames = reader + .readUE("VUI num_reorder_frames"); + vuip.bitstreamRestriction.max_dec_frame_buffering = reader + .readUE("VUI max_dec_frame_buffering");*/ + } + } } return new SpsData( @@ -376,6 +442,7 @@ public final class NalUnitUtil { frameWidth, frameHeight, pixelWidthHeightRatio, + frameRate, separateColorPlaneFlag, frameMbsOnlyFlag, frameNumLength, @@ -384,6 +451,29 @@ public final class NalUnitUtil { deltaPicOrderAlwaysZeroFlag); } + private static void readHRDParameters(ParsableNalUnitBitArray data) { +// HRDParameters hrd = new HRDParameters(); +// hrd.cpb_cnt_minus1 = reader.readUE("SPS: cpb_cnt_minus1"); + int cpb_cnt_minus1 = data.readUnsignedExpGolombCodedInt(); +// hrd.bit_rate_scale = (int) reader.readNBit(4, "HRD: bit_rate_scale"); + int bit_rate_scale = data.readBits(4); +// hrd.cpb_size_scale = (int) reader.readNBit(4, "HRD: cpb_size_scale"); + int cpb_size_scale = data.readBits(4); + int[] bit_rate_value_minus1 = new int[cpb_cnt_minus1 + 1]; + int[] cpb_size_value_minus1 = new int[cpb_cnt_minus1 + 1]; + boolean[] cbr_flag = new boolean[cpb_cnt_minus1 + 1]; + + for (int SchedSelIdx = 0; SchedSelIdx <= cpb_cnt_minus1; SchedSelIdx++) { + bit_rate_value_minus1[SchedSelIdx] = data.readUnsignedExpGolombCodedInt(); + cpb_size_value_minus1[SchedSelIdx] = data.readUnsignedExpGolombCodedInt(); + cbr_flag[SchedSelIdx] = data.readBit(); + } + int initial_cpb_removal_delay_length_minus1 = (int) data.readBits(5); + int cpb_removal_delay_length_minus1 = data.readBits(5); + int dpb_output_delay_length_minus1 = data.readBits(5); + int time_offset_length = data.readBits(5); + } + /** * Parses a PPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection * 7.3.2.2. diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java b/library/common/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java index b794d2db90..88641dc725 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java @@ -34,6 +34,7 @@ public final class AvcConfig { public final int width; public final int height; public final float pixelWidthAspectRatio; + public final float frameRate; /** * Parses AVC configuration data. @@ -63,6 +64,7 @@ public final class AvcConfig { int width = Format.NO_VALUE; int height = Format.NO_VALUE; float pixelWidthAspectRatio = 1; + float frameRate = -1; if (numSequenceParameterSets > 0) { byte[] sps = initializationData.get(0); SpsData spsData = NalUnitUtil.parseSpsNalUnit(initializationData.get(0), @@ -70,21 +72,23 @@ public final class AvcConfig { width = spsData.width; height = spsData.height; pixelWidthAspectRatio = spsData.pixelWidthAspectRatio; + frameRate = spsData.frameRate; } return new AvcConfig(initializationData, nalUnitLengthFieldLength, width, height, - pixelWidthAspectRatio); + pixelWidthAspectRatio, frameRate); } catch (ArrayIndexOutOfBoundsException e) { throw new ParserException("Error parsing AVC config", e); } } private AvcConfig(List initializationData, int nalUnitLengthFieldLength, - int width, int height, float pixelWidthAspectRatio) { + int width, int height, float pixelWidthAspectRatio, float frameRate) { this.initializationData = initializationData; this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; this.width = width; this.height = height; this.pixelWidthAspectRatio = pixelWidthAspectRatio; + this.frameRate = frameRate; } private static byte[] buildNalUnitForChild(ParsableByteArray data) { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java index c91f6ce037..b9fa5aa5d9 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java @@ -97,6 +97,7 @@ import com.google.android.exoplayer2.video.AvcConfig; .setHeight(avcConfig.height) .setPixelWidthHeightRatio(avcConfig.pixelWidthAspectRatio) .setInitializationData(avcConfig.initializationData) + .setFrameRate(avcConfig.frameRate < 0 ? Format.NO_VALUE : avcConfig.frameRate) .build(); output.format(format); hasOutputFormat = true; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 660605ebe5..a677625d85 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -152,6 +152,7 @@ public class MatroskaExtractor implements Extractor { private static final int ID_INFO = 0x1549A966; private static final int ID_TIMECODE_SCALE = 0x2AD7B1; private static final int ID_DURATION = 0x4489; + private static final int ID_FRAME_RATE = 0x2383E3; private static final int ID_CLUSTER = 0x1F43B675; private static final int ID_TIME_CODE = 0xE7; private static final int ID_SIMPLE_BLOCK = 0xA3; @@ -589,6 +590,7 @@ public class MatroskaExtractor implements Extractor { case ID_BLOCK_ADDITIONAL: return EbmlProcessor.ELEMENT_TYPE_BINARY; case ID_DURATION: + case ID_FRAME_RATE: case ID_SAMPLING_FREQUENCY: case ID_PRIMARY_R_CHROMATICITY_X: case ID_PRIMARY_R_CHROMATICITY_Y: @@ -828,6 +830,7 @@ public class MatroskaExtractor implements Extractor { break; case ID_DEFAULT_DURATION: currentTrack.defaultSampleDurationNs = (int) value; + currentTrack.frameRate = (float) 1E9 / value; break; case ID_MAX_BLOCK_ADDITION_ID: currentTrack.maxBlockAdditionId = (int) value; @@ -1008,6 +1011,9 @@ public class MatroskaExtractor implements Extractor { case ID_DURATION: durationTimecode = (long) value; break; + case ID_FRAME_RATE: + currentTrack.frameRate = (float) value; + break; case ID_SAMPLING_FREQUENCY: currentTrack.sampleRate = (int) value; break; @@ -1932,6 +1938,7 @@ public class MatroskaExtractor implements Extractor { public int displayWidth = Format.NO_VALUE; public int displayHeight = Format.NO_VALUE; public int displayUnit = DISPLAY_UNIT_PIXELS; + public float frameRate = -1; @C.Projection public int projectionType = Format.NO_VALUE; public float projectionPoseYaw = 0f; public float projectionPosePitch = 0f; @@ -2009,6 +2016,7 @@ public class MatroskaExtractor implements Extractor { AvcConfig avcConfig = AvcConfig.parse(new ParsableByteArray(codecPrivate)); initializationData = avcConfig.initializationData; nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; + frameRate = avcConfig.frameRate; break; case CODEC_ID_H265: mimeType = MimeTypes.VIDEO_H265; @@ -2195,6 +2203,7 @@ public class MatroskaExtractor implements Extractor { .setRotationDegrees(rotationDegrees) .setProjectionData(projectionData) .setStereoMode(stereoMode) + .setFrameRate(frameRate < 0 ? Format.NO_VALUE : frameRate) .setColorInfo(colorInfo); } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { type = C.TRACK_TYPE_TEXT; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 6eed09760e..c420fd8ba1 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -968,6 +968,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; int height = parent.readUnsignedShort(); boolean pixelWidthHeightRatioFromPasp = false; float pixelWidthHeightRatio = 1; + float frameRate = -1; parent.skipBytes(50); int childPosition = parent.getPosition(); @@ -1014,6 +1015,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; if (!pixelWidthHeightRatioFromPasp) { pixelWidthHeightRatio = avcConfig.pixelWidthAspectRatio; } + frameRate = avcConfig.frameRate; } else if (childAtomType == Atom.TYPE_hvcC) { Assertions.checkState(mimeType == null); mimeType = MimeTypes.VIDEO_H265; @@ -1094,6 +1096,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; .setStereoMode(stereoMode) .setInitializationData(initializationData) .setDrmInitData(drmInitData) + .setFrameRate(frameRate < 0 ? Format.NO_VALUE : frameRate) .build(); } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java index d0bf2067c9..b6a0fc7ead 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java @@ -215,6 +215,7 @@ public final class H264Reader implements ElementaryStreamReader { .setHeight(spsData.height) .setPixelWidthHeightRatio(spsData.pixelWidthAspectRatio) .setInitializationData(initializationData) + .setFrameRate(spsData.frameRate < 0 ? Format.NO_VALUE : spsData.frameRate) .build()); hasOutputFormat = true; sampleReader.putSps(spsData);