Source: lib/util/exp_golomb.js

/**
 * @license
 * Copyright Brightcove, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('shaka.util.ExpGolomb');

goog.require('shaka.util.BufferUtils');
goog.require('shaka.util.DataViewReader');


/**
 * @summary
 * Parser for exponential Golomb codes, a variable-bitwidth number encoding
 * scheme used by h264.
 * Based on https://github.com/videojs/mux.js/blob/main/lib/utils/exp-golomb.js
 *
 * @export
 */
shaka.util.ExpGolomb = class {
  /**
   * @param {!Uint8Array} data
   * @param {boolean=} convertEbsp2rbsp
   */
  constructor(data, convertEbsp2rbsp = false) {
    /** @private {!Uint8Array} */
    this.data_ = data;
    if (convertEbsp2rbsp) {
      this.data_ = this.ebsp2rbsp_(data);
    }

    /** @private {number} */
    this.workingBytesAvailable_ = this.data_.byteLength;

    // the current word being examined
    /** @private {number} */
    this.workingWord_ = 0;

    // the number of bits left to examine in the current word
    /** @private {number} */
    this.workingBitsAvailable_ = 0;
  }

  /**
   * @param {!Uint8Array} data
   * @return {!Uint8Array}
   * @private
   */
  ebsp2rbsp_(data) {
    const ret = new Uint8Array(data.byteLength);
    let retIndex = 0;

    for (let i = 0; i < data.byteLength; i++) {
      if (i >= 2) {
        // Unescape: Skip 0x03 after 00 00
        if (data[i] == 0x03 && data[i - 1] == 0x00 && data[i - 2] == 0x00) {
          continue;
        }
      }
      ret[retIndex] = data[i];
      retIndex++;
    }

    return shaka.util.BufferUtils.toUint8(ret, 0, retIndex);
  }

  /**
   * Load the next word
   *
   * @private
   */
  loadWord_() {
    const position = this.data_.byteLength - this.workingBytesAvailable_;
    const bytes = new Uint8Array(4);
    const availableBytes = Math.min(4, this.workingBytesAvailable_);

    if (availableBytes === 0) {
      return;
    }

    bytes.set(this.data_.subarray(position, position + availableBytes));
    const dataView = new shaka.util.DataViewReader(
        bytes, shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
    this.workingWord_ = dataView.readUint32();

    // track the amount of data that has been processed
    this.workingBitsAvailable_ = availableBytes * 8;
    this.workingBytesAvailable_ -= availableBytes;
  }

  /**
   * Skip n bits
   *
   * @param {number} count
   */
  skipBits(count) {
    if (this.workingBitsAvailable_ <= count) {
      count -= this.workingBitsAvailable_;
      const skipBytes = Math.floor(count / 8);
      count -= (skipBytes * 8);
      this.workingBitsAvailable_ -= skipBytes;
      this.loadWord_();
    }
    this.workingWord_ <<= count;
    this.workingBitsAvailable_ -= count;
  }

  /**
   * Read n bits
   *
   * @param {number} size
   * @return {number}
   */
  readBits(size) {
    let bits = Math.min(this.workingBitsAvailable_, size);
    const valu = this.workingWord_ >>> (32 - bits);
    this.workingBitsAvailable_ -= bits;
    if (this.workingBitsAvailable_ > 0) {
      this.workingWord_ <<= bits;
    } else if (this.workingBytesAvailable_ > 0) {
      this.loadWord_();
    }
    bits = size - bits;
    if (bits > 0) {
      return (valu << bits) | this.readBits(bits);
    }
    return valu;
  }

  /**
   * Return the number of skip leading zeros
   *
   * @return {number}
   * @private
   */
  skipLeadingZeros_() {
    let i;
    for (i = 0; i < this.workingBitsAvailable_; ++i) {
      if ((this.workingWord_ & (0x80000000 >>> i)) !== 0) {
        // the first bit of working word is 1
        this.workingWord_ <<= i;
        this.workingBitsAvailable_ -= i;
        return i;
      }
    }

    // we exhausted workingWord and still have not found a 1
    this.loadWord_();
    return i + this.skipLeadingZeros_();
  }

  /**
   * Skip exponential Golomb
   */
  skipExpGolomb() {
    this.skipBits(1 + this.skipLeadingZeros_());
  }

  /**
   * Return unsigned exponential Golomb
   *
   * @return {number}
   */
  readUnsignedExpGolomb() {
    const clz = this.skipLeadingZeros_();
    return this.readBits(clz + 1) - 1;
  }

  /**
   * Return exponential Golomb
   *
   * @return {number}
   */
  readExpGolomb() {
    const valu = this.readUnsignedExpGolomb();
    if (0x01 & valu) {
      // the number is odd if the low order bit is set
      // add 1 to make it even, and divide by 2
      return (1 + valu) >>> 1;
    }
    // divide by two then make it negative
    return -1 * (valu >>> 1);
  }

  /**
   * Read 1 bit as boolean
   *
   * @return {boolean}
   */
  readBoolean() {
    return this.readBits(1) === 1;
  }

  /**
   * Read 8 bits
   *
   * @return {number}
   */
  readUnsignedByte() {
    return this.readBits(8);
  }

  /**
   * The scaling list is optionally transmitted as part of a Sequence Parameter
   * Set (SPS).
   *
   * @param {number} count the number of entries in this scaling list
   * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
   */
  skipScalingList(count) {
    let lastScale = 8;
    let nextScale = 8;

    for (let j = 0; j < count; j++) {
      if (nextScale !== 0) {
        const deltaScale = this.readExpGolomb();
        nextScale = (lastScale + deltaScale + 256) % 256;
      }
      lastScale = (nextScale === 0) ? lastScale : nextScale;
    }
  }

  /**
   * Return the slice type
   *
   * @return {number}
   */
  readSliceType() {
    // skip Nalu type
    this.readUnsignedByte();
    // discard first_mb_in_slice
    this.readUnsignedExpGolomb();
    // return slice_type
    return this.readUnsignedExpGolomb();
  }
};