Created
September 19, 2023 16:35
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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