mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
51765e9693
Fixes #7693
249 lines
5.9 KiB
JavaScript
249 lines
5.9 KiB
JavaScript
/**
|
|
* @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-bit width 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();
|
|
}
|
|
};
|