/**
* @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();
}
};