UNPKG

shaka-player

Version:
387 lines (342 loc) • 11.3 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.util.Obu'); goog.require('shaka.util.Uint8ArrayUtils'); /** * Open Bitstream Unit parsing. * * AV1 and IAMF both carry their data as a flat sequence of OBUs, each * self-delimiting through its own header. There are no start codes, no length * prefixes and — unlike H.264/H.265 — no emulation prevention bytes, so an OBU * payload can be read as a raw bit stream. * * MPEG-2 TS is the exception: it wraps every OBU in start codes and escapes * it, so its payloads have to go through parseAv1Ts() first. * * The two formats share the leb128() encoding but NOT the header layout, and * their obu_size fields have opposite meanings: in AV1 it excludes the header, * while in IAMF it covers the optional fields that follow it. They therefore * get one parser each, sharing only the primitives below. * * @see https://aomediacodec.github.io/av1-spec/av1-spec.pdf (section 5.3) * @see https://aomediacodec.github.io/iamf/ (section 3.2) * @see https://aomediacodec.github.io/av1-mpeg2-ts/ (section 3.2) */ shaka.util.Obu = class { /** * Reads an unsigned leb128() at the given offset. Returns null if the value * is truncated or malformed, in which case the rest of the data cannot be * trusted either. * * @param {!Uint8Array} data * @param {number} offset * @return {?{value: number, size: number}} */ static readLeb128(data, offset) { let value = 0; // A leb128() is at most 8 bytes, each holding 7 bits of the value plus a // continuation bit. for (let i = 0; i < 8; i++) { if (offset + i >= data.byteLength) { return null; } const byte = data[offset + i]; // Multiply rather than shift: the value can hold up to 56 significant // bits, and JavaScript's bitwise operators would truncate to 32. value += (byte & 0x7f) * Math.pow(2, i * 7); if (!(byte & 0x80)) { return {value, size: i + 1}; } } // More than 8 bytes is malformed. return null; } /** * Splits an AV1 temporal unit into its OBUs. * * @param {!Uint8Array} data * @return {!Array<shaka.util.Obu.Unit>} */ static parseAv1(data) { const Obu = shaka.util.Obu; /** @type {!Array<shaka.util.Obu.Unit>} */ const obus = []; let offset = 0; while (offset < data.byteLength) { const start = offset; // obu_header(): 1 forbidden bit, 4 bits obu_type, obu_extension_flag, // obu_has_size_field, and 1 reserved bit. const headerByte = data[offset++]; // obu_forbidden_bit must be 0. Anything else means we have lost sync, // and continuing would emit garbage OBUs. if (headerByte & 0x80) { break; } const type = (headerByte & 0x78) >> 3; const hasExtension = (headerByte & 0x04) != 0; const hasSizeField = (headerByte & 0x02) != 0; if (hasExtension) { if (offset >= data.byteLength) { break; } // obu_extension_header() is a single byte we have no use for. offset++; } let payloadSize; if (hasSizeField) { const leb128 = Obu.readLeb128(data, offset); if (!leb128) { break; } payloadSize = leb128.value; offset += leb128.size; } else { // Only the last OBU of a temporal unit may omit its size field, in // which case it runs to the end of the unit. payloadSize = data.byteLength - offset; } const payloadEnd = offset + payloadSize; if (payloadEnd > data.byteLength) { // Malformed: the declared size runs past the temporal unit. break; } obus.push({ type, data: data.subarray(offset, payloadEnd), fullData: data.subarray(start, payloadEnd), }); offset = payloadEnd; } return obus; } /** * Converts an AV1 access unit carried in MPEG-2 TS into the low overhead * bitstream format, which is what both parseAv1() and an `av01` sample * expect. Returns null when the access unit cannot be read. * * MPEG-2 TS does not carry the low overhead format directly: each OBU is * wrapped in a ts_open_bitstream_unit(), which prefixes it with a 0x000001 * start code and escapes the byte sequences that would emulate one, in the * same way H.264 does. On top of that, obu_size is only recommended there, * not required, so an OBU that relies on the start code to delimit it has * to get its size field back before anything else can read it. * * @param {!Uint8Array} data * @return {?Uint8Array} */ static parseAv1Ts(data) { const Obu = shaka.util.Obu; /** @type {!Array<!Uint8Array>} */ const pieces = []; // Offset of the first byte of the escaped OBU being read, i.e. just past // its start code. A start code is three bytes and no more: an OBU // payload may end in a zero — a temporal delimiter is 0x12 0x00 — and the // zeros ahead of the 0x01 that it does not need are its own. let obuStart = -1; let numZeros = 0; for (let i = 0; i < data.byteLength; i++) { const value = data[i]; if (!value) { numZeros++; continue; } if (value == 1 && numZeros >= 2) { if (obuStart >= 0) { const piece = Obu.normalizeTsObu_(data.subarray(obuStart, i - 2)); if (!piece) { return null; } pieces.push(...piece); } obuStart = i + 1; } numZeros = 0; } if (obuStart < 0) { // No start code at all: this is not an AV1 access unit. return null; } const piece = Obu.normalizeTsObu_(data.subarray(obuStart)); if (!piece) { return null; } pieces.push(...piece); return shaka.util.Uint8ArrayUtils.concatRange(pieces); } /** * Turns one escaped ts_open_bitstream_unit() payload, with its start code * already stripped, into the pieces of the equivalent low overhead OBU. * Returns null if the OBU header cannot be read. * * @param {!Uint8Array} data * @return {?Array<!Uint8Array>} * @private */ static normalizeTsObu_(data) { const Obu = shaka.util.Obu; const unescaped = Obu.unescapeTsObu_(data); if (!unescaped.byteLength) { // An empty OBU carries nothing; dropping it keeps the rest readable. return []; } const headerByte = unescaped[0]; // obu_forbidden_bit must be 0. Anything else means the start code we // synced on was not one, and the whole access unit is suspect. if (headerByte & 0x80) { return null; } if (headerByte & 0x02) { // obu_size is already there, so the OBU is low overhead as it stands. return [unescaped]; } // obu_extension_flag: the extension byte sits between the header and the // size field, so it has to stay ahead of the size we are inserting. const headerSize = (headerByte & 0x04) ? 2 : 1; if (unescaped.byteLength < headerSize) { return null; } const header = unescaped.slice(0, headerSize); // Set obu_has_size_field now that we are writing one. header[0] |= 0x02; return [ header, Obu.writeLeb128_(unescaped.byteLength - headerSize), unescaped.subarray(headerSize), ]; } /** * Removes the emulation prevention bytes from one escaped OBU. Returns the * input untouched when there are none, which is the common case. * * @param {!Uint8Array} data * @return {!Uint8Array} * @private */ static unescapeTsObu_(data) { /** @type {?Uint8Array} */ let unescaped = null; let outOffset = 0; let numZeros = 0; for (let i = 0; i < data.byteLength; i++) { const value = data[i]; if (numZeros == 2 && value == 0x03) { if (!unescaped) { // First escape of this OBU: copy what we have read so far, and keep // filling the copy from here on. unescaped = new Uint8Array(data.byteLength - 1); unescaped.set(data.subarray(0, i)); outOffset = i; } numZeros = 0; continue; } numZeros = value ? 0 : numZeros + 1; if (unescaped) { unescaped[outOffset++] = value; } } if (!unescaped) { return data; } return unescaped.subarray(0, outOffset); } /** * Encodes a value as an unsigned leb128(). * * @param {number} value * @return {!Uint8Array} * @private */ static writeLeb128_(value) { /** @type {!Array<number>} */ const bytes = []; do { const byte = value % 128; // Divide rather than shift: a leb128() can hold more significant bits // than JavaScript's bitwise operators keep. value = Math.floor(value / 128); bytes.push(value ? (byte | 0x80) : byte); } while (value); return new Uint8Array(bytes); } /** * Splits a sequence of IAMF OBUs. * * @param {!Uint8Array} data * @return {!Array<shaka.util.Obu.Unit>} */ static parseIamf(data) { const Obu = shaka.util.Obu; /** @type {!Array<shaka.util.Obu.Unit>} */ const obus = []; let offset = 0; while (offset < data.byteLength) { const start = offset; // obu_header(): 5 bits obu_type, obu_redundant_copy, // obu_trimming_status_flag and obu_extension_flag. const headerByte = data[offset++]; const type = headerByte >> 3; const hasTrimming = (headerByte & 0x02) != 0; const hasExtension = (headerByte & 0x01) != 0; // obu_size is always present, and covers the optional fields below it. const leb128 = Obu.readLeb128(data, offset); if (!leb128) { break; } offset += leb128.size; const obuEnd = offset + leb128.value; if (obuEnd > data.byteLength) { // Malformed: the declared size runs past the data. break; } if (hasTrimming) { const trimAtEnd = Obu.readLeb128(data, offset); if (!trimAtEnd) { break; } offset += trimAtEnd.size; const trimAtStart = Obu.readLeb128(data, offset); if (!trimAtStart) { break; } offset += trimAtStart.size; } if (hasExtension) { const extensionHeaderSize = Obu.readLeb128(data, offset); if (!extensionHeaderSize) { break; } offset += extensionHeaderSize.size + extensionHeaderSize.value; } if (offset > obuEnd) { // Malformed: the optional fields overran the OBU they belong to. break; } obus.push({ type, data: data.subarray(offset, obuEnd), fullData: data.subarray(start, obuEnd), }); offset = obuEnd; } return obus; } }; /** * One Open Bitstream Unit. * * `data` is the OBU payload alone, with the header and every field that * precedes the payload already skipped; `fullData` also covers the header, * which is what an `av1C` configOBUs entry needs. `type` is the obu_type * field, whose meaning depends on the format the OBU was parsed from. * * @typedef {{ * type: number, * data: !Uint8Array, * fullData: !Uint8Array, * }} */ shaka.util.Obu.Unit;