Skip to content

Instantly share code, notes, and snippets.

@seflless
Created September 19, 2023 16:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seflless/d59549f1e2aff3c2b89fab34bad88f32 to your computer and use it in GitHub Desktop.
Save seflless/d59549f1e2aff3c2b89fab34bad88f32 to your computer and use it in GitHub Desktop.
Given the description field in a VideoDecoder configuration, parse out data so that it can be inspected or logged.
// See VideoDecoder.configure for documentation on all configuration fields:
// https://developer.mozilla.org/en-US/docs/Web/API/VideoDecoder/configure
type AvcCData = {
configurationVersion: number;
profileIndication: number;
profileCompatibility: number;
avcLevelIndication: number;
lengthSizeMinusOne: number;
sps: Uint8Array[];
pps: Uint8Array[];
};
function parseAvcC(data: Uint8Array): AvcCData {
let offset = 0;
const configurationVersion = data[offset]; // should be 1
offset++;
const profileIndication = data[offset];
offset++;
const profileCompatibility = data[offset];
offset++;
const avcLevelIndication = data[offset];
offset++;
// The top 6 bits are '111111' as per spec and the last 2 bits indicate the NALU length
// eslint-disable-next-line no-bitwise
const lengthSizeMinusOne = data[offset] & 3;
offset++;
// eslint-disable-next-line no-bitwise
const numOfSPS = data[offset] & 31; // 5 bits
offset++;
const sps: Uint8Array[] = [];
for (let i = 0; i < numOfSPS; i++) {
// eslint-disable-next-line no-bitwise
const spsLength = (data[offset] << 8) | data[offset + 1];
offset += 2;
sps.push(data.subarray(offset, offset + spsLength));
offset += spsLength;
}
const numOfPPS = data[offset];
offset++;
const pps: Uint8Array[] = [];
for (let i = 0; i < numOfPPS; i++) {
// eslint-disable-next-line no-bitwise
const ppsLength = (data[offset] << 8) | data[offset + 1];
offset += 2;
pps.push(data.subarray(offset, offset + ppsLength));
offset += ppsLength;
}
return {
configurationVersion,
profileIndication,
profileCompatibility,
avcLevelIndication,
lengthSizeMinusOne,
sps,
pps,
};
}
type SpsInfo = {
profileIdc: number;
levelIdc: number;
seqParameterSetId: number;
chromaFormatIdc?: number;
bitDepthLumaMinus8?: number;
bitDepthChromaMinus8?: number;
log2MaxFrameNumMinus4: number;
picOrderCntType: number;
log2MaxPicOrderCntLsbMinus4?: number;
maxNumRefFrames: number;
picWidthInMbsMinus1: number;
picHeightInMapUnitsMinus1: number;
frameWidth: number;
frameHeight: number;
};
function readUE(v: Uint8Array, pos: { value: number }): number {
// Read "Exponential Golomb" value from the bit stream
let zeros = 0;
while (pos.value < v.length * 8 && !(v[pos.value >> 3] & (0x80 >> (pos.value & 7)))) {
zeros++;
pos.value++;
}
pos.value++;
let value = 0;
for (let i = 0; i < zeros; i++) {
value <<= 1;
if (v[pos.value >> 3] & (0x80 >> (pos.value & 7))) {
value |= 1;
}
pos.value++;
}
return value + (1 << zeros) - 1;
}
function parseSPS(sps: Uint8Array): SpsInfo {
const pos = { value: 0 };
const forbiddenZeroBit = (sps[0] & 0x80) >> 7;
const nalRefIdc = (sps[0] & 0x60) >> 5;
const nalUnitType = sps[0] & 0x1f;
if (nalUnitType !== 7) {
throw new Error('Not an SPS NALU!');
}
const profileIdc = sps[1];
const constraintSetFlags = sps[2];
const levelIdc = sps[3];
pos.value = 32; // skip NAL header and profile/level info
const seqParameterSetId = readUE(sps, pos);
let chromaFormatIdc = 1; // Default to 4:2:0 if not present
if (
profileIdc === 100 ||
profileIdc === 110 ||
profileIdc === 122 ||
profileIdc === 244 ||
profileIdc === 44 ||
profileIdc === 83 ||
profileIdc === 86 ||
profileIdc === 118 ||
profileIdc === 128
) {
chromaFormatIdc = readUE(sps, pos);
if (chromaFormatIdc === 3) readUE(sps, pos); // residual_color_transform_flag
const bitDepthLumaMinus8 = readUE(sps, pos);
const bitDepthChromaMinus8 = readUE(sps, pos);
const qpprimeYZeroTransformBypassFlag = readUE(sps, pos);
const seqScalingMatrixPresentFlag = readUE(sps, pos);
}
const log2MaxFrameNumMinus4 = readUE(sps, pos);
const picOrderCntType = readUE(sps, pos);
let log2MaxPicOrderCntLsbMinus4;
if (picOrderCntType === 0) {
log2MaxPicOrderCntLsbMinus4 = readUE(sps, pos);
} else if (picOrderCntType === 1) {
// ... there are more fields to parse here if needed ...
}
const maxNumRefFrames = readUE(sps, pos);
const gapsInFrameNumValueAllowedFlag = readUE(sps, pos);
const picWidthInMbsMinus1 = readUE(sps, pos);
const picHeightInMapUnitsMinus1 = readUE(sps, pos);
const frameWidth = (picWidthInMbsMinus1 + 1) * 16;
const frameHeight = (picHeightInMapUnitsMinus1 + 1) * 16;
return {
profileIdc,
levelIdc,
seqParameterSetId,
chromaFormatIdc,
bitDepthLumaMinus8: chromaFormatIdc ? chromaFormatIdc : undefined,
bitDepthChromaMinus8: chromaFormatIdc ? chromaFormatIdc : undefined,
log2MaxFrameNumMinus4,
picOrderCntType,
log2MaxPicOrderCntLsbMinus4,
maxNumRefFrames,
picWidthInMbsMinus1,
picHeightInMapUnitsMinus1,
frameWidth,
frameHeight,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment