/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.codec.Flac');
goog.require('shaka.util.BufferUtils');
/**
* FLAC utils.
*
* @see https://www.rfc-editor.org/rfc/rfc9639.html
*/
shaka.codec.Flac = class {
/**
* Reads the STREAMINFO block of a FLAC stream.
*
* @param {!Uint8Array} metadata The metadata blocks, i.e. the STREAMINFO
* block, which comes first, and the ones that follow. A leading "fLaC"
* marker, as Matroska stores it, is accepted.
* @return {?shaka.codec.Flac.StreamInfo}
*/
static parseStreamInfo(metadata) {
const Flac = shaka.codec.Flac;
let offset = Flac.hasMarker_(metadata) ? Flac.MARKER_.length : 0;
// The block header: the type (0 for STREAMINFO) with a flag that marks the
// last block, and the 24-bit length of the block.
if (metadata.length < offset + Flac.BLOCK_HEADER_SIZE_ +
Flac.STREAM_INFO_SIZE_ ||
(metadata[offset] & Flac.BLOCK_TYPE_MASK_) !=
Flac.BLOCK_TYPE_STREAM_INFO_) {
return null;
}
offset += Flac.BLOCK_HEADER_SIZE_;
const view = shaka.util.BufferUtils.toDataView(
metadata, offset, Flac.STREAM_INFO_SIZE_);
const minBlockSize = view.getUint16(0);
const maxBlockSize = view.getUint16(2);
// Sample rate (20 bits), channels minus one (3), bits per sample minus
// one (5) and the total samples (36), packed in 8 bytes. The first 32
// bits hold all but the last 4 bits of the total.
const packed = view.getUint32(10);
const sampleRate = packed >>> 12;
const channelCount = ((packed >>> 9) & 0x07) + 1;
const bitsPerSample = ((packed >>> 4) & 0x1f) + 1;
if (!sampleRate || !maxBlockSize) {
return null;
}
return {minBlockSize, maxBlockSize, sampleRate, channelCount,
bitsPerSample};
}
/**
* Gets the payload of the dfLa box (FLAC specific box): a full box that
* holds the metadata blocks. Only the STREAMINFO block, which is all a
* decoder needs, is written, and it is marked as the last block. The
* metadata of a Matroska track has it with the flag cleared, when the muxer
* left the blocks that follow it out, and a decoder that follows the
* specification to the letter then looks for a block that is not there.
*
* @param {!Uint8Array} metadata The metadata blocks, with or without the
* "fLaC" marker.
* @return {!Uint8Array}
*/
static buildDfLa(metadata) {
const Flac = shaka.codec.Flac;
const offset = Flac.hasMarker_(metadata) ? Flac.MARKER_.length : 0;
const block = metadata.slice(offset,
offset + Flac.BLOCK_HEADER_SIZE_ + Flac.STREAM_INFO_SIZE_);
block[0] |= Flac.LAST_BLOCK_FLAG_;
// Version and flags come first.
const result = new Uint8Array(4 + block.length);
result.set(block, 4);
return result;
}
/**
* Gets the number of samples of a FLAC frame from its header.
*
* @param {!Uint8Array} frame
* @param {shaka.codec.Flac.StreamInfo} streamInfo
* @return {number}
*/
static getBlockSize(frame, streamInfo) {
const Flac = shaka.codec.Flac;
// A stream that always uses the same size has no need to be read.
if (streamInfo.minBlockSize == streamInfo.maxBlockSize) {
return streamInfo.maxBlockSize;
}
// The sync code is 14 bits (0xFFF8, with the last bit being the blocking
// strategy); then the block size code (4 bits) and the sample rate code.
if (frame.length < Flac.FRAME_HEADER_MIN_SIZE_ || frame[0] != 0xff ||
(frame[1] & 0xfe) != 0xf8) {
return streamInfo.maxBlockSize;
}
const code = frame[2] >> 4;
if (code == Flac.BLOCK_SIZE_CODE_192_) {
return 192;
}
if (code >= Flac.BLOCK_SIZE_CODE_576_FIRST_ &&
code <= Flac.BLOCK_SIZE_CODE_576_LAST_) {
return 576 << (code - Flac.BLOCK_SIZE_CODE_576_FIRST_);
}
if (code >= Flac.BLOCK_SIZE_CODE_256_FIRST_) {
return 256 << (code - Flac.BLOCK_SIZE_CODE_256_FIRST_);
}
if (code == Flac.BLOCK_SIZE_CODE_8_BITS_ ||
code == Flac.BLOCK_SIZE_CODE_16_BITS_) {
// The size, minus one, is at the end of the header, after the number of
// the frame or of the sample, which is UTF-8 coded: the leading ones of
// its first byte give its length.
const offset = Flac.FRAME_HEADER_MIN_SIZE_ +
Math.max(1, Math.clz32(~(frame[4] << 24)));
if (code == Flac.BLOCK_SIZE_CODE_8_BITS_ && offset < frame.length) {
return frame[offset] + 1;
}
if (code == Flac.BLOCK_SIZE_CODE_16_BITS_ && offset + 1 < frame.length) {
return ((frame[offset] << 8) | frame[offset + 1]) + 1;
}
}
return streamInfo.maxBlockSize;
}
/**
* @param {!Uint8Array} metadata
* @return {boolean}
* @private
*/
static hasMarker_(metadata) {
const marker = shaka.codec.Flac.MARKER_;
return metadata.length >= marker.length &&
marker.every((byte, i) => metadata[i] == byte);
}
};
/**
* @typedef {{
* minBlockSize: number,
* maxBlockSize: number,
* sampleRate: number,
* channelCount: number,
* bitsPerSample: number,
* }}
*/
shaka.codec.Flac.StreamInfo;
/**
* The "fLaC" marker that starts a FLAC stream.
* @private @const {!Array<number>}
*/
shaka.codec.Flac.MARKER_ = [0x66, 0x4c, 0x61, 0x43];
/**
* The header of a metadata block: type and last-block flag (1 byte) and length
* (3 bytes).
* @private @const {number}
*/
shaka.codec.Flac.BLOCK_HEADER_SIZE_ = 4;
/** @private @const {number} */
shaka.codec.Flac.BLOCK_TYPE_MASK_ = 0x7f;
/**
* The bit of the first byte of a metadata block header that marks the last one.
* @private @const {number}
*/
shaka.codec.Flac.LAST_BLOCK_FLAG_ = 0x80;
/** @private @const {number} */
shaka.codec.Flac.BLOCK_TYPE_STREAM_INFO_ = 0;
/** @private @const {number} */
shaka.codec.Flac.STREAM_INFO_SIZE_ = 34;
/**
* The bytes of a frame header before the coded number: sync code, sizes and
* channel assignment.
* @private @const {number}
*/
shaka.codec.Flac.FRAME_HEADER_MIN_SIZE_ = 4;
/**
* The block size codes of the frame header. Code 1 is 192 samples; 2 to 5 are
* 576 doubled each time; 6 and 7 write the size after the header, with 8 and
* 16 bits; 8 to 15 are 256 doubled each time.
* @private @const {number}
*/
shaka.codec.Flac.BLOCK_SIZE_CODE_192_ = 1;
/** @private @const {number} */
shaka.codec.Flac.BLOCK_SIZE_CODE_576_FIRST_ = 2;
/** @private @const {number} */
shaka.codec.Flac.BLOCK_SIZE_CODE_576_LAST_ = 5;
/** @private @const {number} */
shaka.codec.Flac.BLOCK_SIZE_CODE_8_BITS_ = 6;
/** @private @const {number} */
shaka.codec.Flac.BLOCK_SIZE_CODE_16_BITS_ = 7;
/** @private @const {number} */
shaka.codec.Flac.BLOCK_SIZE_CODE_256_FIRST_ = 8;