summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/H262Reader.java
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/H262Reader.java
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/H262Reader.java')
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/H262Reader.java333
1 files changed, 333 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/H262Reader.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/H262Reader.java
new file mode 100644
index 0000000000..576607366e
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/H262Reader.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ts;
+
+import android.util.Pair;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorOutput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.TrackOutput;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.MimeTypes;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.NalUnitUtil;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Parses a continuous H262 byte stream and extracts individual frames.
+ */
+public final class H262Reader implements ElementaryStreamReader {
+
+ private static final int START_PICTURE = 0x00;
+ private static final int START_SEQUENCE_HEADER = 0xB3;
+ private static final int START_EXTENSION = 0xB5;
+ private static final int START_GROUP = 0xB8;
+ private static final int START_USER_DATA = 0xB2;
+
+ private String formatId;
+ private TrackOutput output;
+
+ // Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4.
+ private static final double[] FRAME_RATE_VALUES = new double[] {
+ 24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60};
+
+ // State that should not be reset on seek.
+ private boolean hasOutputFormat;
+ private long frameDurationUs;
+
+ private final UserDataReader userDataReader;
+ private final ParsableByteArray userDataParsable;
+
+ // State that should be reset on seek.
+ private final boolean[] prefixFlags;
+ private final CsdBuffer csdBuffer;
+ private final NalUnitTargetBuffer userData;
+ private long totalBytesWritten;
+ private boolean startedFirstSample;
+
+ // Per packet state that gets reset at the start of each packet.
+ private long pesTimeUs;
+
+ // Per sample state that gets reset at the start of each sample.
+ private long samplePosition;
+ private long sampleTimeUs;
+ private boolean sampleIsKeyframe;
+ private boolean sampleHasPicture;
+
+ public H262Reader() {
+ this(null);
+ }
+
+ /* package */ H262Reader(UserDataReader userDataReader) {
+ this.userDataReader = userDataReader;
+ prefixFlags = new boolean[4];
+ csdBuffer = new CsdBuffer(128);
+ if (userDataReader != null) {
+ userData = new NalUnitTargetBuffer(START_USER_DATA, 128);
+ userDataParsable = new ParsableByteArray();
+ } else {
+ userData = null;
+ userDataParsable = null;
+ }
+ }
+
+ @Override
+ public void seek() {
+ NalUnitUtil.clearPrefixFlags(prefixFlags);
+ csdBuffer.reset();
+ if (userDataReader != null) {
+ userData.reset();
+ }
+ totalBytesWritten = 0;
+ startedFirstSample = false;
+ }
+
+ @Override
+ public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
+ idGenerator.generateNewId();
+ formatId = idGenerator.getFormatId();
+ output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO);
+ if (userDataReader != null) {
+ userDataReader.createTracks(extractorOutput, idGenerator);
+ }
+ }
+
+ @Override
+ public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
+ // TODO (Internal b/32267012): Consider using random access indicator.
+ this.pesTimeUs = pesTimeUs;
+ }
+
+ @Override
+ public void consume(ParsableByteArray data) {
+ 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());
+
+ while (true) {
+ int startCodeOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags);
+
+ if (startCodeOffset == limit) {
+ // We've scanned to the end of the data without finding another start code.
+ if (!hasOutputFormat) {
+ csdBuffer.onData(dataArray, offset, limit);
+ }
+ if (userDataReader != null) {
+ userData.appendToNalUnit(dataArray, offset, limit);
+ }
+ return;
+ }
+
+ // We've found a start code with the following value.
+ int startCodeValue = data.data[startCodeOffset + 3] & 0xFF;
+ // This is the number of bytes from the current offset to the start of the next start
+ // code. It may be negative if the start code started in the previously consumed data.
+ int lengthToStartCode = startCodeOffset - offset;
+
+ if (!hasOutputFormat) {
+ if (lengthToStartCode > 0) {
+ csdBuffer.onData(dataArray, offset, startCodeOffset);
+ }
+ // This is the number of bytes belonging to the next start code that have already been
+ // passed to csdBuffer.
+ int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
+ if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
+ // The csd data is complete, so we can decode and output the media format.
+ Pair<Format, Long> result = parseCsdBuffer(csdBuffer, formatId);
+ output.format(result.first);
+ frameDurationUs = result.second;
+ hasOutputFormat = true;
+ }
+ }
+ if (userDataReader != null) {
+ int bytesAlreadyPassed = 0;
+ if (lengthToStartCode > 0) {
+ userData.appendToNalUnit(dataArray, offset, startCodeOffset);
+ } else {
+ bytesAlreadyPassed = -lengthToStartCode;
+ }
+
+ if (userData.endNalUnit(bytesAlreadyPassed)) {
+ int unescapedLength = NalUnitUtil.unescapeStream(userData.nalData, userData.nalLength);
+ userDataParsable.reset(userData.nalData, unescapedLength);
+ userDataReader.consume(sampleTimeUs, userDataParsable);
+ }
+
+ if (startCodeValue == START_USER_DATA && data.data[startCodeOffset + 2] == 0x1) {
+ userData.startNalUnit(startCodeValue);
+ }
+ }
+ if (startCodeValue == START_PICTURE || startCodeValue == START_SEQUENCE_HEADER) {
+ int bytesWrittenPastStartCode = limit - startCodeOffset;
+ if (startedFirstSample && sampleHasPicture && hasOutputFormat) {
+ // Output the sample.
+ @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
+ int size = (int) (totalBytesWritten - samplePosition) - bytesWrittenPastStartCode;
+ output.sampleMetadata(sampleTimeUs, flags, size, bytesWrittenPastStartCode, null);
+ }
+ if (!startedFirstSample || sampleHasPicture) {
+ // Start the next sample.
+ samplePosition = totalBytesWritten - bytesWrittenPastStartCode;
+ sampleTimeUs = pesTimeUs != C.TIME_UNSET ? pesTimeUs
+ : (startedFirstSample ? (sampleTimeUs + frameDurationUs) : 0);
+ sampleIsKeyframe = false;
+ pesTimeUs = C.TIME_UNSET;
+ startedFirstSample = true;
+ }
+ sampleHasPicture = startCodeValue == START_PICTURE;
+ } else if (startCodeValue == START_GROUP) {
+ sampleIsKeyframe = true;
+ }
+
+ offset = startCodeOffset + 3;
+ }
+ }
+
+ @Override
+ public void packetFinished() {
+ // Do nothing.
+ }
+
+ /**
+ * Parses the {@link Format} and frame duration from a csd buffer.
+ *
+ * @param csdBuffer The csd buffer.
+ * @param formatId The id for the generated format. May be null.
+ * @return A pair consisting of the {@link Format} and the frame duration in microseconds, or
+ * 0 if the duration could not be determined.
+ */
+ private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, String formatId) {
+ byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
+
+ int firstByte = csdData[4] & 0xFF;
+ int secondByte = csdData[5] & 0xFF;
+ int thirdByte = csdData[6] & 0xFF;
+ int width = (firstByte << 4) | (secondByte >> 4);
+ int height = (secondByte & 0x0F) << 8 | thirdByte;
+
+ float pixelWidthHeightRatio = 1f;
+ int aspectRatioCode = (csdData[7] & 0xF0) >> 4;
+ switch(aspectRatioCode) {
+ case 2:
+ pixelWidthHeightRatio = (4 * height) / (float) (3 * width);
+ break;
+ case 3:
+ pixelWidthHeightRatio = (16 * height) / (float) (9 * width);
+ break;
+ case 4:
+ pixelWidthHeightRatio = (121 * height) / (float) (100 * width);
+ break;
+ default:
+ // Do nothing.
+ break;
+ }
+
+ Format format = Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_MPEG2, null,
+ Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE,
+ Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null);
+
+ long frameDurationUs = 0;
+ int frameRateCodeMinusOne = (csdData[7] & 0x0F) - 1;
+ if (0 <= frameRateCodeMinusOne && frameRateCodeMinusOne < FRAME_RATE_VALUES.length) {
+ double frameRate = FRAME_RATE_VALUES[frameRateCodeMinusOne];
+ int sequenceExtensionPosition = csdBuffer.sequenceExtensionPosition;
+ int frameRateExtensionN = (csdData[sequenceExtensionPosition + 9] & 0x60) >> 5;
+ int frameRateExtensionD = (csdData[sequenceExtensionPosition + 9] & 0x1F);
+ if (frameRateExtensionN != frameRateExtensionD) {
+ frameRate *= (frameRateExtensionN + 1d) / (frameRateExtensionD + 1);
+ }
+ frameDurationUs = (long) (C.MICROS_PER_SECOND / frameRate);
+ }
+
+ return Pair.create(format, frameDurationUs);
+ }
+
+ private static final class CsdBuffer {
+
+ private static final byte[] START_CODE = new byte[] {0, 0, 1};
+
+ private boolean isFilling;
+
+ public int length;
+ public int sequenceExtensionPosition;
+ public byte[] data;
+
+ public CsdBuffer(int initialCapacity) {
+ data = new byte[initialCapacity];
+ }
+
+ /**
+ * Resets the buffer, clearing any data that it holds.
+ */
+ public void reset() {
+ isFilling = false;
+ length = 0;
+ sequenceExtensionPosition = 0;
+ }
+
+ /**
+ * Called when a start code is encountered in the stream.
+ *
+ * @param startCodeValue The start code value.
+ * @param bytesAlreadyPassed The number of bytes of the start code that have been passed to
+ * {@link #onData(byte[], int, int)}, or 0.
+ * @return Whether the csd data is now complete. If true is returned, neither
+ * this method nor {@link #onData(byte[], int, int)} should be called again without an
+ * interleaving call to {@link #reset()}.
+ */
+ public boolean onStartCode(int startCodeValue, int bytesAlreadyPassed) {
+ if (isFilling) {
+ length -= bytesAlreadyPassed;
+ if (sequenceExtensionPosition == 0 && startCodeValue == START_EXTENSION) {
+ sequenceExtensionPosition = length;
+ } else {
+ isFilling = false;
+ return true;
+ }
+ } else if (startCodeValue == START_SEQUENCE_HEADER) {
+ isFilling = true;
+ }
+ onData(START_CODE, 0, START_CODE.length);
+ return false;
+ }
+
+ /**
+ * Called to pass stream data.
+ *
+ * @param newData 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 onData(byte[] newData, int offset, int limit) {
+ if (!isFilling) {
+ return;
+ }
+ int readLength = limit - offset;
+ if (data.length < length + readLength) {
+ data = Arrays.copyOf(data, (length + readLength) * 2);
+ }
+ System.arraycopy(newData, offset, data, length, readLength);
+ length += readLength;
+ }
+
+ }
+
+}