UNPKG

shaka-player

Version:
542 lines (480 loc) • 15.8 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.codec.AV1'); goog.require('shaka.util.ExpGolomb'); goog.require('shaka.util.Obu'); /** * AV1 utils. * * An AV1 access unit is a "temporal unit", split into its Open Bitstream Units * by shaka.util.Obu.parseAv1. This is what reads meaning out of those OBUs. * * @see https://aomediacodec.github.io/av1-spec/av1-spec.pdf * @see https://aomediacodec.github.io/av1-isobmff/ (av1C) */ shaka.codec.AV1 = class { /** * Gets the RFC 6381 codec string from an AV1CodecConfigurationRecord (av1C). * * @param {!Uint8Array} av1C * @return {string} */ static getCodecs(av1C) { // Byte 1: seq_profile (3 bits) and seq_level_idx_0 (5 bits). // Byte 2: seq_tier_0, high_bitdepth, twelve_bit, monochrome, ... const profile = av1C[1] >> 5; const level = av1C[1] & 0x1f; const tier = av1C[2] & 0x80 ? 'H' : 'M'; const highBitDepth = !!(av1C[2] & 0x40); const twelveBit = !!(av1C[2] & 0x20); const bitDepth = twelveBit ? 12 : (highBitDepth ? 10 : 8); return 'av01.' + profile + '.' + String(level).padStart(2, '0') + tier + '.' + String(bitDepth).padStart(2, '0'); } /** * Reads the sequence header of a temporal unit and returns the properties * needed to describe the stream, or null when the unit carries none. * * Only key frames carry a sequence header, so callers must cache the result * and reuse it for the inter frames that follow. * * @param {!Array<shaka.util.Obu.Unit>} obus * @return {?shaka.codec.AV1.Info} */ static parseInfo(obus) { const AV1 = shaka.codec.AV1; if (!obus.length) { return null; } const sequenceHeader = obus.find((obu) => { return obu.type == AV1.OBU_TYPE_SEQUENCE_HEADER_; }); if (!sequenceHeader) { return null; } const parsed = AV1.parseSequenceHeader_(sequenceHeader.data); if (!parsed) { return null; } return { width: parsed.width, height: parsed.height, reducedStillPicture: parsed.reducedStillPicture, videoConfig: AV1.buildAv1C_(parsed, sequenceHeader.fullData), }; } /** * Returns whether a temporal unit codes a shown key frame. * * `uncompressed_header()` (AV1 §5.9.2) puts `show_existing_frame` and * `frame_type` in the first three bits of a frame header, so this needs no * state from the sequence header beyond `reduced_still_picture_header` — * which suppresses both fields and makes every frame a key frame. * * @param {!Array<shaka.util.Obu.Unit>} obus * @param {boolean} reducedStillPicture * @return {boolean} */ static isKeyframe(obus, reducedStillPicture) { const AV1 = shaka.codec.AV1; for (const obu of obus) { if (obu.type != AV1.OBU_TYPE_FRAME_ && obu.type != AV1.OBU_TYPE_FRAME_HEADER_) { continue; } if (reducedStillPicture) { return true; } if (!obu.data.byteLength) { return false; } const firstByte = obu.data[0]; // show_existing_frame f(1) — a repeat of an already decoded frame, which // codes no picture of its own and is never a key frame here. if (firstByte & 0x80) { return false; } // frame_type f(2), where 0 is KEY_FRAME. return ((firstByte & 0x60) >> 5) == AV1.FRAME_TYPE_KEY_; } return false; } /** * Builds an AV1CodecConfigurationRecord (the payload of an `av1C` box). * * The record is four fixed bytes describing the decoder requirements, * followed by `configOBUs` — the sequence header itself. The OBU bytes are * copied verbatim rather than re-serialised from the parsed fields, because * a sequence header carries data this parser does not model (operating * points, timing info, trailing bits), and the decoder is configured from * these bytes. * * @param {shaka.codec.AV1.SequenceHeader} header * @param {!Uint8Array} sequenceHeaderObu * @return {!Uint8Array} * @private */ static buildAv1C_(header, sequenceHeaderObu) { const record = new Uint8Array(4 + sequenceHeaderObu.byteLength); // marker f(1) = 1, version f(7) = 1 record[0] = 0x81; // seq_profile f(3), seq_level_idx_0 f(5) record[1] = ((header.seqProfile & 0x07) << 5) | (header.seqLevelIdx & 0x1f); // seq_tier_0 f(1), high_bitdepth f(1), twelve_bit f(1), monochrome f(1), // chroma_subsampling_x f(1), chroma_subsampling_y f(1), // chroma_sample_position f(2) record[2] = (header.seqTier << 7) | (header.highBitdepth << 6) | (header.twelveBit << 5) | (header.monochrome << 4) | (header.subsamplingX << 3) | (header.subsamplingY << 2) | (header.chromaSamplePosition & 0x03); // reserved f(3) = 0, initial_presentation_delay_present f(1) = 0, // reserved f(4) = 0. We do not signal a presentation delay: it is // optional, and the frames arrive one per LOC object in decode order. record[3] = 0x00; record.set(sequenceHeaderObu, 4); return record; } /** * Parses `sequence_header_obu()` (AV1 §5.5.1). * * @param {!Uint8Array} data The OBU payload, without its header. * @return {?shaka.codec.AV1.SequenceHeader} * @private */ static parseSequenceHeader_(data) { const AV1 = shaka.codec.AV1; if (!data.byteLength) { return null; } const reader = AV1.makeReader_(data); const seqProfile = AV1.readBits_(reader, 3); AV1.readBits_(reader, 1); // still_picture const reducedStillPicture = AV1.readBits_(reader, 1) == 1; let seqLevelIdx = 0; let seqTier = 0; if (reducedStillPicture) { seqLevelIdx = AV1.readBits_(reader, 5); } else { let decoderModelInfoPresent = false; let bufferDelayLength = 0; const timingInfoPresent = AV1.readBits_(reader, 1) == 1; if (timingInfoPresent) { // timing_info(): num_units_in_display_tick f(32), time_scale f(32) AV1.skipBits_(reader, 64); const equalPictureInterval = AV1.readBits_(reader, 1) == 1; if (equalPictureInterval) { AV1.skipUvlc_(reader); // num_ticks_per_picture_minus_1 } decoderModelInfoPresent = AV1.readBits_(reader, 1) == 1; if (decoderModelInfoPresent) { bufferDelayLength = AV1.readBits_(reader, 5) + 1; AV1.skipBits_(reader, 32); // num_units_in_decoding_tick // buffer_removal_time_length_minus_1 f(5), // frame_presentation_time_length_minus_1 f(5) AV1.skipBits_(reader, 10); } } const initialDisplayDelayPresent = AV1.readBits_(reader, 1) == 1; const operatingPointsCnt = AV1.readBits_(reader, 5) + 1; for (let i = 0; i < operatingPointsCnt; i++) { AV1.skipBits_(reader, 12); // operating_point_idc[i] const levelIdx = AV1.readBits_(reader, 5); // Levels above 7 (i.e. 4.0 and up) are the only ones that define a // tier; below that the tier is implicitly Main. const tier = levelIdx > 7 ? AV1.readBits_(reader, 1) : 0; if (i == 0) { seqLevelIdx = levelIdx; seqTier = tier; } if (decoderModelInfoPresent) { // decoder_model_present_for_this_op[i] if (AV1.readBits_(reader, 1) == 1) { // operating_parameters_info(i): decoder_buffer_delay f(n), // encoder_buffer_delay f(n), low_delay_mode_flag f(1) AV1.skipBits_(reader, 2 * bufferDelayLength + 1); } } if (initialDisplayDelayPresent) { if (AV1.readBits_(reader, 1) == 1) { AV1.readBits_(reader, 4); // initial_display_delay_minus_1[i] } } } } const frameWidthBits = AV1.readBits_(reader, 4) + 1; const frameHeightBits = AV1.readBits_(reader, 4) + 1; const width = AV1.readBits_(reader, frameWidthBits) + 1; const height = AV1.readBits_(reader, frameHeightBits) + 1; if (!reducedStillPicture) { if (AV1.readBits_(reader, 1) == 1) { // frame_id_numbers_present_flag // delta_frame_id_length_minus_2 f(4), // additional_frame_id_length_minus_1 f(3) AV1.skipBits_(reader, 7); } } // use_128x128_superblock, enable_filter_intra, enable_intra_edge_filter AV1.skipBits_(reader, 3); if (!reducedStillPicture) { // enable_interintra_compound, enable_masked_compound, // enable_warped_motion, enable_dual_filter AV1.skipBits_(reader, 4); const enableOrderHint = AV1.readBits_(reader, 1) == 1; if (enableOrderHint) { // enable_jnt_comp, enable_ref_frame_mvs AV1.skipBits_(reader, 2); } let seqForceScreenContentTools = AV1.SELECT_SCREEN_CONTENT_TOOLS_; if (AV1.readBits_(reader, 1) == 0) { // seq_choose_screen_content_tools seqForceScreenContentTools = AV1.readBits_(reader, 1); } if (seqForceScreenContentTools > 0) { if (AV1.readBits_(reader, 1) == 0) { // seq_choose_integer_mv AV1.readBits_(reader, 1); // seq_force_integer_mv } } if (enableOrderHint) { AV1.readBits_(reader, 3); // order_hint_bits_minus_1 } } // enable_superres, enable_cdef, enable_restoration AV1.skipBits_(reader, 3); // ── color_config() (AV1 §5.5.2) ────────────────────────────────────── const highBitdepth = AV1.readBits_(reader, 1); let twelveBit = 0; let bitDepth = highBitdepth ? 10 : 8; if (seqProfile == 2 && highBitdepth) { twelveBit = AV1.readBits_(reader, 1); bitDepth = twelveBit ? 12 : 10; } const monochrome = seqProfile == 1 ? 0 : AV1.readBits_(reader, 1); let colorPrimaries = AV1.CP_UNSPECIFIED_; let transferCharacteristics = AV1.TC_UNSPECIFIED_; let matrixCoefficients = AV1.MC_UNSPECIFIED_; if (AV1.readBits_(reader, 1) == 1) { // color_description_present_flag colorPrimaries = AV1.readBits_(reader, 8); transferCharacteristics = AV1.readBits_(reader, 8); matrixCoefficients = AV1.readBits_(reader, 8); } let subsamplingX = 1; let subsamplingY = 1; let chromaSamplePosition = AV1.CSP_UNKNOWN_; if (monochrome) { AV1.readBits_(reader, 1); // color_range } else if (colorPrimaries == AV1.CP_BT_709_ && transferCharacteristics == AV1.TC_SRGB_ && matrixCoefficients == AV1.MC_IDENTITY_) { // Lossless sRGB: 4:4:4 with an implied full color range. subsamplingX = 0; subsamplingY = 0; } else { AV1.readBits_(reader, 1); // color_range if (seqProfile == 0) { // Main profile is 4:2:0 only. subsamplingX = 1; subsamplingY = 1; } else if (seqProfile == 1) { // High profile is 4:4:4 only. subsamplingX = 0; subsamplingY = 0; } else if (bitDepth == 12) { subsamplingX = AV1.readBits_(reader, 1); subsamplingY = subsamplingX ? AV1.readBits_(reader, 1) : 0; } else { // Professional profile below 12 bits is 4:2:2 only. subsamplingX = 1; subsamplingY = 0; } if (subsamplingX && subsamplingY) { chromaSamplePosition = AV1.readBits_(reader, 2); } } // A header that ran past the end of the OBU was truncated, or was never a // sequence header at all. Either way the fields above are meaningless, and // an av1C built from them would misconfigure the decoder. if (reader.overrun || width <= 1 || height <= 1) { return null; } return { seqProfile, seqLevelIdx, seqTier, highBitdepth, twelveBit, monochrome, subsamplingX, subsamplingY, chromaSamplePosition, width, height, reducedStillPicture, }; } /** * Wraps an ExpGolomb in a bit budget. * * A sequence header is a chain of conditionally present fields, so a * truncated one is not detected by any single read — the parser simply keeps * asking for bits that are not there. ExpGolomb answers those by recursing * on itself until the stack gives out, so the budget is tracked here, and * once it is spent every read returns 0 and `overrun` stays set for the * caller to reject the whole header. * * @param {!Uint8Array} data * @return {shaka.codec.AV1.Reader} * @private */ static makeReader_(data) { return { gb: new shaka.util.ExpGolomb(data), bitsLeft: data.byteLength * 8, overrun: false, }; } /** * Reads `count` bits, at most 16 at a time so the result of a read can never * be interpreted as a negative 32-bit integer. * * @param {shaka.codec.AV1.Reader} reader * @param {number} count * @return {number} * @private */ static readBits_(reader, count) { if (count > reader.bitsLeft) { reader.overrun = true; reader.bitsLeft = 0; return 0; } reader.bitsLeft -= count; let value = 0; while (count > 0) { const chunk = Math.min(count, 16); value = (value * Math.pow(2, chunk)) + reader.gb.readBits(chunk); count -= chunk; } return value; } /** * Skips `count` bits. * * @param {shaka.codec.AV1.Reader} reader * @param {number} count * @private */ static skipBits_(reader, count) { shaka.codec.AV1.readBits_(reader, count); } /** * Skips a variable length unsigned integer (AV1 §4.10.3). * * @param {shaka.codec.AV1.Reader} reader * @private */ static skipUvlc_(reader) { const AV1 = shaka.codec.AV1; let leadingZeros = 0; while (leadingZeros < 32 && AV1.readBits_(reader, 1) == 0) { if (reader.overrun) { return; } leadingZeros++; } if (leadingZeros < 32) { AV1.skipBits_(reader, leadingZeros); } } }; /** * @typedef {{ * width: number, * height: number, * reducedStillPicture: boolean, * videoConfig: !Uint8Array, * }} */ shaka.codec.AV1.Info; /** * A bit reader with a budget. `overrun` latches once a read has asked for more * bits than the buffer holds. * * @typedef {{ * gb: !shaka.util.ExpGolomb, * bitsLeft: number, * overrun: boolean, * }} */ shaka.codec.AV1.Reader; /** * @typedef {{ * seqProfile: number, * seqLevelIdx: number, * seqTier: number, * highBitdepth: number, * twelveBit: number, * monochrome: number, * subsamplingX: number, * subsamplingY: number, * chromaSamplePosition: number, * width: number, * height: number, * reducedStillPicture: boolean, * }} */ shaka.codec.AV1.SequenceHeader; /** * @private @const {number} */ shaka.codec.AV1.OBU_TYPE_SEQUENCE_HEADER_ = 1; /** * @private @const {number} */ shaka.codec.AV1.OBU_TYPE_FRAME_HEADER_ = 3; /** * @private @const {number} */ shaka.codec.AV1.OBU_TYPE_FRAME_ = 6; /** * @private @const {number} */ shaka.codec.AV1.FRAME_TYPE_KEY_ = 0; /** * Sentinel meaning "decide per frame" for seq_force_screen_content_tools. * * @private @const {number} */ shaka.codec.AV1.SELECT_SCREEN_CONTENT_TOOLS_ = 2; /** * @private @const {number} */ shaka.codec.AV1.CP_BT_709_ = 1; /** * @private @const {number} */ shaka.codec.AV1.CP_UNSPECIFIED_ = 2; /** * @private @const {number} */ shaka.codec.AV1.TC_UNSPECIFIED_ = 2; /** * @private @const {number} */ shaka.codec.AV1.TC_SRGB_ = 13; /** * @private @const {number} */ shaka.codec.AV1.MC_IDENTITY_ = 0; /** * @private @const {number} */ shaka.codec.AV1.MC_UNSPECIFIED_ = 2; /** * @private @const {number} */ shaka.codec.AV1.CSP_UNKNOWN_ = 0;