All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.red5.io.obu.OBUParser Maven / Gradle / Ivy

There is a newer version: 2.0.15
Show newest version
package org.red5.io.obu;

import static org.red5.io.obu.OBPConstants.OBU_FRAME_TYPE_BITSHIFT;
import static org.red5.io.obu.OBPConstants.OBU_FRAME_TYPE_MASK;
import static org.red5.io.obu.OBUType.FRAME;
import static org.red5.io.obu.OBUType.FRAME_HEADER;
import static org.red5.io.obu.OBUType.METADATA;
import static org.red5.io.obu.OBUType.PADDING;
import static org.red5.io.obu.OBUType.REDUNDANT_FRAME_HEADER;
import static org.red5.io.obu.OBUType.SEQUENCE_HEADER;
import static org.red5.io.obu.OBUType.TEMPORAL_DELIMITER;
import static org.red5.io.obu.OBUType.TILE_GROUP;
import static org.red5.io.obu.OBUType.TILE_LIST;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import org.red5.io.utils.HexDump;
import org.red5.io.utils.LEB128;
import org.red5.io.utils.LEB128.LEB128Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Parsers OBU providing headers and extract relevant data. Logic is derived from the C code in the obuparser project.
 *
 * @author Paul Gregoire
 */
public class OBUParser {

    private static final Logger log = LoggerFactory.getLogger(OBUParser.class);

    private static final Set VALID_OBU_TYPES = Set.of(SEQUENCE_HEADER.getValue(), TEMPORAL_DELIMITER.getValue(), FRAME_HEADER.getValue(), TILE_GROUP.getValue(), METADATA.getValue(), FRAME.getValue(), REDUNDANT_FRAME_HEADER.getValue(), TILE_LIST.getValue(), PADDING.getValue());

    public static final byte OBU_START_FRAGMENT_BIT = (byte) 0b1000_0000; // 0b1'0000'000

    public static final byte OBU_END_FRAGMENT_BIT = 0b0100_0000;

    public static final byte OBU_START_SEQUENCE_BIT = 0b0000_1000;

    public static final byte OBU_COUNT_MASK = 0b0011_0000;

    public static final byte OBU_TYPE_MASK = 0b0111_1000;

    public static final byte OBU_SIZE_PRESENT_BIT = 0b0000_0010; // 0b0'0000'010

    public static final byte OBU_EXT_BIT = 0b0000_0100; // 0b0'0000'100

    public static final byte OBU_EXT_S1T1_BIT = 0b0010_1000; // 0b001'01'000

    public static final byte OBU_TYPE_SHIFT = 3;

    // constexpr uint8_t kAv1ObuTypeSequenceHeader = 1 << 3;
    // constexpr uint8_t kAv1ObuTypeTemporalDelimiter = 2 << 3;
    // constexpr uint8_t kAv1ObuTypeFrameHeader = 3 << 3;
    // constexpr uint8_t kAv1ObuTypeTileGroup = 4 << 3;
    // constexpr uint8_t kAv1ObuTypeMetadata = 5 << 3;
    // constexpr uint8_t kAv1ObuTypeFrame = 6 << 3;
    // constexpr uint8_t kAv1ObuTypeTileList = 8 << 3;
    // constexpr uint8_t kAv1ObuExtensionPresentBit = 0b0'0000'100;
    // constexpr uint8_t kAv1ObuSizePresentBit = 0b0'0000'010;
    // constexpr uint8_t kAv1ObuExtensionS1T1 = 0b001'01'000;

    /*
    * obp_get_next_obu parses the next OBU header in a packet containing a set of one or more OBUs
    * (e.g. an IVF or ISOBMFF packet) and returns its location in the buffer, as well as all
    * relevant data from the header.
    *
    * Input:
    *     buf      - Input packet buffer.
    *     buf_size - Size of the input packet buffer.
    *     err      - An error buffer and buffer size to write any error messages into.
    *
    * Output:
    *     obu_type    - The type of OBU.
    *     offset      - The offset into the buffer where this OBU starts, excluding the OBU header.
    *     obu_size    - The size of the OBU, excluding the size of the OBU header.
    *     temporal_id - The temporal ID of the OBU.
    *     spatial_id  - The spatial ID of the OBU.
    *
    * Returns:
    *     0 on success, -1 on error.
    */
    public static OBUInfo getNextObu(byte[] buf, int offset, int bufSize) throws OBUParseException {
        log.trace("getNextObu - buffer length: {} size: {} offset: {}", buf.length, bufSize, offset);
        if (bufSize < 1) {
            throw new OBUParseException("Buffer is too small to contain an OBU");
        }
        if (buf.length < (offset + 1)) {
            throw new OBUParseException("Buffer is too small for given offset");
        }
        int pos = offset;
        int obuType = (buf[pos] & OBU_FRAME_TYPE_MASK) >>> OBU_FRAME_TYPE_BITSHIFT;
        if (!isValidObu(obuType)) {
            log.warn("OBU header contains invalid OBU type: {} data: {}", obuType, HexDump.byteArrayToHexString(buf));
            throw new OBUParseException("OBU header contains invalid OBU type: " + obuType);
        }
        OBUInfo info = new OBUInfo(OBUType.fromValue(obuType), ByteBuffer.allocate(1180));
        boolean obuExtensionFlag = obuHasExtension(buf[pos]);
        boolean obuHasSizeField = obuHasSize(buf[pos]);
        log.trace("OBU type: {} extension? {} size field? {}", info.obuType, obuExtensionFlag, obuHasSizeField);
        pos++; // move past the OBU header
        if (obuExtensionFlag) {
            if (bufSize < pos + 1) {
                throw new OBUParseException("Buffer is too small to contain an OBU extension header");
            }
            info.temporalId = (buf[pos] & 0xE0) >> 5;
            info.spatialId = (buf[pos] & 0x18) >> 3;
            log.trace("Temporal id: {} spatial id: {}", info.temporalId, info.spatialId);
            pos++; // move past the OBU extension header
        }
        if (obuHasSizeField) {
            byte[] lengthBytes = new byte[buf[pos] == 127 ? 2 : 1];
            System.arraycopy(buf, pos, lengthBytes, 0, lengthBytes.length);
            LEB128Result result = LEB128.decode(lengthBytes);
            pos += result.bytesRead;
            info.size = result.value;
            log.trace("OBU had size field: {}", info.size);
        } else {
            info.size = bufSize - pos;
        }
        log.trace("OBU size: {}", info.size);
        info.data = ByteBuffer.wrap(Arrays.copyOfRange(buf, pos, (pos + info.size)));
        if (info.size > bufSize - pos) {
            throw new OBUParseException("Invalid OBU size: larger than remaining buffer");
        }
        return info;
    }

    /*
     * obp_parse_sequence_header parses a sequence header OBU and fills out the fields in a
     * user-provided OBPSequenceHeader structure.
     *
     * Input:
     *     buf      - Input OBU buffer. This is expected to *NOT* contain the OBU header.
     *     buf_size - Size of the input OBU buffer.
     *     err      - An error buffer and buffer size to write any error messages into.
     *
     * Output:
     *     seq_header - A user provided structure that will be filled in with all the parsed data.
     *
     * Returns:
     *     0 on success, -1 on error.
     */
    public static OBPSequenceHeader parseSequenceHeader(byte[] buf, int bufSize) throws OBUParseException {
        BitReader br = new BitReader(buf, bufSize);
        OBPSequenceHeader seq = new OBPSequenceHeader();

        seq.seqProfile = (byte) br.readBits(3);
        seq.stillPicture = br.readBits(1) != 0;
        seq.reducedStillPictureHeader = br.readBits(1) != 0;

        if (seq.reducedStillPictureHeader) {
            seq.timingInfoPresentFlag = false;
            seq.decoderModelInfoPresentFlag = false;
            seq.initialDisplayDelayPresentFlag = false;
            seq.operatingPointsCntMinus1 = 0;
            seq.operatingPointIdc[0] = 0;
            seq.seqLevelIdx[0] = 0;
            seq.seqTier[0] = 0;
            seq.decoderModelPresentForThisOp[0] = false;
            seq.initialDisplayDelayPresentForThisOp[0] = false;
        } else {
            seq.timingInfoPresentFlag = br.readBits(1) != 0;
            if (seq.timingInfoPresentFlag) {
                seq.timingInfo = new OBPSequenceHeader.TimingInfo();
                seq.timingInfo.numUnitsInDisplayTick = br.readBits(32);
                seq.timingInfo.timeScale = br.readBits(32);
                seq.timingInfo.equalPictureInterval = br.readBits(1) != 0;
                if (seq.timingInfo.equalPictureInterval) {
                    seq.timingInfo.numTicksPerPictureMinus1 = readUvlc(br);
                }
                seq.decoderModelInfoPresentFlag = br.readBits(1) != 0;
                if (seq.decoderModelInfoPresentFlag) {
                    seq.decoderModelInfo = new OBPSequenceHeader.DecoderModelInfo();
                    seq.decoderModelInfo.bufferDelayLengthMinus1 = (byte) br.readBits(5);
                    seq.decoderModelInfo.numUnitsInDecodingTick = br.readBits(32);
                    seq.decoderModelInfo.bufferRemovalTimeLengthMinus1 = (byte) br.readBits(5);
                    seq.decoderModelInfo.framePresentationTimeLengthMinus1 = (byte) br.readBits(5);
                }
            } else {
                seq.decoderModelInfoPresentFlag = false;
            }
            seq.initialDisplayDelayPresentFlag = br.readBits(1) != 0;
            seq.operatingPointsCntMinus1 = (byte) br.readBits(5);
            for (int i = 0; i <= seq.operatingPointsCntMinus1; i++) {
                seq.operatingPointIdc[i] = (byte) br.readBits(12);
                seq.seqLevelIdx[i] = (byte) br.readBits(5);
                if (seq.seqLevelIdx[i] > 7) {
                    seq.seqTier[i] = (byte) br.readBits(1);
                } else {
                    seq.seqTier[i] = 0;
                }
                if (seq.decoderModelInfoPresentFlag) {
                    seq.decoderModelPresentForThisOp[i] = br.readBits(1) != 0;
                    if (seq.decoderModelPresentForThisOp[i]) {
                        seq.operatingParametersInfo[i] = new OBPSequenceHeader.OperatingParametersInfo();
                        int n = seq.decoderModelInfo.bufferDelayLengthMinus1 + 1;
                        seq.operatingParametersInfo[i].decoderBufferDelay = br.readBits(n);
                        seq.operatingParametersInfo[i].encoderBufferDelay = br.readBits(n);
                        seq.operatingParametersInfo[i].lowDelayModeFlag = br.readBits(1) != 0;
                    }
                } else {
                    seq.decoderModelPresentForThisOp[i] = false;
                }
                if (seq.initialDisplayDelayPresentFlag) {
                    seq.initialDisplayDelayPresentForThisOp[i] = br.readBits(1) != 0;
                    if (seq.initialDisplayDelayPresentForThisOp[i]) {
                        seq.initialDisplayDelayMinus1[i] = (byte) br.readBits(4);
                    }
                }
            }
        }

        seq.frameWidthBitsMinus1 = (byte) br.readBits(4);
        seq.frameHeightBitsMinus1 = (byte) br.readBits(4);
        seq.maxFrameWidthMinus1 = (int) br.readBits(seq.frameWidthBitsMinus1 + 1);
        seq.maxFrameHeightMinus1 = (int) br.readBits(seq.frameHeightBitsMinus1 + 1);

        if (seq.reducedStillPictureHeader) {
            seq.frameIdNumbersPresentFlag = false;
        } else {
            seq.frameIdNumbersPresentFlag = br.readBits(1) != 0;
        }

        if (seq.frameIdNumbersPresentFlag) {
            seq.deltaFrameIdLengthMinus2 = (byte) br.readBits(4);
            seq.additionalFrameIdLengthMinus1 = (byte) br.readBits(3);
        }

        seq.use128x128Superblock = br.readBits(1) != 0;
        seq.enableFilterIntra = br.readBits(1) != 0;
        seq.enableIntraEdgeFilter = br.readBits(1) != 0;

        if (seq.reducedStillPictureHeader) {
            seq.enableInterintraCompound = false;
            seq.enableMaskedCompound = false;
            seq.enableWarpedMotion = false;
            seq.enableDualFilter = false;
            seq.enableOrderHint = false;
            seq.enableJntComp = false;
            seq.enableRefFrameMvs = false;
            seq.seqForceScreenContentTools = 2;
            seq.seqForceIntegerMv = 2;
            seq.OrderHintBits = 0;
        } else {
            seq.enableInterintraCompound = br.readBits(1) != 0;
            seq.enableMaskedCompound = br.readBits(1) != 0;
            seq.enableWarpedMotion = br.readBits(1) != 0;
            seq.enableDualFilter = br.readBits(1) != 0;
            seq.enableOrderHint = br.readBits(1) != 0;
            if (seq.enableOrderHint) {
                seq.enableJntComp = br.readBits(1) != 0;
                seq.enableRefFrameMvs = br.readBits(1) != 0;
            } else {
                seq.enableJntComp = false;
                seq.enableRefFrameMvs = false;
            }
            seq.seqChooseScreenContentTools = br.readBits(1) != 0;
            if (seq.seqChooseScreenContentTools) {
                seq.seqForceScreenContentTools = 2;
            } else {
                seq.seqForceScreenContentTools = (int) br.readBits(1);
            }
            if (seq.seqForceScreenContentTools > 0) {
                seq.seqChooseIntegerMv = br.readBits(1) != 0;
                if (seq.seqChooseIntegerMv) {
                    seq.seqForceIntegerMv = 2;
                } else {
                    seq.seqForceIntegerMv = (int) br.readBits(1);
                }
            } else {
                seq.seqForceIntegerMv = 2;
            }
            if (seq.enableOrderHint) {
                seq.orderHintBitsMinus1 = (byte) br.readBits(3);
                seq.OrderHintBits = (byte) (seq.orderHintBitsMinus1 + 1);
            } else {
                seq.OrderHintBits = 0;
            }
        }

        seq.enableSuperres = br.readBits(1) != 0;
        seq.enableCdef = br.readBits(1) != 0;
        seq.enableRestoration = br.readBits(1) != 0;

        seq.colorConfig = new OBPSequenceHeader.ColorConfig();
        seq.colorConfig.highBitdepth = br.readBits(1) != 0;
        if (seq.seqProfile == 2 && seq.colorConfig.highBitdepth) {
            seq.colorConfig.twelveBit = br.readBits(1) != 0;
            seq.colorConfig.BitDepth = seq.colorConfig.twelveBit ? (byte) 12 : (byte) 10;
        } else {
            seq.colorConfig.BitDepth = seq.colorConfig.highBitdepth ? (byte) 10 : (byte) 8;
        }
        if (seq.seqProfile == 1) {
            seq.colorConfig.monoChrome = false;
        } else {
            seq.colorConfig.monoChrome = br.readBits(1) != 0;
        }
        seq.colorConfig.NumPlanes = seq.colorConfig.monoChrome ? (byte) 1 : (byte) 3;
        seq.colorConfig.colorDescriptionPresentFlag = br.readBits(1) != 0;
        if (seq.colorConfig.colorDescriptionPresentFlag) {
            seq.colorConfig.colorPrimaries = OBPColorPrimaries.values()[br.readBits(8)];
            seq.colorConfig.transferCharacteristics = OBPTransferCharacteristics.values()[br.readBits(8)];
            seq.colorConfig.matrixCoefficients = OBPMatrixCoefficients.values()[br.readBits(8)];
        } else {
            seq.colorConfig.colorPrimaries = OBPColorPrimaries.CP_UNSPECIFIED;
            seq.colorConfig.transferCharacteristics = OBPTransferCharacteristics.TC_UNSPECIFIED;
            seq.colorConfig.matrixCoefficients = OBPMatrixCoefficients.MC_UNSPECIFIED;
        }
        if (seq.colorConfig.monoChrome) {
            seq.colorConfig.colorRange = br.readBits(1) != 0;
            seq.colorConfig.subsamplingX = true;
            seq.colorConfig.subsamplingY = true;
            seq.colorConfig.chromaSamplePosition = OBPChromaSamplePosition.CSP_UNKNOWN;
            seq.colorConfig.separateUvDeltaQ = false;
        } else if (seq.colorConfig.colorPrimaries == OBPColorPrimaries.CP_BT_709 && seq.colorConfig.transferCharacteristics == OBPTransferCharacteristics.TC_SRGB && seq.colorConfig.matrixCoefficients == OBPMatrixCoefficients.MC_IDENTITY) {
            seq.colorConfig.colorRange = true;
            seq.colorConfig.subsamplingX = false;
            seq.colorConfig.subsamplingY = false;
        } else {
            seq.colorConfig.colorRange = br.readBits(1) != 0;
            if (seq.seqProfile == 0) {
                seq.colorConfig.subsamplingX = true;
                seq.colorConfig.subsamplingY = true;
            } else if (seq.seqProfile == 1) {
                seq.colorConfig.subsamplingX = false;
                seq.colorConfig.subsamplingY = false;
            } else {
                if (seq.colorConfig.BitDepth == 12) {
                    seq.colorConfig.subsamplingX = br.readBits(1) != 0;
                    if (seq.colorConfig.subsamplingX) {
                        seq.colorConfig.subsamplingY = br.readBits(1) != 0;
                    } else {
                        seq.colorConfig.subsamplingY = false;
                    }
                } else {
                    seq.colorConfig.subsamplingX = true;
                    seq.colorConfig.subsamplingY = false;
                }
            }
            if (seq.colorConfig.subsamplingX && seq.colorConfig.subsamplingY) {
                seq.colorConfig.chromaSamplePosition = OBPChromaSamplePosition.values()[br.readBits(2)];
            }
        }
        seq.colorConfig.separateUvDeltaQ = br.readBits(1) != 0;

        seq.filmGrainParamsPresent = br.readBits(1) != 0;

        return seq;
    }

    /*
    * obp_parse_frame_header parses a frame header OBU and fills out the fields in a user-provided
    * OBPFrameHeader structure.
    *
    * Input:
    *     buf          - Input OBU buffer. This is expected to *NOT* contain the OBU header.
    *     buf_size     - Size of the input OBU buffer.
    *     state        - An opaque state structure. Must be zeroed by the user on first use.
    *     temporal_id  - A temporal ID previously obtained from obu_parse_sequence header.
    *     spatial_id   - A spatial ID previously obtained from obu_parse_sequence header.
    *     err          - An error buffer and buffer size to write any error messages into.
    *
    * Output:
    *     frame_header    - A user provided structure that will be filled in with all the parsed data.
    *     SeenFrameHeader - Whether or not a frame header has beee seen. Tracking variable as per AV1 spec.
    *
    * Returns:
    *     0 on success, -1 on error.
    */
    private static int parseFrameHeader(byte[] buf, int bufSize, OBPSequenceHeader seq, OBPState state, int temporalId, int spatialId, OBPFrameHeader fh, AtomicBoolean seenFrameHeader) throws OBUParseException {
        BitReader br = new BitReader(buf, bufSize);
        if (seenFrameHeader.get()) {
            if (!state.prevFilled) {
                throw new OBUParseException("SeenFrameHeader is true, but no previous header exists in state");
            }
            copyFrameHeader(state.prev, fh);
            return 0;
        }
        seenFrameHeader.set(true);
        int idLen = 0;
        if (seq.frameIdNumbersPresentFlag) {
            idLen = seq.additionalFrameIdLengthMinus1 + seq.deltaFrameIdLengthMinus2 + 3;
        }
        byte allFrames = (byte) 255; // (1 << 8) - 1
        boolean frameIsIntra = false;
        if (seq.reducedStillPictureHeader) {
            fh.showExistingFrame = false;
            fh.frameType = OBPFrameType.KEYFRAME;
            frameIsIntra = true;
            fh.showFrame = true;
            fh.showableFrame = true;
        } else {
            fh.showExistingFrame = br.readBits(1) != 0;
            if (fh.showExistingFrame) {
                fh.frameToShowMapIdx = (byte) br.readBits(3);
                if (seq.decoderModelInfoPresentFlag && !seq.timingInfo.equalPictureInterval) {
                    int n = seq.decoderModelInfo.framePresentationTimeLengthMinus1 + 1;
                    fh.temporalPointInfo.framePresentationTime = br.readBits(n);
                }
                fh.refreshFrameFlags = 0;
                if (seq.frameIdNumbersPresentFlag) {
                    fh.displayFrameId = br.readBits(idLen);
                }
                fh.frameType = state.refFrameType[fh.frameToShowMapIdx];
                if (fh.frameType == OBPFrameType.KEYFRAME) {
                    fh.refreshFrameFlags = allFrames;
                }
                if (seq.filmGrainParamsPresent) {
                    copyFilmGrainParams(state.refGrainParams[fh.frameToShowMapIdx], fh.filmGrainParams);
                }
                return 0;
            }
            fh.frameType = OBPFrameType.values()[br.readBits(2)];
            frameIsIntra = (fh.frameType == OBPFrameType.INTRA_ONLY_FRAME || fh.frameType == OBPFrameType.KEYFRAME);
            fh.showFrame = br.readBits(1) != 0;
            if (fh.showFrame && seq.decoderModelInfoPresentFlag && !seq.timingInfo.equalPictureInterval) {
                int n = seq.decoderModelInfo.framePresentationTimeLengthMinus1 + 1;
                fh.temporalPointInfo.framePresentationTime = br.readBits(n);
            }
            if (fh.showFrame) {
                fh.showableFrame = (fh.frameType != OBPFrameType.KEYFRAME);
            } else {
                fh.showableFrame = br.readBits(1) != 0;
            }
            if (fh.frameType == OBPFrameType.SWITCH_FRAME || (fh.frameType == OBPFrameType.KEYFRAME && fh.showFrame)) {
                fh.errorResilientMode = true;
            } else {
                fh.errorResilientMode = br.readBits(1) != 0;
            }
        }
        if (fh.frameType == OBPFrameType.KEYFRAME && fh.showFrame) {
            for (int i = 0; i < 8; i++) {
                state.refValid[i] = 0;
                state.refOrderHint[i] = 0;
            }
            for (int i = 0; i < 7; i++) {
                state.orderHint[1 + i] = 0;
            }
        }
        fh.disableCdfUpdate = br.readBits(1) != 0;
        if (seq.seqForceScreenContentTools == 2) {
            fh.allowScreenContentTools = br.readBits(1) != 0;
        } else {
            fh.allowScreenContentTools = seq.seqForceScreenContentTools != 0;
        }
        if (fh.allowScreenContentTools) {
            if (seq.seqForceIntegerMv == 2) {
                fh.forceIntegerMv = br.readBits(1) != 0;
            } else {
                fh.forceIntegerMv = seq.seqForceIntegerMv != 0;
            }
        } else {
            fh.forceIntegerMv = false;
        }
        if (frameIsIntra) {
            fh.forceIntegerMv = true;
        }
        if (seq.frameIdNumbersPresentFlag) {
            fh.currentFrameId = br.readBits(idLen);
            byte diffLen = (byte) (seq.deltaFrameIdLengthMinus2 + 2);
            for (int i = 0; i < 8; i++) {
                if (fh.currentFrameId > (1 << diffLen)) {
                    if (state.refFrameId[i] > fh.currentFrameId || state.refFrameId[i] < (fh.currentFrameId - (1 << diffLen))) {
                        state.refValid[i] = 0;
                    }
                } else {
                    if (state.refFrameId[i] > fh.currentFrameId && state.refFrameId[i] < ((1 << idLen) + fh.currentFrameId - (1 << diffLen))) {
                        state.refValid[i] = 0;
                    }
                }
            }
        } else {
            fh.currentFrameId = 0;
        }
        if (fh.frameType == OBPFrameType.SWITCH_FRAME) {
            fh.frameSizeOverrideFlag = true;
        } else if (seq.reducedStillPictureHeader) {
            fh.frameSizeOverrideFlag = false;
        } else {
            fh.frameSizeOverrideFlag = br.readBits(1) != 0;
        }
        if (seq.OrderHintBits != 0) {
            fh.orderHint = (byte) br.readBits(seq.OrderHintBits);
        } else {
            fh.orderHint = 0;
        }
        byte orderHint = fh.orderHint;
        if (frameIsIntra || fh.errorResilientMode) {
            fh.primaryRefFrame = 7;
        } else {
            fh.primaryRefFrame = (byte) br.readBits(3);
        }
        if (seq.decoderModelInfoPresentFlag) {
            fh.bufferRemovalTimePresentFlag = br.readBits(1) != 0;
            if (fh.bufferRemovalTimePresentFlag) {
                for (int opNum = 0; opNum <= seq.operatingPointsCntMinus1; opNum++) {
                    if (seq.decoderModelPresentForThisOp[opNum]) {
                        int opPtIdc = seq.operatingPointIdc[opNum];
                        int inTemporalLayer = (opPtIdc >> temporalId) & 1;
                        int inSpatialLayer = (opPtIdc >> (spatialId + 8)) & 1;
                        if (opPtIdc == 0 || (inTemporalLayer != 0 && inSpatialLayer != 0)) {
                            int n = seq.decoderModelInfo.bufferRemovalTimeLengthMinus1 + 1;
                            fh.bufferRemovalTime[opNum] = br.readBits(n);
                        }
                    }
                }
            }
        }
        fh.allowHighPrecisionMv = false;
        fh.useRefFrameMvs = false;
        fh.allowIntrabc = false;
        if (fh.frameType == OBPFrameType.SWITCH_FRAME || (fh.frameType == OBPFrameType.KEYFRAME && fh.showFrame)) {
            fh.refreshFrameFlags = allFrames;
        } else {
            fh.refreshFrameFlags = (byte) br.readBits(8);
        }
        if (!frameIsIntra || fh.refreshFrameFlags != allFrames) {
            if (fh.errorResilientMode && seq.enableOrderHint) {
                for (int i = 0; i < 8; i++) {
                    fh.refOrderHint[i] = (byte) br.readBits(seq.OrderHintBits);
                    if (fh.refOrderHint[i] != state.refOrderHint[i]) {
                        state.refValid[i] = 0;
                    }
                }
            }
        }
        // Frame size
        if (frameIsIntra) {
            parseFrameSize(br, seq, fh);
            parseRenderSize(br, fh);
            if (fh.allowScreenContentTools && fh.upscaledWidth == fh.frameWidth) {
                fh.allowIntrabc = br.readBits(1) != 0;
            }
        } else {
            if (!seq.enableOrderHint) {
                fh.frameRefsShortSignaling = false;
            } else {
                fh.frameRefsShortSignaling = br.readBits(1) != 0;
                if (fh.frameRefsShortSignaling) {
                    fh.lastFrameIdx = (byte) br.readBits(3);
                    fh.goldFrameIdx = (byte) br.readBits(3);
                    setFrameRefs(fh, seq, state);
                }
            }
            for (int i = 0; i < 7; i++) {
                if (!fh.frameRefsShortSignaling) {
                    fh.refFrameIdx[i] = (byte) br.readBits(3);
                }
                if (seq.frameIdNumbersPresentFlag) {
                    int n = seq.deltaFrameIdLengthMinus2 + 2;
                    fh.deltaFrameIdMinus1[i] = (byte) br.readBits(n);
                    int DeltaFrameId = fh.deltaFrameIdMinus1[i] + 1;
                    int expectedFrameId = (fh.currentFrameId + (1 << idLen) - DeltaFrameId) % (1 << idLen);
                    if (state.refFrameId[fh.refFrameIdx[i]] != expectedFrameId) {
                        throw new OBUParseException("Reference frame id mismatch");
                    }
                }
            }
            if (fh.frameSizeOverrideFlag && !fh.errorResilientMode) {
                parseSuperresParams(br, seq, fh);
            } else {
                parseFrameSize(br, seq, fh);
                parseRenderSize(br, fh);
            }
            if (fh.forceIntegerMv) {
                fh.allowHighPrecisionMv = false;
            } else {
                fh.allowHighPrecisionMv = br.readBits(1) != 0;
            }
            parseInterpolationFilter(br, fh);
            fh.isMotionModeSwitchable = br.readBits(1) != 0;
            if (fh.errorResilientMode || !seq.enableRefFrameMvs) {
                fh.useRefFrameMvs = false;
            } else {
                fh.useRefFrameMvs = br.readBits(1) != 0;
            }
            for (int i = 0; i < 7; i++) {
                int refFrame = 1 + i;
                byte hint = state.refOrderHint[fh.refFrameIdx[i]];
                state.orderHint[refFrame] = hint;
                if (!seq.enableOrderHint) {
                    state.refFrameSignBias[refFrame] = 0;
                } else {
                    state.refFrameSignBias[refFrame] = getRelativeDist(hint, orderHint, seq) > 0 ? 1 : 0;
                }
            }
        }
        if (seq.reducedStillPictureHeader || fh.disableCdfUpdate) {
            fh.disableFrameEndUpdateCdf = true;
        } else {
            fh.disableFrameEndUpdateCdf = br.readBits(1) != 0;
        }
        if (fh.primaryRefFrame == 7) {
            setupPastIndependence(fh);
        } else {
            loadPrevious(fh, state);
        }
        // Tile info; after parsing frame size and other related parameters
        parseTileInfo(br, fh, seq);
        // Quantization params
        parseQuantizationParams(br, fh, seq);
        // Segmentation params
        parseSegmentationParams(br, fh, seq);
        // Delta Q params
        parseDeltaQParams(br, fh);
        // Delta LF params
        parseDeltaLfParams(br, fh, seq);
        boolean codedLossless = computeCodedLossless(fh, seq);
        fh.codedLossless = codedLossless;
        fh.allLossless = codedLossless && (fh.frameWidth == fh.upscaledWidth);
        // Loop filter params
        parseLoopFilterParams(br, fh, seq);
        // CDEF params
        parseCdefParams(br, fh, seq);
        // LR params
        parseLrParams(br, fh, seq);
        // Read TX mode
        if (codedLossless) {
            // TxMode implicitly set to ONLY_4X4
            fh.txMode = OBPTxMode.ONLY_4X4;
        } else {
            fh.txModeSelect = br.readBits(1) != 0;
            fh.txMode = fh.txModeSelect ? OBPTxMode.SELECT : OBPTxMode.LARGEST;
        }
        // Frame reference mode
        parseFrameReferenceMode(br, fh, frameIsIntra);
        // Skip mode params
        parseSkipModeParams(br, fh, seq, state, frameIsIntra);
        if (frameIsIntra || fh.errorResilientMode || !seq.enableWarpedMotion) {
            fh.allowWarpedMotion = false;
        } else {
            fh.allowWarpedMotion = br.readBits(1) != 0;
        }
        fh.reducedTxSet = br.readBits(1) != 0;
        // Global motion params
        parseGlobalMotionParams(br, fh, frameIsIntra);
        // Film grain params
        parseFilmGrainParams(br, fh, seq, state);
        br.byteAlignment();
        state.frameHeaderEndPos = br.getPosition();
        // Stash refs for future frame use
        for (int i = 0; i < 8; i++) {
            if ((fh.refreshFrameFlags & (1 << i)) != 0) {
                state.refOrderHint[i] = fh.orderHint;
                state.refFrameType[i] = fh.frameType;
                state.refUpscaledWidth[i] = fh.upscaledWidth;
                state.refFrameHeight[i] = fh.frameHeight;
                state.refRenderWidth[i] = fh.renderWidth;
                state.refRenderHeight[i] = fh.renderHeight;
                state.refFrameId[i] = fh.currentFrameId;
                copyFilmGrainParams(fh.filmGrainParams, state.refGrainParams[i]);
                // save_grain_params()
                for (int j = 0; j < 8; j++) {
                    System.arraycopy(fh.globalMotionParams.gmParams[j], 0, state.savedGmParams[i][j], 0, 6);
                }
                // save_segmentation_params()
                for (int j = 0; j < 8; j++) {
                    System.arraycopy(fh.segmentationParams.featureEnabled[j], 0, state.savedFeatureEnabled[i][j], 0, 8);
                    System.arraycopy(fh.segmentationParams.featureData[j], 0, state.savedFeatureData[i][j], 0, 8);
                }
                // save_loop_filter_params()
                System.arraycopy(fh.loopFilterParams.loopFilterRefDeltas, 0, state.savedLoopFilterRefDeltas[i], 0, 8);
                System.arraycopy(fh.loopFilterParams.loopFilterModeDeltas, 0, state.savedLoopFilterModeDeltas[i], 0, 2);
            }
        }
        // Handle show_existing_frame semantics
        if (fh.showExistingFrame && fh.frameType == OBPFrameType.KEYFRAME) {
            fh.orderHint = state.refOrderHint[fh.frameToShowMapIdx];
            for (int i = 0; i < 8; i++) {
                System.arraycopy(state.savedGmParams[fh.frameToShowMapIdx][i], 0, fh.globalMotionParams.gmParams[i], 0, 6);
            }
        }
        if (fh.showExistingFrame) {
            seenFrameHeader.set(false);
            state.prevFilled = false;
        } else {
            copyFrameHeader(fh, state.prev);
            state.prevFilled = true;
        }
        return 0;
    }

    /*
     * This method parses the interpolation filter information from the bitstream.
     */
    private static void parseInterpolationFilter(BitReader br, OBPFrameHeader fh) throws OBUParseException {
        fh.interpolationFilter.isFilterSwitchable = br.readBits(1) != 0;
        if (fh.interpolationFilter.isFilterSwitchable) {
            fh.interpolationFilter.interpolationFilter = OBPInterpolationFilter.SWITCHABLE;
        } else {
            fh.interpolationFilter.interpolationFilter = OBPInterpolationFilter.values()[br.readBits(2)];
        }
    }

    /*
     * This method handles parsing the frame size, either from the bitstream (if frameSizeOverrideFlag is set) or using
     * the values from the sequence header.
     */
    private static void parseFrameSize(BitReader br, OBPSequenceHeader seq, OBPFrameHeader fh) throws OBUParseException {
        if (fh.frameSizeOverrideFlag) {
            int n = seq.frameWidthBitsMinus1 + 1;
            fh.frameWidthMinus1 = br.readBits(n);
            n = seq.frameHeightBitsMinus1 + 1;
            fh.frameHeightMinus1 = br.readBits(n);
            fh.frameWidth = fh.frameWidthMinus1 + 1;
            fh.frameHeight = fh.frameHeightMinus1 + 1;
        } else {
            fh.frameWidth = seq.maxFrameWidthMinus1 + 1;
            fh.frameHeight = seq.maxFrameHeightMinus1 + 1;
        }
        parseSuperresParams(br, seq, fh);
        fh.miCols = 2 * ((fh.frameWidth + 7) >> 3);
        fh.miRows = 2 * ((fh.frameHeight + 7) >> 3);
    }

    /*
     * This method parses the superresolution parameters if superresolution is enabled.
     */
    private static void parseSuperresParams(BitReader br, OBPSequenceHeader seq, OBPFrameHeader fh) throws OBUParseException {
        if (seq.enableSuperres) {
            fh.superresParams.useSuperres = br.readBits(1) != 0;
        } else {
            fh.superresParams.useSuperres = false;
        }
        if (fh.superresParams.useSuperres) {
            fh.superresParams.codedDenom = (byte) br.readBits(3);
            fh.superresParams.superresDenom = fh.superresParams.codedDenom + 9;
        } else {
            fh.superresParams.superresDenom = 8;
        }
        fh.upscaledWidth = fh.frameWidth;
        fh.frameWidth = (fh.upscaledWidth * 8 + (fh.superresParams.superresDenom / 2)) / fh.superresParams.superresDenom;
    }

    /*
     * This method parses the render size, which may be different from the frame size.
     */
    private static void parseRenderSize(BitReader br, OBPFrameHeader fh) throws OBUParseException {
        fh.renderAndFrameSizeDifferent = br.readBits(1) != 0;
        if (fh.renderAndFrameSizeDifferent) {
            fh.renderWidthMinus1 = br.readBits(16);
            fh.renderHeightMinus1 = br.readBits(16);
            fh.renderWidth = fh.renderWidthMinus1 + 1;
            fh.renderHeight = fh.renderHeightMinus1 + 1;
        } else {
            fh.renderWidth = fh.upscaledWidth;
            fh.renderHeight = fh.frameHeight;
        }
    }

    private static void parseQuantizationParams(BitReader br, OBPFrameHeader fh, OBPSequenceHeader seq) throws OBUParseException {
        fh.quantizationParams.baseQIdx = br.readBits(8);
        fh.quantizationParams.deltaQYDc = readDeltaQ(br);
        if (seq.colorConfig.NumPlanes > 1) {
            if (seq.colorConfig.separateUvDeltaQ) {
                fh.quantizationParams.diffUvDelta = br.readBits(1) != 0;
            } else {
                fh.quantizationParams.diffUvDelta = false;
            }
            fh.quantizationParams.deltaQUDc = readDeltaQ(br);
            fh.quantizationParams.deltaQUAc = readDeltaQ(br);
            if (fh.quantizationParams.diffUvDelta) {
                fh.quantizationParams.deltaQVDc = readDeltaQ(br);
                fh.quantizationParams.deltaQVAc = readDeltaQ(br);
            } else {
                fh.quantizationParams.deltaQVDc = fh.quantizationParams.deltaQUDc;
                fh.quantizationParams.deltaQVAc = fh.quantizationParams.deltaQUAc;
            }
        } else {
            fh.quantizationParams.deltaQUDc = 0;
            fh.quantizationParams.deltaQUAc = 0;
            fh.quantizationParams.deltaQVDc = 0;
            fh.quantizationParams.deltaQVAc = 0;
        }
        fh.quantizationParams.usingQmatrix = br.readBits(1) != 0;
        if (fh.quantizationParams.usingQmatrix) {
            fh.quantizationParams.qmY = br.readBits(4);
            fh.quantizationParams.qmU = br.readBits(4);
            if (!seq.colorConfig.separateUvDeltaQ) {
                fh.quantizationParams.qmV = fh.quantizationParams.qmU;
            } else {
                fh.quantizationParams.qmV = br.readBits(4);
            }
        }
    }

    /*
     * This method is quite straightforward. It sets the referenceSelectInter flag in the frame header based on whether
     * the frame is intra or not. If the frame is not intra, it reads a single bit from the bitstream to determine the
     * value of referenceSelectInter.
     */
    private static void parseFrameReferenceMode(BitReader br, OBPFrameHeader fh, boolean FrameIsIntra) throws OBUParseException {
        if (FrameIsIntra) {
            fh.referenceSelectInter = false;
        } else {
            fh.referenceSelectInter = br.readBits(1) != 0;
        }
    }

    private static void parseSkipModeParams(BitReader br, OBPFrameHeader fh, OBPSequenceHeader seq, OBPState state, boolean frameIsIntra) throws OBUParseException {
        boolean skipModeAllowed;
        if (frameIsIntra || !fh.referenceSelectInter || !seq.enableOrderHint) {
            skipModeAllowed = false;
        } else {
            int forwardIdx = -1;
            int backwardIdx = -1;
            int forwardHint = 0;
            int backwardHint = 0;
            for (int i = 0; i < OBPConstants.REFS_PER_FRAME; i++) {
                int refHint = state.refOrderHint[fh.refFrameIdx[i]];
                if (getRelativeDist(refHint, fh.orderHint, seq) < 0) {
                    if (forwardIdx < 0 || getRelativeDist(refHint, forwardHint, seq) > 0) {
                        forwardIdx = i;
                        forwardHint = refHint;
                    }
                } else if (getRelativeDist(refHint, fh.orderHint, seq) > 0) {
                    if (backwardIdx < 0 || getRelativeDist(refHint, backwardHint, seq) < 0) {
                        backwardIdx = i;
                        backwardHint = refHint;
                    }
                }
            }
            if (forwardIdx < 0) {
                skipModeAllowed = false;
            } else if (backwardIdx >= 0) {
                skipModeAllowed = true;
                // SkipModeFrame not used in parsing, so we don't set it here
            } else {
                int secondForwardIdx = -1;
                int secondForwardHint = 0;
                for (int i = 0; i < OBPConstants.REFS_PER_FRAME; i++) {
                    int refHint = state.refOrderHint[fh.refFrameIdx[i]];
                    if (getRelativeDist(refHint, forwardHint, seq) < 0) {
                        if (secondForwardIdx < 0 || getRelativeDist(refHint, secondForwardHint, seq) > 0) {
                            secondForwardIdx = i;
                            secondForwardHint = refHint;
                        }
                    }
                }
                skipModeAllowed = secondForwardIdx >= 0;
                // SkipModeFrame not used in parsing, so we don't set it here
            }
        }
        if (skipModeAllowed) {
            fh.skipModePresent = br.readBits(1) != 0;
        } else {
            fh.skipModePresent = false;
        }
    }

    /*
     * This method performs a deep copy of a frame header.
     */
    private static void copyFrameHeader(OBPFrameHeader source, OBPFrameHeader dest) {
        // Implement a deep copy of all fields from source to dest
        // This is a simplified version, you'll need to copy all relevant fields
        dest.showExistingFrame = source.showExistingFrame;
        dest.frameType = source.frameType;
        dest.showFrame = source.showFrame;
        // TODO(paul) copy all other fields

        // For complex objects like filmGrainParams, use the copy method we defined earlier
        copyFilmGrainParams(source.filmGrainParams, dest.filmGrainParams);

        // For arrays, use System.arraycopy
        System.arraycopy(source.refFrameIdx, 0, dest.refFrameIdx, 0, source.refFrameIdx.length);
        // TODO(paul) copy other arrays

    }

    /*
     * This method computes whether the frame is losslessly coded.
     */
    private static boolean computeCodedLossless(OBPFrameHeader fh, OBPSequenceHeader seq) {
        for (int segmentId = 0; segmentId < 8; segmentId++) {
            int qindex = getQIndex(true, segmentId, fh.quantizationParams.baseQIdx, fh, seq);
            if (qindex != 0 || fh.quantizationParams.deltaQYDc != 0 || fh.quantizationParams.deltaQUAc != 0 || fh.quantizationParams.deltaQUDc != 0 || fh.quantizationParams.deltaQVAc != 0 || fh.quantizationParams.deltaQVDc != 0) {
                return false;
            }
        }
        return true;
    }

    /*
     * This method computes the quantization index for a given segment.
     */
    private static int getQIndex(boolean ignoreDeltaQ, int segmentId, int currentQIndex, OBPFrameHeader fh, OBPSequenceHeader seq) {
        if (fh.segmentationParams.segmentationEnabled && fh.segmentationParams.featureEnabled[segmentId][0]) {
            int data = fh.segmentationParams.featureData[segmentId][0];
            int qindex = fh.quantizationParams.baseQIdx + data;
            if (!ignoreDeltaQ && fh.deltaQParams.deltaQPresent) {
                qindex = currentQIndex + data;
            }
            return Math.max(0, Math.min(255, qindex));
        }
        if (!ignoreDeltaQ && fh.deltaQParams.deltaQPresent) {
            return currentQIndex;
        }
        return fh.quantizationParams.baseQIdx;
    }

    /*
     * obp_parse_frame parses a frame OBU and fills out the fields in user-provided OBPFrameHeader
     * and OBPTileGroup structures.
     *
     * Input:
     *     buf          - Input OBU buffer. This is expected to *NOT* contain the OBU header.
     *     buf_size     - Size of the input OBU buffer.
     *     state        - An opaque state structure. Must be zeroed by the user on first use.
     *     temporal_id  - A temporal ID previously obtained from obu_parse_sequence header.
     *     spatial_id   - A spatial ID previously obtained from obu_parse_sequence header.
     *     err          - An error buffer and buffer size to write any error messages into.
     *
     * Output:
     *     frame_header    - A user provided structure that will be filled in with all the parsed data.
     *     tile_group      - A user provided structure that will be filled in with all the parsed data.
     *     SeenFrameHeader - Whether or not a frame header has been seen. Tracking variable as per AV1 spec.
     *
     * Returns:
     *     0 on success, -1 on error.
     */
    public static void parseFrame(byte[] buf, int bufSize, OBPSequenceHeader seq, OBPState state, int temporalId, int spatialId, OBPFrameHeader fh, OBPTileGroup tileGroup, AtomicBoolean SeenFrameHeader) throws OBUParseException {
        int startBitPos = 0, endBitPos, headerBytes;
        int ret = parseFrameHeader(buf, bufSize, seq, state, temporalId, spatialId, fh, SeenFrameHeader);
        if (ret < 0) {
            throw new OBUParseException("Failed to parse frame header");
        }
        endBitPos = state.frameHeaderEndPos;
        headerBytes = (endBitPos - startBitPos) / 8;
        parseTileGroup(buf, headerBytes, bufSize - headerBytes, fh, tileGroup, SeenFrameHeader);
    }

    /*
    * obp_parse_tile_group parses a tile group OBU and fills out the fields in a
    * user-provided OBPTileGroup structure.
    *
    * Input:
    *     buf          - Input OBU buffer. This is expected to *NOT* contain the OBU header.
    *     buf_size     - Size of the input OBU buffer.
    *     frame_header - A filled in frame header OBU previously seen.
    *     err          - An error buffer and buffer size to write any error messages into.
    *
    * Output:
    *     tile_group      - A user provided structure that will be filled in with all the parsed data.
    *     SeenFrameHeader - Whether or not a frame header has been seen. Tracking variable as per AV1 spec.
    *
    * Returns:
    *     0 on success, -1 on error.
    */
    private static void parseTileGroup(byte[] buf, int offset, int size, OBPFrameHeader fh, OBPTileGroup tileGroup, AtomicBoolean SeenFrameHeader) throws OBUParseException {
        BitReader br = new BitReader(buf, offset, size);
        tileGroup.numTiles = (short) (fh.tileInfo.tileCols * fh.tileInfo.tileRows);
        long startBitPos = br.getPosition();
        tileGroup.tileStartAndEndPresentFlag = false;
        if (tileGroup.numTiles > 1) {
            tileGroup.tileStartAndEndPresentFlag = br.readBits(1) != 0;
        }
        if (tileGroup.numTiles == 1 || !tileGroup.tileStartAndEndPresentFlag) {
            tileGroup.tgStart = 0;
            tileGroup.tgEnd = (short) (tileGroup.numTiles - 1);
        } else {
            int tileBits = tileLog2(1, fh.tileInfo.tileCols) + tileLog2(1, fh.tileInfo.tileRows);
            tileGroup.tgStart = (short) br.readBits(tileBits);
            tileGroup.tgEnd = (short) br.readBits(tileBits);
        }
        br.byteAlignment();
        long endBitPos = br.getPosition();
        long headerBytes = (endBitPos - startBitPos) / 8;
        long sz = size - headerBytes;
        long pos = headerBytes;

        for (int tileNum = tileGroup.tgStart; tileNum <= tileGroup.tgEnd; tileNum++) {
            boolean lastTile = (tileNum == tileGroup.tgEnd);
            if (lastTile) {
                tileGroup.tileSize[tileNum] = sz;
            } else {
                int TileSizeBytes = fh.tileInfo.tileSizeBytesMinus1 + 1;
                long tileSizeMinus1;
                if (sz < TileSizeBytes) {
                    throw new OBUParseException("Not enough bytes left to read tile size for tile " + tileNum);
                }
                tileSizeMinus1 = readLe(buf, (int) (offset + pos), TileSizeBytes);
                tileGroup.tileSize[tileNum] = tileSizeMinus1 + 1;
                if (sz < tileGroup.tileSize[tileNum]) {
                    throw new OBUParseException("Not enough bytes to contain TileSize for tile " + tileNum);
                }
                sz -= tileGroup.tileSize[tileNum] + TileSizeBytes;
                pos += tileGroup.tileSize[tileNum] + TileSizeBytes;
            }
        }
        if (tileGroup.tgEnd == tileGroup.numTiles - 1) {
            SeenFrameHeader.set(false);
        }
    }

    /*
    * obp_parse_metadata parses a metadata OBU and fills out the fields in a user-provided OBPMetadata
    * structure. This OBU's returned payload is *NOT* safe to use once the user-provided 'buf' has
    * been freed, since it may fill the structure with pointers to offsets in that data.
    *
    * Input:
    *     buf      - Input OBU buffer. This is expected to *NOT* contain the OBU header.
    *     buf_size - Size of the input OBU buffer.
    *     err      - An error buffer and buffer size to write any error messages into.
    *
    * Output:
    *     metadata - A user provided structure that will be filled in with all the parsed data.
    *
    * Returns:
    *     0 on success, -1 on error.
    */
    public static OBPMetadata parseMetadata(byte[] buf, int bufSize) throws OBUParseException {
        OBPMetadata metadata = new OBPMetadata();
        int consumed;
        // int will only be 4 bytes long max
        LEB128Result result = LEB128.decode(buf);
        int leb = result.value;
        consumed = result.bytesRead;
        // old logic
        //int leb = LEB128.encode(Arrays.copyOfRange(buf, 0, 4));
        //consumed = 4;
        metadata.metadataType = OBPMetadataType.fromValue(leb);
        BitReader br = new BitReader(buf, consumed, bufSize - consumed);
        switch (metadata.metadataType) {
            case HDR_CLL:
                metadata.metadataHdrCll = new OBPMetadata.MetadataHdrCll();
                metadata.metadataHdrCll.maxCll = (short) br.readBits(16);
                metadata.metadataHdrCll.maxFall = (short) br.readBits(16);
                break;
            case HDR_MDCV:
                metadata.metadataHdrMdcv = new OBPMetadata.MetadataHdrMdcv();
                for (int i = 0; i < 3; i++) {
                    metadata.metadataHdrMdcv.primaryChromaticityX[i] = (short) br.readBits(16);
                    metadata.metadataHdrMdcv.primaryChromaticityY[i] = (short) br.readBits(16);
                }
                metadata.metadataHdrMdcv.whitePointChromaticityX = (short) br.readBits(16);
                metadata.metadataHdrMdcv.whitePointChromaticityY = (short) br.readBits(16);
                metadata.metadataHdrMdcv.luminanceMax = br.readBits(32);
                metadata.metadataHdrMdcv.luminanceMin = br.readBits(32);
                break;
            case SCALABILITY:
                metadata.metadataScalability = new OBPMetadata.MetadataScalability();
                metadata.metadataScalability.scalabilityModeIdc = (byte) br.readBits(8);
                if (metadata.metadataScalability.scalabilityModeIdc != 0) {
                    metadata.metadataScalability.scalabilityStructure = new OBPMetadata.MetadataScalability.ScalabilityStructure();
                    parseScalabilityStructure(br, metadata.metadataScalability.scalabilityStructure);
                }
                break;
            case ITUT_T35:
                metadata.metadataItutT35 = new OBPMetadata.MetadataItutT35();
                metadata.metadataItutT35.ituTT35CountryCode = (byte) br.readBits(8);
                long offset = 1;
                if (metadata.metadataItutT35.ituTT35CountryCode == 0xFF) {
                    metadata.metadataItutT35.ituTT35CountryCodeExtensionByte = (byte) br.readBits(8);
                    offset++;
                }
                metadata.metadataItutT35.ituTT35PayloadBytes = Arrays.copyOfRange(buf, (int) (consumed + offset), buf.length);
                metadata.metadataItutT35.ituTT35PayloadBytesSize = findItuT35PayloadSize(metadata.metadataItutT35.ituTT35PayloadBytes);
                break;
            case TIMECODE:
                metadata.metadataTimecode = new OBPMetadata.MetadataTimecode();
                metadata.metadataTimecode.countingType = (byte) br.readBits(5);
                metadata.metadataTimecode.fullTimestampFlag = br.readBits(1) != 0;
                metadata.metadataTimecode.discontinuityFlag = br.readBits(1) != 0;
                metadata.metadataTimecode.cntDroppedFlag = br.readBits(1) != 0;
                metadata.metadataTimecode.nFrames = (short) br.readBits(9);
                if (metadata.metadataTimecode.fullTimestampFlag) {
                    metadata.metadataTimecode.secondsValue = (byte) br.readBits(6);
                    metadata.metadataTimecode.minutesValue = (byte) br.readBits(6);
                    metadata.metadataTimecode.hoursValue = (byte) br.readBits(5);
                } else {
                    metadata.metadataTimecode.secondsFlag = br.readBits(1) != 0;
                    if (metadata.metadataTimecode.secondsFlag) {
                        metadata.metadataTimecode.secondsValue = (byte) br.readBits(6);
                        metadata.metadataTimecode.minutesFlag = br.readBits(1) != 0;
                        if (metadata.metadataTimecode.minutesFlag) {
                            metadata.metadataTimecode.minutesValue = (byte) br.readBits(6);
                            metadata.metadataTimecode.hoursFlag = br.readBits(1) != 0;
                            if (metadata.metadataTimecode.hoursFlag) {
                                metadata.metadataTimecode.hoursValue = (byte) br.readBits(5);
                            }
                        }
                    }
                }
                metadata.metadataTimecode.timeOffsetLength = (byte) br.readBits(5);
                if (metadata.metadataTimecode.timeOffsetLength > 0) {
                    metadata.metadataTimecode.timeOffsetValue = br.readBits(metadata.metadataTimecode.timeOffsetLength);
                }
                break;
            default:
                if (metadata.metadataType.getValue() >= 6 && metadata.metadataType.getValue() <= 31) {
                    metadata.unregistered = new OBPMetadata.Unregistered();
                    metadata.unregistered.buf = Arrays.copyOfRange(buf, (int) consumed, buf.length);
                    metadata.unregistered.bufSize = bufSize - consumed;
                } else {
                    throw new OBUParseException("Invalid metadata type: " + metadata.metadataType.getValue());
                }
        }
        return metadata;
    }

    /*
    * obp_parse_tile_list parses a tile list OBU and fills out the fields in a user-provided OBPTileList
    * structure. This OBU's returned payload is *NOT* safe to use once the user-provided 'buf' has
    * been freed, since it may fill the structure with pointers to offsets in that data.
    *
    * Input:
    *     buf      - Input OBU buffer. This is expected to *NOT* contain the OBU header.
    *     buf_size - Size of the input OBU buffer.
    *     err      - An error buffer and buffer size to write any error messages into.
    *
    * Output:
    *     tile_list - A user provided structure that will be filled in with all the parsed data.
    *
    * Returns:
    *     0 on success, -1 on error.
    */
    public static OBPTileList parseTileList(byte[] buf, long bufSize) throws OBUParseException {
        OBPTileList tileList = new OBPTileList();
        int pos = 0;
        if (bufSize < 4) {
            throw new OBUParseException("Tile list OBU must be at least 4 bytes");
        }
        tileList.outputFrameWidthInTilesMinus1 = buf[0];
        tileList.outputFrameHeightInTilesMinus1 = buf[1];
        tileList.tileCountMinus1 = (short) (((buf[2] & 0xFF) << 8) | (buf[3] & 0xFF));
        pos += 4;
        tileList.tileListEntry = new OBPTileList.TileListEntry[tileList.tileCountMinus1 + 1];
        for (int i = 0; i <= tileList.tileCountMinus1; i++) {
            if (pos + 5 > bufSize) {
                throw new OBUParseException("Tile list OBU malformed: Not enough bytes for next tile_list_entry()");
            }
            OBPTileList.TileListEntry entry = new OBPTileList.TileListEntry();
            entry.anchorFrameIdx = buf[pos];
            entry.anchorTileRow = buf[pos + 1];
            entry.anchorTileCol = buf[pos + 2];
            entry.tileDataSizeMinus1 = (short) (((buf[pos + 3] & 0xFF) << 8) | (buf[pos + 4] & 0xFF));
            pos += 5;
            int N = 8 * (entry.tileDataSizeMinus1 + 1);
            if (pos + N > bufSize) {
                throw new OBUParseException("Tile list OBU malformed: Not enough bytes for next tile_list_entry()'s data");
            }
            entry.codedTileData = Arrays.copyOfRange(buf, pos, (pos + N));
            entry.codedTileDataSize = N;
            pos += N;
            tileList.tileListEntry[i] = entry;
        }
        return tileList;
    }

    private static void copyFilmGrainParams(OBPFilmGrainParameters source, OBPFilmGrainParameters dest) {
        dest.applyGrain = source.applyGrain;
        dest.grainSeed = source.grainSeed;
        dest.updateGrain = source.updateGrain;
        dest.filmGrainParamsRefIdx = source.filmGrainParamsRefIdx;
        dest.numYPoints = source.numYPoints;
        System.arraycopy(source.pointYValue, 0, dest.pointYValue, 0, source.pointYValue.length);
        System.arraycopy(source.pointYScaling, 0, dest.pointYScaling, 0, source.pointYScaling.length);
        dest.chromaScalingFromLuma = source.chromaScalingFromLuma;
        dest.numCbPoints = source.numCbPoints;
        System.arraycopy(source.pointCbValue, 0, dest.pointCbValue, 0, source.pointCbValue.length);
        System.arraycopy(source.pointCbScaling, 0, dest.pointCbScaling, 0, source.pointCbScaling.length);
        dest.numCrPoints = source.numCrPoints;
        System.arraycopy(source.pointCrValue, 0, dest.pointCrValue, 0, source.pointCrValue.length);
        System.arraycopy(source.pointCrScaling, 0, dest.pointCrScaling, 0, source.pointCrScaling.length);
        dest.grainScalingMinus8 = source.grainScalingMinus8;
        dest.arCoeffLag = source.arCoeffLag;
        System.arraycopy(source.arCoeffsYPlus128, 0, dest.arCoeffsYPlus128, 0, source.arCoeffsYPlus128.length);
        System.arraycopy(source.arCoeffsCbPlus128, 0, dest.arCoeffsCbPlus128, 0, source.arCoeffsCbPlus128.length);
        System.arraycopy(source.arCoeffsCrPlus128, 0, dest.arCoeffsCrPlus128, 0, source.arCoeffsCrPlus128.length);
        dest.arCoeffShiftMinus6 = source.arCoeffShiftMinus6;
        dest.grainScaleShift = source.grainScaleShift;
        dest.cbMult = source.cbMult;
        dest.cbLumaMult = source.cbLumaMult;
        dest.cbOffset = source.cbOffset;
        dest.crMult = source.crMult;
        dest.crLumaMult = source.crLumaMult;
        dest.crOffset = source.crOffset;
        dest.overlapFlag = source.overlapFlag;
        dest.clipToRestrictedRange = source.clipToRestrictedRange;
    }

    private static void setFrameRefs(OBPFrameHeader fh, OBPSequenceHeader seq, OBPState state) throws OBUParseException {
        int[] usedFrame = new int[8];
        long curFrameHint, lastOrderHint, goldOrderHint, latestOrderHint, earliestOrderHint;
        int ref;
        byte[] shiftedOrderHints = new byte[8];
        final int[] Ref_Frame_List = { 2, 3, 5, 6, 7 }; // LAST2_FRAME, LAST3_FRAME, BWDREF_FRAME, ALTREF2_FRAME, ALTREF_FRAME
        int[] refFrameIdx = new int[8];

        for (int i = 0; i < 7; i++) {
            refFrameIdx[i] = -1;
        }
        refFrameIdx[0] = fh.lastFrameIdx;
        refFrameIdx[3] = fh.goldFrameIdx;
        for (int i = 0; i < 8; i++) {
            usedFrame[i] = 0;
        }
        usedFrame[fh.lastFrameIdx] = 1;
        usedFrame[fh.goldFrameIdx] = 2;
        curFrameHint = 1L << (seq.OrderHintBits - 1);
        for (int i = 0; i < 8; i++) {
            shiftedOrderHints[i] = (byte) (curFrameHint + getRelativeDist(state.refOrderHint[i], fh.orderHint, seq));
        }
        lastOrderHint = shiftedOrderHints[fh.lastFrameIdx];
        goldOrderHint = shiftedOrderHints[fh.goldFrameIdx];
        if (lastOrderHint >= curFrameHint || goldOrderHint >= curFrameHint) {
            throw new OBUParseException("Invalid order hints");
        }

        // find_latest_backward()
        ref = -1;
        latestOrderHint = 0;
        for (int i = 0; i < 8; i++) {
            long hint = shiftedOrderHints[i];
            if (usedFrame[i] == 0 && hint >= curFrameHint && (ref < 0 || hint >= latestOrderHint)) {
                ref = i;
                latestOrderHint = hint;
            }
        }
        if (ref >= 0) {
            refFrameIdx[6] = ref;
            usedFrame[ref] = 1;
        }

        // find_earliest_backward()
        ref = -1;
        earliestOrderHint = 0;
        for (int i = 0; i < 8; i++) {
            long hint = shiftedOrderHints[i];
            if (usedFrame[i] == 0 && hint >= curFrameHint && (ref < 0 || hint < earliestOrderHint)) {
                ref = i;
                earliestOrderHint = hint;
            }
        }
        if (ref >= 0) {
            refFrameIdx[4] = ref;
            usedFrame[ref] = 1;
        }

        // find_earliest_backward()
        ref = -1;
        earliestOrderHint = 0;
        for (int i = 0; i < 8; i++) {
            long hint = shiftedOrderHints[i];
            if (usedFrame[i] == 0 && hint >= curFrameHint && (ref < 0 || hint < earliestOrderHint)) {
                ref = i;
                earliestOrderHint = hint;
            }
        }
        if (ref >= 0) {
            refFrameIdx[5] = ref;
            usedFrame[ref] = 1;
        }

        for (int i = 0; i < 5; i++) {
            int refFrame = Ref_Frame_List[i];
            if (refFrameIdx[refFrame - 1] < 0) {
                ref = -1;
                long latestOrderHintSubRef = 0;
                for (int j = 0; j < 8; j++) {
                    long hint = shiftedOrderHints[j];
                    if (usedFrame[j] == 0 && hint < curFrameHint && (ref < 0 || hint >= latestOrderHintSubRef)) {
                        ref = j;
                        latestOrderHintSubRef = hint;
                    }
                }
                if (ref >= 0) {
                    refFrameIdx[refFrame - 1] = ref;
                    usedFrame[ref] = 1;
                }
            }
        }

        ref = -1;
        for (int i = 0; i < 8; i++) {
            long hint = shiftedOrderHints[i];
            if (ref < 0 || hint < earliestOrderHint) {
                ref = i;
                earliestOrderHint = hint;
            }
        }
        for (int i = 0; i < 7; i++) {
            if (refFrameIdx[i] < 0) {
                refFrameIdx[i] = ref;
            }
        }
        for (int i = 0; i < 7; i++) {
            fh.refFrameIdx[i] = (byte) refFrameIdx[i];
        }
    }

    private static void setupPastIndependence(OBPFrameHeader fh) {
        for (int i = 1; i < 7; i++) {
            fh.globalMotionParams.gmType[i] = 0;
            for (int j = 0; j < 6; j++) {
                fh.globalMotionParams.gmParams[i][j] = (j % 3 == 2) ? (1 << 16) : 0;
            }
        }
        fh.loopFilterParams.loopFilterDeltaEnabled = true;
        fh.loopFilterParams.loopFilterRefDeltas[0] = 1;
        fh.loopFilterParams.loopFilterRefDeltas[1] = 0;
        fh.loopFilterParams.loopFilterRefDeltas[2] = 0;
        fh.loopFilterParams.loopFilterRefDeltas[3] = 0;
        fh.loopFilterParams.loopFilterRefDeltas[4] = 0;
        fh.loopFilterParams.loopFilterRefDeltas[5] = -1;
        fh.loopFilterParams.loopFilterRefDeltas[6] = -1;
        fh.loopFilterParams.loopFilterRefDeltas[7] = -1;
        for (int i = 0; i < 2; i++) {
            fh.loopFilterParams.loopFilterModeDeltas[i] = 0;
        }
    }

    /*
     * This method loads the previous frame's parameters into the current frame header.
     */
    private static void loadPrevious(OBPFrameHeader fh, OBPState state) {
        int prevFrame = fh.refFrameIdx[fh.primaryRefFrame];
        // Load global motion parameters
        for (int i = 0; i < OBPConstants.REFS_PER_FRAME; i++) {
            for (int j = 0; j < 6; j++) {
                fh.globalMotionParams.prevGmParams[i][j] = state.savedGmParams[prevFrame][i][j];
            }
        }
        // Load loop filter parameters
        for (int i = 0; i < OBPConstants.TOTAL_REFS_PER_FRAME; i++) {
            fh.loopFilterParams.loopFilterRefDeltas[i] = state.savedLoopFilterRefDeltas[prevFrame][i];
        }
        for (int i = 0; i < 2; i++) {
            fh.loopFilterParams.loopFilterModeDeltas[i] = state.savedLoopFilterModeDeltas[prevFrame][i];
        }
        // Load segmentation parameters
        fh.segmentationParams.segmentationEnabled = state.savedSegmentationParams[prevFrame].segmentationEnabled;
        fh.segmentationParams.segmentationUpdateMap = state.savedSegmentationParams[prevFrame].segmentationUpdateMap;
        fh.segmentationParams.segmentationTemporalUpdate = state.savedSegmentationParams[prevFrame].segmentationTemporalUpdate;
        fh.segmentationParams.segmentationUpdateData = state.savedSegmentationParams[prevFrame].segmentationUpdateData;
        for (int i = 0; i < OBPConstants.MAX_SEGMENTS; i++) {
            for (int j = 0; j < OBPConstants.SEG_LVL_MAX; j++) {
                fh.segmentationParams.featureEnabled[i][j] = state.savedFeatureEnabled[prevFrame][i][j];
                fh.segmentationParams.featureData[i][j] = state.savedFeatureData[prevFrame][i][j];
            }
        }
    }

    private static void parseGlobalMotionParams(BitReader br, OBPFrameHeader fh, boolean frameIsIntra) throws OBUParseException {
        for (int ref = 1; ref < 7; ref++) {
            fh.globalMotionParams.gmType[ref] = 0;
            for (int i = 0; i < 6; i++) {
                fh.globalMotionParams.gmParams[ref][i] = (i % 3 == 2) ? (1 << 16) : 0;
            }
        }
        if (!frameIsIntra) {
            for (int ref = 1; ref <= 7; ref++) {
                boolean isGlobal = br.readBits(1) != 0;
                if (isGlobal) {
                    boolean isRotZoom = br.readBits(1) != 0;
                    if (isRotZoom) {
                        fh.globalMotionParams.gmType[ref] = 2;
                    } else {
                        boolean isTranslation = br.readBits(1) != 0;
                        fh.globalMotionParams.gmType[ref] = (byte) (isTranslation ? 1 : 3);
                    }
                }
                if (fh.globalMotionParams.gmType[ref] >= 2) {
                    readGlobalParam(br, fh, fh.globalMotionParams.gmType[ref], ref, 2);
                    readGlobalParam(br, fh, fh.globalMotionParams.gmType[ref], ref, 3);
                    if (fh.globalMotionParams.gmType[ref] == 3) {
                        readGlobalParam(br, fh, fh.globalMotionParams.gmType[ref], ref, 4);
                        readGlobalParam(br, fh, fh.globalMotionParams.gmType[ref], ref, 5);
                    } else {
                        fh.globalMotionParams.gmParams[ref][4] = -fh.globalMotionParams.gmParams[ref][3];
                        fh.globalMotionParams.gmParams[ref][5] = fh.globalMotionParams.gmParams[ref][2];
                    }
                }
                if (fh.globalMotionParams.gmType[ref] >= 1) {
                    readGlobalParam(br, fh, fh.globalMotionParams.gmType[ref], ref, 0);
                    readGlobalParam(br, fh, fh.globalMotionParams.gmType[ref], ref, 1);
                }
            }
        }
    }

    private static void readGlobalParam(BitReader br, OBPFrameHeader fh, int type, int ref, int idx) throws OBUParseException {
        int absBits = 12;
        int precBits = 15;
        if (idx < 2) {
            if (type == 1) { // TRANSLATION
                absBits = 9 - (fh.allowHighPrecisionMv ? 0 : 1);
                precBits = 3 - (fh.allowHighPrecisionMv ? 0 : 1);
            } else {
                absBits = 12;
                precBits = 6;
            }
        }
        int precDiff = 16 - precBits;
        int round = (idx % 3) == 2 ? (1 << 16) : 0;
        int sub = (idx % 3) == 2 ? (1 << precBits) : 0;
        int mx = (1 << absBits);
        int r = (fh.globalMotionParams.prevGmParams[ref][idx] >> precDiff) - sub;
        int val = decodeSignedSubexpWithRef(br, -mx, mx + 1, r);
        if (val < 0) {
            val = -val;
            fh.globalMotionParams.gmParams[ref][idx] = -(val << precDiff) + round;
        } else {
            fh.globalMotionParams.gmParams[ref][idx] = (val << precDiff) + round;
        }
    }

    private static void parseFilmGrainParams(BitReader br, OBPFrameHeader fh, OBPSequenceHeader seq, OBPState state) throws OBUParseException {
        if (!seq.filmGrainParamsPresent || (!fh.showFrame && !fh.showableFrame)) {
            resetGrainParams(fh.filmGrainParams);
            return;
        }

        fh.filmGrainParams.applyGrain = br.readBits(1) != 0;

        if (!fh.filmGrainParams.applyGrain) {
            resetGrainParams(fh.filmGrainParams);
            return;
        }

        fh.filmGrainParams.grainSeed = (short) br.readBits(16);

        if (fh.frameType == OBPFrameType.INTERFRAME) {
            fh.filmGrainParams.updateGrain = br.readBits(1) != 0;
        } else {
            fh.filmGrainParams.updateGrain = true;
        }

        if (!fh.filmGrainParams.updateGrain) {
            fh.filmGrainParams.filmGrainParamsRefIdx = (byte) br.readBits(3);
            short tempGrainSeed = fh.filmGrainParams.grainSeed;
            fh.filmGrainParams = state.refGrainParams[fh.filmGrainParams.filmGrainParamsRefIdx];
            fh.filmGrainParams.grainSeed = tempGrainSeed;
            return;
        }

        fh.filmGrainParams.numYPoints = (byte) br.readBits(4);
        for (int i = 0; i < fh.filmGrainParams.numYPoints; i++) {
            fh.filmGrainParams.pointYValue[i] = (byte) br.readBits(8);
            fh.filmGrainParams.pointYScaling[i] = (byte) br.readBits(8);
        }

        if (seq.colorConfig.monoChrome) {
            fh.filmGrainParams.chromaScalingFromLuma = false;
        } else {
            fh.filmGrainParams.chromaScalingFromLuma = br.readBits(1) != 0;
        }

        if (seq.colorConfig.monoChrome || fh.filmGrainParams.chromaScalingFromLuma || (seq.colorConfig.subsamplingX && seq.colorConfig.subsamplingY && fh.filmGrainParams.numYPoints == 0)) {
            fh.filmGrainParams.numCbPoints = 0;
            fh.filmGrainParams.numCrPoints = 0;
        } else {
            fh.filmGrainParams.numCbPoints = (byte) br.readBits(4);
            for (int i = 0; i < fh.filmGrainParams.numCbPoints; i++) {
                fh.filmGrainParams.pointCbValue[i] = (byte) br.readBits(8);
                fh.filmGrainParams.pointCbScaling[i] = (byte) br.readBits(8);
            }
            fh.filmGrainParams.numCrPoints = (byte) br.readBits(4);
            for (int i = 0; i < fh.filmGrainParams.numCrPoints; i++) {
                fh.filmGrainParams.pointCrValue[i] = (byte) br.readBits(8);
                fh.filmGrainParams.pointCrScaling[i] = (byte) br.readBits(8);
            }
        }

        fh.filmGrainParams.grainScalingMinus8 = (byte) br.readBits(2);
        fh.filmGrainParams.arCoeffLag = (byte) br.readBits(2);

        int numPosLuma = 2 * fh.filmGrainParams.arCoeffLag * (fh.filmGrainParams.arCoeffLag + 1);
        int numPosChroma = numPosLuma;
        if (fh.filmGrainParams.numYPoints > 0) {
            numPosChroma = numPosLuma + 1;
            for (int i = 0; i < numPosLuma; i++) {
                fh.filmGrainParams.arCoeffsYPlus128[i] = (byte) br.readBits(8);
            }
        }
        if (fh.filmGrainParams.chromaScalingFromLuma || fh.filmGrainParams.numCbPoints > 0) {
            for (int i = 0; i < numPosChroma; i++) {
                fh.filmGrainParams.arCoeffsCbPlus128[i] = (byte) br.readBits(8);
            }
        }
        if (fh.filmGrainParams.chromaScalingFromLuma || fh.filmGrainParams.numCrPoints > 0) {
            for (int i = 0; i < numPosChroma; i++) {
                fh.filmGrainParams.arCoeffsCrPlus128[i] = (byte) br.readBits(8);
            }
        }

        fh.filmGrainParams.arCoeffShiftMinus6 = (byte) br.readBits(2);
        fh.filmGrainParams.grainScaleShift = (byte) br.readBits(2);

        if (fh.filmGrainParams.numCbPoints > 0) {
            fh.filmGrainParams.cbMult = (byte) br.readBits(8);
            fh.filmGrainParams.cbLumaMult = (byte) br.readBits(8);
            fh.filmGrainParams.cbOffset = (short) br.readBits(9);
        }

        if (fh.filmGrainParams.numCrPoints > 0) {
            fh.filmGrainParams.crMult = (byte) br.readBits(8);
            fh.filmGrainParams.crLumaMult = (byte) br.readBits(8);
            fh.filmGrainParams.crOffset = (short) br.readBits(9);
        }

        fh.filmGrainParams.overlapFlag = br.readBits(1) != 0;
        fh.filmGrainParams.clipToRestrictedRange = br.readBits(1) != 0;
    }

    private static void parseTileInfo(BitReader br, OBPFrameHeader fh, OBPSequenceHeader seq) throws OBUParseException {
        int sbCols = seq.use128x128Superblock ? ((fh.miCols + 31) >> 5) : ((fh.miCols + 15) >> 4);
        int sbRows = seq.use128x128Superblock ? ((fh.miRows + 31) >> 5) : ((fh.miRows + 15) >> 4);
        int sbShift = seq.use128x128Superblock ? 5 : 4;
        int sbSize = sbShift + 2;
        int maxTileWidthSb = 4096 >> sbSize;
        int maxTileAreaSb = (4096 * 2304) >> (2 * sbSize);
        int minLog2TileCols = tileLog2(maxTileWidthSb, sbCols);
        int maxLog2TileCols = tileLog2(1, Math.min(sbCols, 64));
        int maxLog2TileRows = tileLog2(1, Math.min(sbRows, 64));
        int minLog2Tiles = Math.max(minLog2TileCols, tileLog2(maxTileAreaSb, sbRows * sbCols));
        fh.tileInfo.uniformTileSpacingFlag = br.readBits(1) != 0;
        if (fh.tileInfo.uniformTileSpacingFlag) {
            fh.tileInfo.tileColsLog2 = minLog2TileCols;
            while (fh.tileInfo.tileColsLog2 < maxLog2TileCols) {
                int incrementTileColsLog2 = br.readBits(1);
                if (incrementTileColsLog2 == 1) {
                    fh.tileInfo.tileColsLog2++;
                } else {
                    break;
                }
            }
            int tileWidthSb = (sbCols + (1 << fh.tileInfo.tileColsLog2) - 1) >> fh.tileInfo.tileColsLog2;
            int i = 0;
            for (int startSb = 0; startSb < sbCols; startSb += tileWidthSb) {
                i++;
            }
            fh.tileInfo.tileCols = i;
            int minLog2TileRows = Math.max(minLog2Tiles - fh.tileInfo.tileColsLog2, 0);
            fh.tileInfo.tileRowsLog2 = minLog2TileRows;
            while (fh.tileInfo.tileRowsLog2 < maxLog2TileRows) {
                int incrementTileRowsLog2 = br.readBits(1);
                if (incrementTileRowsLog2 == 1) {
                    fh.tileInfo.tileRowsLog2++;
                } else {
                    break;
                }
            }
            int tileHeightSb = (sbRows + (1 << fh.tileInfo.tileRowsLog2) - 1) >> fh.tileInfo.tileRowsLog2;
            i = 0;
            for (int startSb = 0; startSb < sbRows; startSb += tileHeightSb) {
                i++;
            }
            fh.tileInfo.tileRows = i;
        } else {
            int widestTileSb = 0;
            int startSb = 0;
            int i;
            for (i = 0; startSb < sbCols; i++) {
                int maxWidth = Math.min(sbCols - startSb, maxTileWidthSb);
                int widthInSbs = (int) readNs(br, maxWidth) + 1;
                widestTileSb = Math.max(widestTileSb, widthInSbs);
                startSb += widthInSbs;
            }
            fh.tileInfo.tileCols = i;
            fh.tileInfo.tileColsLog2 = tileLog2(1, fh.tileInfo.tileCols);

            if (minLog2Tiles > 0) {
                maxTileAreaSb = (sbRows * sbCols) >> (minLog2Tiles + 1);
            } else {
                maxTileAreaSb = sbRows * sbCols;
            }
            int maxTileHeightSb = Math.max(maxTileAreaSb / widestTileSb, 1);

            startSb = 0;
            for (i = 0; startSb < sbRows; i++) {
                int maxHeight = Math.min(sbRows - startSb, maxTileHeightSb);
                int heightInSbs = (int) readNs(br, maxHeight) + 1;
                startSb += heightInSbs;
            }
            fh.tileInfo.tileRows = i;
            fh.tileInfo.tileRowsLog2 = tileLog2(1, fh.tileInfo.tileRows);
        }
        if (fh.tileInfo.tileColsLog2 > 0 || fh.tileInfo.tileRowsLog2 > 0) {
            fh.tileInfo.contextUpdateTileId = br.readBits(fh.tileInfo.tileColsLog2 + fh.tileInfo.tileRowsLog2);
            fh.tileInfo.tileSizeBytesMinus1 = br.readBits(2);
        } else {
            fh.tileInfo.contextUpdateTileId = 0;
        }
    }

    private static void resetGrainParams(OBPFilmGrainParameters params) {
        params.applyGrain = false;
        params.grainSeed = 0;
        params.updateGrain = false;
        params.filmGrainParamsRefIdx = 0;
        params.numYPoints = 0;
        Arrays.fill(params.pointYValue, (byte) 0);
        Arrays.fill(params.pointYScaling, (byte) 0);
        params.chromaScalingFromLuma = false;
        params.numCbPoints = 0;
        Arrays.fill(params.pointCbValue, (byte) 0);
        Arrays.fill(params.pointCbScaling, (byte) 0);
        params.numCrPoints = 0;
        Arrays.fill(params.pointCrValue, (byte) 0);
        Arrays.fill(params.pointCrScaling, (byte) 0);
        params.grainScalingMinus8 = 0;
        params.arCoeffLag = 0;
        Arrays.fill(params.arCoeffsYPlus128, (byte) 0);
        Arrays.fill(params.arCoeffsCbPlus128, (byte) 0);
        Arrays.fill(params.arCoeffsCrPlus128, (byte) 0);
        params.arCoeffShiftMinus6 = 0;
        params.grainScaleShift = 0;
        params.cbMult = 0;
        params.cbLumaMult = 0;
        params.cbOffset = 0;
        params.crMult = 0;
        params.crLumaMult = 0;
        params.crOffset = 0;
        params.overlapFlag = false;
        params.clipToRestrictedRange = false;
    }

    private static int decodeSignedSubexpWithRef(BitReader br, int low, int high, int r) throws OBUParseException {
        int x = decodeUnsignedSubexpWithRef(br, high - low, r - low);
        return x + low;
    }

    private static int decodeUnsignedSubexpWithRef(BitReader br, int mx, int r) throws OBUParseException {
        int v;
        if ((r << 1) <= mx) {
            v = decodeSubexp(br, mx);
            if (v < r) {
                return v;
            } else {
                return mx - 1 - v + r;
            }
        } else {
            v = decodeSubexp(br, mx);
            if (v < (mx - r)) {
                return r + v;
            } else {
                return v - (mx - r);
            }
        }
    }

    private static int decodeSubexp(BitReader br, int numSyms) throws OBUParseException {
        int i = 0;
        int mk = 0;
        int k = 3;
        while (true) {
            int b2 = i != 0 ? k + i - 1 : k;
            int a = 1 << b2;
            if (numSyms <= mk + 3 * a) {
                return (int) readNs(br, numSyms - mk) + mk;
            } else {
                boolean subexpMoreBits = br.readBits(1) != 0;
                if (subexpMoreBits) {
                    i++;
                    mk += a;
                } else {
                    return (int) br.readBits(b2) + mk;
                }
            }
        }
    }

    private static long readNs(BitReader br, long n) throws OBUParseException {
        if (n == 0) {
            return 0;
        }
        int w = floorLog2(n) + 1;
        long m = (1L << w) - n;
        long v = br.readBits(w - 1);
        if (v < m) {
            return v;
        }
        long extraBit = br.readBits(1);
        return (v << 1) - m + extraBit;
    }

    private static int floorLog2(long n) {
        return 63 - Long.numberOfLeadingZeros(n);
    }

    private static int tileLog2(int blkSize, int target) {
        int k = 0;
        for (; (blkSize << k) < target; k++)
            ;
        return k;
    }

    private static void parseScalabilityStructure(BitReader br, OBPMetadata.MetadataScalability.ScalabilityStructure structure) throws OBUParseException {
        structure.spatialLayersCntMinus1 = (byte) br.readBits(2);
        structure.spatialLayerDimensionsPresentFlag = br.readBits(1) != 0;
        structure.spatialLayerDescriptionPresentFlag = br.readBits(1) != 0;
        structure.temporalGroupDescriptionPresentFlag = br.readBits(1) != 0;
        structure.scalabilityStructureReserved3bits = (byte) br.readBits(3);
        if (structure.spatialLayerDimensionsPresentFlag) {
            for (int i = 0; i <= structure.spatialLayersCntMinus1; i++) {
                structure.spatialLayerMaxWidth[i] = (short) br.readBits(16);
                structure.spatialLayerMaxHeight[i] = (short) br.readBits(16);
            }
        }
        if (structure.spatialLayerDescriptionPresentFlag) {
            for (int i = 0; i <= structure.spatialLayersCntMinus1; i++) {
                structure.spatialLayerRefId[i] = (byte) br.readBits(8);
            }
        }
        if (structure.temporalGroupDescriptionPresentFlag) {
            structure.temporalGroupSize = (byte) br.readBits(8);
            for (int i = 0; i < structure.temporalGroupSize; i++) {
                structure.temporalGroupTemporalId[i] = (byte) br.readBits(3);
                structure.temporalGroupTemporalSwitchingUpPointFlag[i] = br.readBits(1) != 0;
                structure.temporalGroupSpatialSwitchingUpPointFlag[i] = br.readBits(1) != 0;
                structure.temporalGroupRefCnt[i] = (byte) br.readBits(3);
                for (int j = 0; j < structure.temporalGroupRefCnt[i]; j++) {
                    structure.temporalGroupRefPicDiff[i][j] = (byte) br.readBits(8);
                }
            }
        }
    }

    private static long findItuT35PayloadSize(byte[] payload) {
        int nonZeroBytesCount = 0;
        for (int i = payload.length - 1; i >= 0; i--) {
            if (payload[i] != 0) {
                nonZeroBytesCount++;
                if (nonZeroBytesCount == 2) {
                    return i + 1;
                }
            }
        }
        return payload.length;
    }

    private static long readLe(byte[] buf, int offset, int n) {
        long t = 0;
        for (int i = 0; i < n; i++) {
            t |= ((long) (buf[offset + i] & 0xFF)) << (i * 8);
        }
        return t;
    }

    private static void parseSegmentationParams(BitReader br, OBPFrameHeader fh, OBPSequenceHeader seq) throws OBUParseException {
        fh.segmentationParams.segmentationEnabled = br.readBits(1) != 0;
        if (fh.segmentationParams.segmentationEnabled) {
            if (fh.primaryRefFrame == 7) {
                fh.segmentationParams.segmentationUpdateMap = true;
                fh.segmentationParams.segmentationTemporalUpdate = false;
                fh.segmentationParams.segmentationUpdateData = true;
            } else {
                fh.segmentationParams.segmentationUpdateMap = br.readBits(1) != 0;
                if (fh.segmentationParams.segmentationUpdateMap) {
                    fh.segmentationParams.segmentationTemporalUpdate = br.readBits(1) != 0;
                }
                fh.segmentationParams.segmentationUpdateData = br.readBits(1) != 0;
            }
            if (fh.segmentationParams.segmentationUpdateData) {
                for (int i = 0; i < 8; i++) {
                    for (int j = 0; j < 8; j++) {
                        int featureEnabled = br.readBits(1);
                        fh.segmentationParams.featureEnabled[i][j] = featureEnabled != 0;
                        if (featureEnabled != 0) {
                            int bitsToRead = OBPConstants.Segmentation_Feature_Bits[j];
                            int limit = OBPConstants.Segmentation_Feature_Max[j];
                            int featureValue;
                            if (OBPConstants.Segmentation_Feature_Signed[j] != 0) {
                                featureValue = br.readBits(1 + bitsToRead);
                                if ((featureValue & (1 << bitsToRead)) != 0) {
                                    featureValue = featureValue - (1 << (bitsToRead + 1));
                                }
                            } else {
                                featureValue = br.readBits(bitsToRead);
                            }
                            fh.segmentationParams.featureData[i][j] = (short) Math.max(-limit, Math.min(limit, featureValue));
                        } else {
                            fh.segmentationParams.featureData[i][j] = 0;
                        }
                    }
                }
            }
        } else {
            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    fh.segmentationParams.featureEnabled[i][j] = false;
                    fh.segmentationParams.featureData[i][j] = 0;
                }
            }
        }
    }

    /*
     * This method reads a delta Q value from the bitstream. It first reads a single bit to determine if the delta is
     * coded. If it is, it reads a signed value using 7 bits. If not, it returns 0.
     */
    private static int readDeltaQ(BitReader br) throws OBUParseException {
        int delta_coded = br.readBits(1);
        if (delta_coded != 0) {
            return br.readBits(7) - 1;
        } else {
            return 0;
        }
    }

    private static void parseDeltaQParams(BitReader br, OBPFrameHeader fh) throws OBUParseException {
        fh.deltaQParams.deltaQRes = 0;
        fh.deltaQParams.deltaQPresent = false;
        if (fh.quantizationParams.baseQIdx > 0) {
            fh.deltaQParams.deltaQPresent = br.readBits(1) != 0;
        }
        if (fh.deltaQParams.deltaQPresent) {
            fh.deltaQParams.deltaQRes = (byte) br.readBits(2);
        }
    }

    private static void parseDeltaLfParams(BitReader br, OBPFrameHeader fh, OBPSequenceHeader seq) throws OBUParseException {
        fh.deltaLfParams.deltaLfPresent = false;
        fh.deltaLfParams.deltaLfRes = 0;
        fh.deltaLfParams.deltaLfMulti = false;
        if (fh.deltaQParams.deltaQPresent) {
            if (!fh.allowIntrabc) {
                fh.deltaLfParams.deltaLfPresent = br.readBits(1) != 0;
            }
            if (fh.deltaLfParams.deltaLfPresent) {
                fh.deltaLfParams.deltaLfRes = (byte) br.readBits(2);
                fh.deltaLfParams.deltaLfMulti = br.readBits(1) != 0;
            }
        }
    }

    private static void parseLoopFilterParams(BitReader br, OBPFrameHeader fh, OBPSequenceHeader seq) throws OBUParseException {
        if (fh.codedLossless || fh.allowIntrabc) {
            fh.loopFilterParams.loopFilterDeltaEnabled = true;
            fh.loopFilterParams.loopFilterRefDeltas[0] = 1;
            fh.loopFilterParams.loopFilterRefDeltas[1] = 0;
            fh.loopFilterParams.loopFilterRefDeltas[2] = 0;
            fh.loopFilterParams.loopFilterRefDeltas[3] = 0;
            fh.loopFilterParams.loopFilterRefDeltas[4] = 0;
            fh.loopFilterParams.loopFilterRefDeltas[5] = -1;
            fh.loopFilterParams.loopFilterRefDeltas[6] = -1;
            fh.loopFilterParams.loopFilterRefDeltas[7] = -1;
            for (int i = 0; i < 2; i++) {
                fh.loopFilterParams.loopFilterModeDeltas[i] = 0;
            }
            return;
        }

        fh.loopFilterParams.loopFilterLevel[0] = (byte) br.readBits(6);
        fh.loopFilterParams.loopFilterLevel[1] = (byte) br.readBits(6);
        if (seq.colorConfig.NumPlanes > 1) {
            if (fh.loopFilterParams.loopFilterLevel[0] != 0 || fh.loopFilterParams.loopFilterLevel[1] != 0) {
                fh.loopFilterParams.loopFilterLevel[2] = (byte) br.readBits(6);
                fh.loopFilterParams.loopFilterLevel[3] = (byte) br.readBits(6);
            }
        }
        fh.loopFilterParams.loopFilterSharpness = (byte) br.readBits(3);
        fh.loopFilterParams.loopFilterDeltaEnabled = br.readBits(1) != 0;
        if (fh.loopFilterParams.loopFilterDeltaEnabled) {
            fh.loopFilterParams.loopFilterDeltaUpdate = br.readBits(1) != 0;
            if (fh.loopFilterParams.loopFilterDeltaUpdate) {
                for (int i = 0; i < 8; i++) {
                    fh.loopFilterParams.updateRefDelta[i] = br.readBits(1) != 0;
                    if (fh.loopFilterParams.updateRefDelta[i]) {
                        fh.loopFilterParams.loopFilterRefDeltas[i] = (byte) br.readBits(7);
                        if ((fh.loopFilterParams.loopFilterRefDeltas[i] & 0x40) != 0) {
                            fh.loopFilterParams.loopFilterRefDeltas[i] -= 128;
                        }
                    }
                }
                for (int i = 0; i < 2; i++) {
                    fh.loopFilterParams.updateModeDelta[i] = br.readBits(1) != 0;
                    if (fh.loopFilterParams.updateModeDelta[i]) {
                        fh.loopFilterParams.loopFilterModeDeltas[i] = (byte) br.readBits(7);
                        if ((fh.loopFilterParams.loopFilterModeDeltas[i] & 0x40) != 0) {
                            fh.loopFilterParams.loopFilterModeDeltas[i] -= 128;
                        }
                    }
                }
            }
        }
    }

    private static void parseCdefParams(BitReader br, OBPFrameHeader fh, OBPSequenceHeader seq) throws OBUParseException {
        if (fh.codedLossless || fh.allowIntrabc || !seq.enableCdef) {
            fh.cdefParams.cdefBits = 0;
            fh.cdefParams.cdefYPriStrength[0] = 0;
            fh.cdefParams.cdefYSecStrength[0] = 0;
            fh.cdefParams.cdefUvPriStrength[0] = 0;
            fh.cdefParams.cdefUvSecStrength[0] = 0;
            return;
        }

        fh.cdefParams.cdefDampingMinus3 = (byte) br.readBits(2);
        fh.cdefParams.cdefBits = (byte) br.readBits(2);
        for (int i = 0; i < (1 << fh.cdefParams.cdefBits); i++) {
            fh.cdefParams.cdefYPriStrength[i] = (byte) br.readBits(4);
            fh.cdefParams.cdefYSecStrength[i] = (byte) br.readBits(2);
            if (fh.cdefParams.cdefYSecStrength[i] == 3) {
                fh.cdefParams.cdefYSecStrength[i] += 1;
            }
            if (seq.colorConfig.NumPlanes > 1) {
                fh.cdefParams.cdefUvPriStrength[i] = (byte) br.readBits(4);
                fh.cdefParams.cdefUvSecStrength[i] = (byte) br.readBits(2);
                if (fh.cdefParams.cdefUvSecStrength[i] == 3) {
                    fh.cdefParams.cdefUvSecStrength[i] += 1;
                }
            }
        }
    }

    private static void parseLrParams(BitReader br, OBPFrameHeader fh, OBPSequenceHeader seq) throws OBUParseException {
        if (fh.allLossless || fh.allowIntrabc || !seq.enableRestoration) {
            fh.lrParams.lrType[0] = 0;
            fh.lrParams.lrType[1] = 0;
            fh.lrParams.lrType[2] = 0;
            return;
        }
        boolean usesLr = false;
        boolean usesChromaLr = false;
        for (int i = 0; i < seq.colorConfig.NumPlanes; i++) {
            fh.lrParams.lrType[i] = (byte) br.readBits(2);
            if (fh.lrParams.lrType[i] != 0) {
                usesLr = true;
                if (i > 0) {
                    usesChromaLr = true;
                }
            }
        }
        if (usesLr) {
            if (seq.use128x128Superblock) {
                fh.lrParams.lrUnitShift = (byte) (br.readBits(1) + 1);
            } else {
                fh.lrParams.lrUnitShift = (byte) br.readBits(1);
                if (fh.lrParams.lrUnitShift != 0) {
                    fh.lrParams.lrUnitShift += br.readBits(1);
                }
            }
            // LoopRestorationSize is not directly used in parsing, so we don't set it here
            if (seq.colorConfig.subsamplingX && seq.colorConfig.subsamplingY && usesChromaLr) {
                fh.lrParams.lrUvShift = br.readBits(1) != 0;
            } else {
                fh.lrParams.lrUvShift = false;
            }
        }
    }

    private static int getRelativeDist(int a, int b, OBPSequenceHeader seq) {
        if (seq.enableOrderHint) {
            int diff = a - b;
            int m = 1 << (seq.OrderHintBits - 1);
            diff = (diff & (m - 1)) - (diff & m);
            return diff;
        }
        return 0;
    }

    /**
     * Returns true if the given OBU type value is valid.
     *
     * @param type the OBU type value
     * @return true if the given OBU type value is valid
     */
    public static boolean isValidObu(int type) {
        return VALID_OBU_TYPES.contains(type);
    }

    /**
     * Returns true if the given OBU type is valid.
     *
     * @param type the OBU type
     * @return true if the given OBU type is valid
     */
    public static boolean isValidObu(OBUType type) {
        switch (type) {
            case SEQUENCE_HEADER:
                //case TEMPORAL_DELIMITER: // not meant for rtp transport
            case FRAME_HEADER:
            case TILE_GROUP:
            case METADATA:
            case FRAME:
            case REDUNDANT_FRAME_HEADER:
                //case TILE_LIST: // not meant for rtp transport
                //case PADDING: // not meant for rtp transport
                return true;
            default:
                return false;
        }
    }

    private static long readUvlc(BitReader br) throws OBUParseException {
        int leadingZeros = 0;
        while (leadingZeros < 32 && br.readBits(1) == 0) {
            leadingZeros++;
        }
        if (leadingZeros == 32) {
            throw new OBUParseException("Invalid UVLC code");
        }
        return br.readBits(leadingZeros) + ((1L << leadingZeros) - 1);
    }

    /**
     * Returns true if the given byte starts a fragment. This is denoted as Z in the spec: MUST be set to 1 if the
     * first OBU element is an OBU fragment that is a continuation of an OBU fragment from the previous packet, and
     * MUST be set to 0 otherwise.
     *
     * @param aggregationHeader
     * @return true if the given byte starts a fragment
     */
    public static boolean startsWithFragment(byte aggregationHeader) {
        return (aggregationHeader & OBU_START_FRAGMENT_BIT) != 0;
    }

    /**
     * Returns true if the given byte ends a fragment. This is denoted as Y in the spec: MUST be set to 1 if the last
     * OBU element is an OBU fragment that will continue in the next packet, and MUST be set to 0 otherwise.
     *
     * @param aggregationHeader
     * @return true if the given byte ends a fragment
     */
    public static boolean endsWithFragment(byte aggregationHeader) {
        return (aggregationHeader & OBU_END_FRAGMENT_BIT) != 0;
    }

    /**
     * Returns true if the given byte is the starts a new sequence. This denoted as N in the spec: MUST be set to 1 if
     * the packet is the first packet of a coded video sequence, and MUST be set to 0 otherwise.
     *
     * @param aggregationHeader
     * @return true if the given byte starts a new sequence
     */
    public static boolean startsNewCodedVideoSequence(byte aggregationHeader) {
        return (aggregationHeader & OBU_START_SEQUENCE_BIT) != 0;
    }

    /**
     * Returns expected number of OBU's.
     *
     * @param aggregationHeader
     * @return expected number of OBU's
     */
    public static int obuCount(byte aggregationHeader) {
        return (aggregationHeader & OBU_COUNT_MASK) >> 4;
    }

    /**
     * Returns the OBU type from the given byte.
     *
     * @param obuHeader
     * @return the OBU type
     */
    public static int obuType(byte obuHeader) {
        return (obuHeader & OBU_TYPE_MASK) >>> 3;
    }

    /**
     * Returns whether or not the OBU has an extension.
     *
     * @param obuHeader
     * @return true if the OBU has an extension
     */
    public static boolean obuHasExtension(byte obuHeader) {
        return (obuHeader & OBU_EXT_BIT) != 0;
    }

    /**
     * Returns whether or not the OBU has a size.
     *
     * @param obuHeader
     * @return true if the OBU has a size
     */
    public static boolean obuHasSize(byte obuHeader) {
        return (obuHeader & OBU_SIZE_PRESENT_BIT) != 0;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy