Make VP9 and AV1 codec strings much more detailed

This commit is contained in:
David Payr
2024-12-30 00:08:23 +01:00
parent d579556471
commit 94b43bf8da
4 changed files with 114 additions and 33 deletions
+1 -1
View File
@@ -21,7 +21,7 @@
const output = new Metamuxer.Output({ target, format });
const mediaSource = new Metamuxer.VideoFrameSource({
codec: 'av1',
codec: 'vp9',
bitrate: 1e6
});
output.addVideoTrack(mediaSource);
+66 -11
View File
@@ -120,6 +120,9 @@ const AV1_LEVEL_TABLE = [
{ maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 19 }, // Level 6.3 (High Tier)
];
const VP9_DEFAULT_SUFFIX = '.01.01.01.01.00';
const AV1_DEFAULT_SUFFIX = '.0.110.01.01.01.0';
export const buildVideoCodecString = (codec: VideoCodec, width: number, height: number, bitrate: number) => {
if (codec === 'avc') {
const profileIndication = 0x64; // High Profile
@@ -166,7 +169,7 @@ export const buildVideoCodecString = (codec: VideoCodec, width: number, height:
const bitDepth = '08'; // 8-bit
return `vp09.${profile}.${levelInfo.level.toString().padStart(2, '0')}.${bitDepth}`;
return `vp09.${profile}.${levelInfo.level.toString().padStart(2, '0')}.${bitDepth}${VP9_DEFAULT_SUFFIX}`;
} else if (codec === 'av1') {
const profile = 0; // Main Profile, single digit
@@ -174,10 +177,11 @@ export const buildVideoCodecString = (codec: VideoCodec, width: number, height:
const levelInfo = AV1_LEVEL_TABLE.find(
level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate,
) ?? last(AV1_LEVEL_TABLE)!;
const level = levelInfo.level.toString().padStart(2, '0');
const bitDepth = '08'; // 8-bit
return `av01.${profile}.${levelInfo.level.toString().padStart(2, '0')}${levelInfo.tier}.${bitDepth}`;
return `av01.${profile}.${level}${levelInfo.tier}.${bitDepth}${AV1_DEFAULT_SUFFIX}`;
}
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
@@ -188,22 +192,32 @@ export type Vp9CodecInfo = {
profile: number;
level: number;
bitDepth: number;
chromaSubsampling: number;
videoFullRangeFlag: number;
colourPrimaries: number;
transferCharacteristics: number;
matrixCoefficients: number;
};
export type Av1CodecInfo = {
seqProfile: number;
seqLevelIdx0: number;
seqTier0: number;
profile: number;
level: number;
tier: number;
bitDepth: number;
monochrome: number;
chromaSubsamplingX: number;
chromaSubsamplingY: number;
chromaSamplePosition: number;
};
export const extractVideoCodecString = (trackInfo: {
codec: VideoCodec | null;
codecDescription: Uint8Array | null;
colorSpace: VideoColorSpaceInit | null;
vp9CodecInfo: Vp9CodecInfo | null;
av1CodecInfo: Av1CodecInfo | null;
}) => {
const { codec, codecDescription: description, vp9CodecInfo, av1CodecInfo } = trackInfo;
const { codec, codecDescription: description, colorSpace, vp9CodecInfo, av1CodecInfo } = trackInfo;
if (codec === 'avc') {
if (!description || description.byteLength < 4) {
@@ -266,19 +280,60 @@ export const extractVideoCodecString = (trackInfo: {
const profile = vp9CodecInfo.profile.toString().padStart(2, '0');
const level = vp9CodecInfo.level.toString().padStart(2, '0');
const bitDepth = vp9CodecInfo.bitDepth.toString().padStart(2, '0');
const chromaSubsampling = vp9CodecInfo.chromaSubsampling.toString().padStart(2, '0');
const colourPrimaries = vp9CodecInfo.colourPrimaries.toString().padStart(2, '0');
const transferCharacteristics = vp9CodecInfo.transferCharacteristics.toString().padStart(2, '0');
const matrixCoefficients = vp9CodecInfo.matrixCoefficients.toString().padStart(2, '0');
const videoFullRangeFlag = vp9CodecInfo.videoFullRangeFlag.toString().padStart(2, '0');
return `vp09.${profile}.${level}.${bitDepth}`;
let string = `vp09.${profile}.${level}.${bitDepth}.${chromaSubsampling}`;
string += `.${colourPrimaries}.${transferCharacteristics}.${matrixCoefficients}.${videoFullRangeFlag}`;
const defaultSuffix = '.01.01.01.01.00';
if (string.endsWith(defaultSuffix)) {
string = string.slice(0, -defaultSuffix.length);
}
return string;
} else if (codec === 'av1') {
if (!av1CodecInfo) {
throw new Error('Missing AV1 codec info - unable to construct codec string.');
}
const profile = av1CodecInfo.seqProfile; // Single digit
const level = av1CodecInfo.seqLevelIdx0.toString().padStart(2, '0');
const tier = av1CodecInfo.seqTier0 ? 'H' : 'M';
// https://aomediacodec.github.io/av1-isobmff/#codecsparam
const profile = av1CodecInfo.profile; // Single digit
const level = av1CodecInfo.level.toString().padStart(2, '0');
const tier = av1CodecInfo.tier ? 'H' : 'M';
const bitDepth = av1CodecInfo.bitDepth.toString().padStart(2, '0');
const monochrome = av1CodecInfo.monochrome ? '1' : '0';
const chromaSubsampling = 100 * av1CodecInfo.chromaSubsamplingX
+ 10 * av1CodecInfo.chromaSubsamplingY
+ 1 * (
av1CodecInfo.chromaSubsamplingX && av1CodecInfo.chromaSubsamplingY
? av1CodecInfo.chromaSamplePosition
: 0
);
return `av01.${profile}.${level}${tier}.${bitDepth}`;
// The defaults are 1 (ITU-R BT.709)
const colorPrimaries = colorSpace?.primaries ? COLOR_PRIMARIES_MAP[colorSpace.primaries] : 1;
const transferCharacteristics = colorSpace?.transfer ? TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] : 1;
const matrixCoefficients = colorSpace?.matrix ? MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] : 1;
const videoFullRangeFlag = colorSpace?.fullRange ? 1 : 0;
let string = `av01.${profile}.${level}${tier}.${bitDepth}`;
string += `.${monochrome}.${chromaSubsampling.toString().padStart(3, '0')}`;
string += `.${colorPrimaries.toString().padStart(2, '0')}`;
string += `.${transferCharacteristics.toString().padStart(2, '0')}`;
string += `.${matrixCoefficients.toString().padStart(2, '0')}`;
string += `.${videoFullRangeFlag}`;
const defaultSuffix = '.0.110.01.01.01.0';
if (string.endsWith(defaultSuffix)) {
string = string.slice(0, -defaultSuffix.length);
}
return string;
}
throw new TypeError(`Unhandled codec '${codec}'.`);
+18 -11
View File
@@ -601,18 +601,25 @@ export const vpcC = (trackData: IsobmffVideoTrackData) => {
const level = Number(parts[2]);
const bitDepth = Number(parts[3]);
const chromaSubsampling = 0;
const thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + Number(decoderConfig.colorSpace.fullRange);
const chromaSubsampling = parts[4] ? Number(parts[4]) : 1; // 4:2:0 colocated with luma (0,0)
const videoFullRangeFlag = parts[8] ? Number(parts[8]) : Number(decoderConfig.colorSpace.fullRange);
const thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + videoFullRangeFlag;
const colourPrimaries = decoderConfig.colorSpace.primaries
? COLOR_PRIMARIES_MAP[decoderConfig.colorSpace.primaries]
: 2; // Default to undetermined
const transferCharacteristics = decoderConfig.colorSpace.transfer
? TRANSFER_CHARACTERISTICS_MAP[decoderConfig.colorSpace.transfer]
: 2;
const matrixCoefficients = decoderConfig.colorSpace.matrix
? MATRIX_COEFFICIENTS_MAP[decoderConfig.colorSpace.matrix]
: 2;
const colourPrimaries = parts[5]
? Number(parts[5])
: decoderConfig.colorSpace.primaries
? COLOR_PRIMARIES_MAP[decoderConfig.colorSpace.primaries]
: 2; // Default to undetermined
const transferCharacteristics = parts[6]
? Number(parts[6])
: decoderConfig.colorSpace.transfer
? TRANSFER_CHARACTERISTICS_MAP[decoderConfig.colorSpace.transfer]
: 2;
const matrixCoefficients = parts[7]
? Number(parts[7])
: decoderConfig.colorSpace.matrix
? MATRIX_COEFFICIENTS_MAP[decoderConfig.colorSpace.matrix]
: 2;
return fullBox('vpcC', 1, 0, [
u8(profile), // Profile
+29 -10
View File
@@ -728,11 +728,22 @@ export class IsobmffDemuxer extends Demuxer {
const profile = this.isobmffReader.readU8();
const level = this.isobmffReader.readU8();
const thirdByte = this.isobmffReader.readU8();
const bitDepth = thirdByte >> 4;
const chromaSubsampling = (thirdByte >> 1) & 0b111;
const videoFullRangeFlag = thirdByte & 1;
const colourPrimaries = this.isobmffReader.readU8();
const transferCharacteristics = this.isobmffReader.readU8();
const matrixCoefficients = this.isobmffReader.readU8();
track.info.vp9CodecInfo = {
profile: profile,
level: level,
bitDepth: thirdByte >> 4,
profile,
level,
bitDepth,
chromaSubsampling,
videoFullRangeFlag,
colourPrimaries,
transferCharacteristics,
matrixCoefficients,
};
}; break;
@@ -743,22 +754,30 @@ export class IsobmffDemuxer extends Demuxer {
this.isobmffReader.pos += 1; // Marker + version
const secondByte = this.isobmffReader.readU8();
const seqProfile = secondByte >> 5;
const seqLevelIdx0 = secondByte & 0b11111;
const profile = secondByte >> 5;
const level = secondByte & 0b11111;
const thirdByte = this.isobmffReader.readU8();
const seqTier0 = thirdByte >> 7;
const tier = thirdByte >> 7;
const highBitDepth = (thirdByte >> 6) & 1;
const twelveBit = (thirdByte >> 5) & 1;
const monochrome = (thirdByte >> 4) & 1;
const chromaSubsamplingX = (thirdByte >> 3) & 1;
const chromaSubsamplingY = (thirdByte >> 2) & 1;
const chromaSamplePosition = thirdByte & 0b11;
// Logic from https://aomediacodec.github.io/av1-spec/av1-spec.pdf
const bitDepth = seqProfile == 2 && highBitDepth ? (twelveBit ? 12 : 10) : (highBitDepth ? 10 : 8);
const bitDepth = profile == 2 && highBitDepth ? (twelveBit ? 12 : 10) : (highBitDepth ? 10 : 8);
track.info.av1CodecInfo = {
seqProfile,
seqLevelIdx0,
seqTier0,
profile,
level,
tier,
bitDepth,
monochrome,
chromaSubsamplingX,
chromaSubsamplingY,
chromaSamplePosition,
};
}; break;