/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.codec.VP9');
/**
* VP9 utils.
*
* @see https://www.webmproject.org/vp9/
* @see https://www.webmproject.org/vp9/mp4/
*/
shaka.codec.VP9 = class {
/**
* Reads what the codec string of a stream needs from a key frame, for the
* containers that do not carry it apart from the frames.
*
* @param {!Uint8Array} frame A VP9 frame; only key frames are usable.
* @return {?{profile: number, bitDepth: number, chroma: number,
* width: number, height: number}}
* @see https://www.webmproject.org/vp9/ (uncompressed_header)
*/
static parseKeyFrame(frame) {
const VP9 = shaka.codec.VP9;
if (frame.length < VP9.MIN_KEYFRAME_SIZE_) {
return null;
}
let bit = 0;
const read = (count) => {
let value = 0;
for (let i = 0; i < count; i++) {
const byte = frame[bit >> 3];
value = (value << 1) | ((byte >> (7 - (bit & 7))) & 1);
bit++;
}
return value;
};
if (read(2) != VP9.FRAME_MARKER_) {
return null;
}
const profileLow = read(1);
const profileHigh = read(1);
const profile = (profileHigh << 1) + profileLow;
if (profile == VP9.PROFILE_WITH_RESERVED_BIT_) {
read(1);
}
const showExistingFrame = read(1);
if (showExistingFrame) {
return null;
}
const frameType = read(1);
// show_frame and error_resilient_mode.
read(2);
if (frameType != VP9.KEY_FRAME_) {
return null;
}
if (read(24) != VP9.SYNC_CODE_) {
return null;
}
let bitDepth = 8;
if (profile >= 2) {
bitDepth = read(1) ? 12 : 10;
}
const colorSpace = read(3);
let subsamplingX = 1;
let subsamplingY = 1;
if (colorSpace != VP9.COLOR_SPACE_RGB_) {
// color_range.
read(1);
if (profile == 1 || profile == 3) {
subsamplingX = read(1);
subsamplingY = read(1);
// reserved_zero.
read(1);
}
} else if (profile == 1 || profile == 3) {
subsamplingX = 0;
subsamplingY = 0;
}
const width = read(16) + 1;
const height = read(16) + 1;
let chroma = VP9.CHROMA_420_COLOCATED;
if (subsamplingX && !subsamplingY) {
chroma = VP9.CHROMA_422;
} else if (!subsamplingX && !subsamplingY) {
chroma = VP9.CHROMA_444;
}
return {profile, bitDepth, chroma, width, height};
}
/**
* Builds the codec string of a VP9 stream.
*
* @param {{profile: number, bitDepth: number, chroma: number}} info
* @param {number} width
* @param {number} height
* @param {?number} frameDuration Seconds per frame, if known.
* @return {string}
*/
static getCodecs(info, width, height, frameDuration) {
const VP9 = shaka.codec.VP9;
const pad = (value) => String(value).padStart(2, '0');
const level = VP9.getLevel_(width, height, frameDuration);
return 'vp09.' + pad(info.profile) + '.' + pad(level) + '.' +
pad(info.bitDepth) + '.' + pad(info.chroma);
}
/**
* Builds the payload of the vpcC box (VP Codec Configuration) from a VP9
* codec string.
*
* @param {string} codecs 'vp09.PP.LL.DD', optionally followed by
* '.CC.cp.tc.mc.FF'.
* @return {!Uint8Array}
* @see https://www.webmproject.org/vp9/mp4/#vp-codec-configuration-box
*/
static buildVpcC(codecs) {
const VP9 = shaka.codec.VP9;
// Skip the 'vp09' sample entry name and read the numeric fields.
const fields = codecs.split('.').slice(1).map((value) => {
return parseInt(value, 10);
});
const [profile = 0, level = 0, bitDepth = 8,
chroma = VP9.CHROMA_420_COLOCATED,
primaries = VP9.UNSPECIFIED_,
transfer = VP9.UNSPECIFIED_,
matrix = VP9.UNSPECIFIED_,
fullRange = 0] = fields;
return new Uint8Array([
// A full box: version 1, no flags.
1, 0, 0, 0,
profile,
level,
(bitDepth << 4) | (chroma << 1) | fullRange,
primaries,
transfer,
matrix,
// codecInitializationDataSize (2 bytes): VP9 has none.
0, 0,
]);
}
/**
* Picks the lowest VP9 level whose limits hold the picture size and the
* sample rate.
*
* @param {number} width
* @param {number} height
* @param {?number} frameDuration
* @return {number}
* @private
*/
static getLevel_(width, height, frameDuration) {
const VP9 = shaka.codec.VP9;
const pictureSize = width * height;
const frameRate = frameDuration ? 1 / frameDuration :
VP9.DEFAULT_FRAME_RATE_;
const sampleRate = pictureSize * frameRate;
for (const [level, maxSampleRate, maxPictureSize] of VP9.LEVELS_) {
if (pictureSize <= maxPictureSize && sampleRate <= maxSampleRate) {
return level;
}
}
return VP9.LEVELS_[VP9.LEVELS_.length - 1][0];
}
};
/**
* Values of the chroma subsampling field of the VP9 codec string.
* @const {number}
*/
shaka.codec.VP9.CHROMA_420_COLOCATED = 1;
/** @const {number} */
shaka.codec.VP9.CHROMA_422 = 2;
/** @const {number} */
shaka.codec.VP9.CHROMA_444 = 3;
/**
* The value of the color primaries, transfer and matrix fields when the stream
* does not say (H.273 "unspecified").
* @private @const {number}
*/
shaka.codec.VP9.UNSPECIFIED_ = 2;
/**
* Frame rate assumed to pick a level when the stream does not say.
* @private @const {number}
*/
shaka.codec.VP9.DEFAULT_FRAME_RATE_ = 30;
/**
* Fields of the uncompressed header of a VP9 frame: the marker every frame
* starts with, the profile whose header has one reserved bit, the value of
* frame_type for a key frame, the sync code of key frames and RGB's color
* space.
* @private @const {number}
*/
shaka.codec.VP9.FRAME_MARKER_ = 2;
/** @private @const {number} */
shaka.codec.VP9.PROFILE_WITH_RESERVED_BIT_ = 3;
/** @private @const {number} */
shaka.codec.VP9.KEY_FRAME_ = 0;
/** @private @const {number} */
shaka.codec.VP9.SYNC_CODE_ = 0x498342;
/** @private @const {number} */
shaka.codec.VP9.COLOR_SPACE_RGB_ = 7;
/**
* The bytes needed to read the uncompressed header of a key frame up to the
* frame size: marker, profile and flags, sync code, color config and size.
* @private @const {number}
*/
shaka.codec.VP9.MIN_KEYFRAME_SIZE_ = 12;
/**
* VP9 levels: [level, MaxLumaSampleRate, MaxLumaPictureSize], from Annex A of
* the VP9 bitstream specification. Levels 4/4.1 and 5/5.1/5.2 differ only in
* the bitrate and the sample rate limits.
* @private @const {!Array<!Array<number>>}
*/
shaka.codec.VP9.LEVELS_ = [
[10, 829440, 36864],
[11, 2764800, 73728],
[20, 4608000, 122880],
[21, 9216000, 245760],
[30, 20736000, 552960],
[31, 36864000, 983040],
[40, 83558400, 2228224],
[41, 160432128, 2228224],
[50, 311951360, 8912896],
[51, 588251136, 8912896],
[52, 1176502272, 8912896],
[60, 1176502272, 35651584],
[61, 2353004544, 35651584],
[62, 4706009088, 35651584],
];