UNPKG

shaka-player

Version:
315 lines (289 loc) • 9.87 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.util.WebmGenerator'); goog.require('shaka.mkv.BlockFlag'); goog.require('shaka.mkv.ElementId'); goog.require('shaka.mkv.TrackType'); goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.SegmentGenerator'); goog.require('shaka.util.Uint8ArrayUtils'); /** * Writes the initialization and media segments that MSE plays for WebM: a * header with one track, and Clusters of SimpleBlocks. It is what Matroska * needs to become WebM (VP8, Vorbis): WebM is a subset of it, and the frames * themselves are the same. Blocks that lace several frames are the exception, * as MSE does not take lacing: each frame gets a block of its own. * * @implements {shaka.util.SegmentGenerator} */ shaka.util.WebmGenerator = class { /** * @param {shaka.util.WebmGenerator.StreamInfo} streamInfo */ constructor(streamInfo) { /** @private {shaka.util.WebmGenerator.StreamInfo} */ this.streamInfo_ = streamInfo; } /** * Generates an initialization segment. * * @override * @return {!Uint8Array} */ initSegment() { const WebmGenerator = shaka.util.WebmGenerator; const ElementId = shaka.mkv.ElementId; const header = WebmGenerator.element_(ElementId.EBML, WebmGenerator.uint_(ElementId.EBML_VERSION, 1), WebmGenerator.uint_(ElementId.EBML_READ_VERSION, 1), WebmGenerator.uint_(ElementId.EBML_MAX_ID_LENGTH, 4), WebmGenerator.uint_(ElementId.EBML_MAX_SIZE_LENGTH, 8), WebmGenerator.string_(ElementId.DOC_TYPE, 'webm'), WebmGenerator.uint_(ElementId.DOC_TYPE_VERSION, 4), WebmGenerator.uint_(ElementId.DOC_TYPE_READ_VERSION, 2)); const info = WebmGenerator.element_(ElementId.INFO, WebmGenerator.uint_( ElementId.TIMECODE_SCALE, this.streamInfo_.timecodeScale), WebmGenerator.string_(ElementId.MUXING_APP, 'Shaka Player'), WebmGenerator.string_(ElementId.WRITING_APP, 'Shaka Player')); const tracks = WebmGenerator.element_( ElementId.TRACKS, this.trackEntry_()); // The Segment has no size, as it goes on for as long as media is appended. return shaka.util.Uint8ArrayUtils.concat(header, WebmGenerator.idBytes_(ElementId.SEGMENT), WebmGenerator.UNKNOWN_SIZE_, info, tracks); } /** * Generates a media segment: one or more Clusters. * * @override * @return {!Uint8Array} */ segmentData() { const WebmGenerator = shaka.util.WebmGenerator; const ElementId = shaka.mkv.ElementId; const frames = this.streamInfo_.frames; const clusters = []; let start = 0; while (start < frames.length) { // A block writes its time as a signed 16-bit number of ticks after the // time of its Cluster, so a Cluster can only hold so much time. const clusterTimecode = frames[start].timecode; let end = start; const blocks = []; while (end < frames.length && frames[end].timecode - clusterTimecode <= WebmGenerator.MAX_CLUSTER_TICKS_) { blocks.push(this.simpleBlock_(frames[end], clusterTimecode)); end++; } clusters.push(WebmGenerator.element_(ElementId.CLUSTER, WebmGenerator.uint_(ElementId.CLUSTER_TIMECODE, clusterTimecode), ...blocks)); start = end; } return shaka.util.Uint8ArrayUtils.concat(...clusters); } /** * @return {!Uint8Array} * @private */ trackEntry_() { const WebmGenerator = shaka.util.WebmGenerator; const ElementId = shaka.mkv.ElementId; const info = this.streamInfo_; const isVideo = info.type == shaka.mkv.TrackType.VIDEO; const fields = [ WebmGenerator.uint_(ElementId.TRACK_NUMBER, info.trackNumber), // The identifier of the track; the number is as unique as anything. WebmGenerator.uint_(ElementId.TRACK_UID, info.trackNumber), WebmGenerator.uint_(ElementId.TRACK_TYPE, info.type), WebmGenerator.string_(ElementId.CODEC_ID, info.codecId), ]; if (info.codecPrivate) { fields.push(WebmGenerator.element_( ElementId.CODEC_PRIVATE, info.codecPrivate)); } if (isVideo) { fields.push(WebmGenerator.element_(ElementId.VIDEO, WebmGenerator.uint_(ElementId.PIXEL_WIDTH, info.width), WebmGenerator.uint_(ElementId.PIXEL_HEIGHT, info.height))); } else { fields.push(WebmGenerator.element_(ElementId.AUDIO, WebmGenerator.float_(ElementId.SAMPLING_FREQUENCY, info.sampleRate), WebmGenerator.uint_(ElementId.CHANNELS, info.channelCount))); } return WebmGenerator.element_(ElementId.TRACK_ENTRY, ...fields); } /** * @param {shaka.util.WebmGenerator.Frame} frame * @param {number} clusterTimecode * @return {!Uint8Array} * @private */ simpleBlock_(frame, clusterTimecode) { const WebmGenerator = shaka.util.WebmGenerator; const relative = frame.timecode - clusterTimecode; // The track number is a variable-length integer, whose marker is part of // its value; a size is the same thing. const trackNumber = WebmGenerator.sizeVint_(this.streamInfo_.trackNumber); // The keyframe flag is set on every block of a track that has no other // kind (audio). const isKeyframe = frame.keyframe || this.streamInfo_.type != shaka.mkv.TrackType.VIDEO; const flags = isKeyframe ? shaka.mkv.BlockFlag.KEYFRAME : 0; return WebmGenerator.element_(shaka.mkv.ElementId.SIMPLE_BLOCK, trackNumber, new Uint8Array([(relative >> 8) & 0xff, relative & 0xff, flags]), frame.data); } /** * @param {number} id * @param {...!Uint8Array} payload * @return {!Uint8Array} * @private */ static element_(id, ...payload) { const WebmGenerator = shaka.util.WebmGenerator; const body = shaka.util.Uint8ArrayUtils.concat(...payload); return shaka.util.Uint8ArrayUtils.concat( WebmGenerator.idBytes_(id), WebmGenerator.sizeVint_(body.length), body); } /** * @param {number} id * @param {number} value * @return {!Uint8Array} * @private */ static uint_(id, value) { const bytes = []; let rest = value; do { bytes.unshift(rest % 256); rest = Math.floor(rest / 256); } while (rest > 0); return shaka.util.WebmGenerator.element_(id, new Uint8Array(bytes)); } /** * @param {number} id * @param {number} value * @return {!Uint8Array} * @private */ static float_(id, value) { const bytes = new Uint8Array(8); shaka.util.BufferUtils.toDataView(bytes).setFloat64(0, value); return shaka.util.WebmGenerator.element_(id, bytes); } /** * @param {number} id * @param {string} value * @return {!Uint8Array} * @private */ static string_(id, value) { return shaka.util.WebmGenerator.element_(id, new Uint8Array( Array.from(value).map((char) => char.charCodeAt(0)))); } /** * @param {number} id An element ID, with its length marker. * @return {!Uint8Array} * @private */ static idBytes_(id) { const bytes = []; for (let rest = id; rest > 0; rest = Math.floor(rest / 256)) { bytes.unshift(rest % 256); } return new Uint8Array(bytes); } /** * Writes a variable-length integer with as few bytes as it can: the number * of leading zeros of the first byte is the number of bytes that follow it. * * @param {number} value * @return {!Uint8Array} * @private */ static sizeVint_(value) { for (let length = 1; length <= 8; length++) { // All the bits set is the reserved "unknown" value. if (value < Math.pow(2, 7 * length) - 1) { const bytes = new Uint8Array(length); let rest = value; for (let i = length - 1; i >= 0; i--) { bytes[i] = rest % 256; rest = Math.floor(rest / 256); } bytes[0] |= 0x80 >> (length - 1); return bytes; } } throw new RangeError('The value does not fit an EBML integer'); } }; /** * @typedef {{ * timecode: number, * data: !Uint8Array, * keyframe: boolean, * }} * * @property {number} timecode * The time, in ticks of the timecode scale of the stream. * @property {!Uint8Array} data * @property {boolean} keyframe */ shaka.util.WebmGenerator.Frame; /** * @typedef {{ * trackNumber: number, * type: number, * codecId: string, * codecPrivate: ?Uint8Array, * timecodeScale: number, * width: number, * height: number, * channelCount: number, * sampleRate: number, * frames: !Array<shaka.util.WebmGenerator.Frame>, * }} * * @property {number} trackNumber * @property {number} type * A shaka.mkv.TrackType. * @property {string} codecId * The Matroska CodecID. * @property {?Uint8Array} codecPrivate * @property {number} timecodeScale * Nanoseconds per tick. * @property {number} width * Of a video track. * @property {number} height * Of a video track. * @property {number} channelCount * Of an audio track. * @property {number} sampleRate * Of an audio track. * @property {!Array<shaka.util.WebmGenerator.Frame>} frames * The frames of the media segment. */ shaka.util.WebmGenerator.StreamInfo; /** * The size that EBML writes to say that an element goes on to the end: 8 bytes, * all the bits of the value set. * * @private @const {!Uint8Array} */ shaka.util.WebmGenerator.UNKNOWN_SIZE_ = new Uint8Array([0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); /** * How far apart in time the first and the last block of a Cluster may be. The * time of a block is a signed 16-bit number of ticks after that of its Cluster, * so this leaves room for the difference to be rounded. * * @private @const {number} */ shaka.util.WebmGenerator.MAX_CLUSTER_TICKS_ = 30000;