From d5795564713181373c221c03dea3afde1a5fdd85 Mon Sep 17 00:00:00 2001 From: David Payr <1696106+Vanilagy@users.noreply.github.com> Date: Sun, 29 Dec 2024 23:04:06 +0100 Subject: [PATCH] Refine av1C box data --- dev/demux.html | 1 - dev/player.html | 2 ++ src/codec.ts | 51 ++-------------------------------- src/isobmff/isobmff-boxes.ts | 53 ++++++++++++++++++++++++++++-------- 4 files changed, 45 insertions(+), 62 deletions(-) diff --git a/dev/demux.html b/dev/demux.html index 48801c4..cd9415d 100644 --- a/dev/demux.html +++ b/dev/demux.html @@ -29,7 +29,6 @@ output.start(); for await (const frame of drain.frames(0, 10)) { - console.log(frame.timestamp, frame.duration) await mediaSource.digest(frame); frame.close(); } diff --git a/dev/player.html b/dev/player.html index 50dd21b..44e6526 100644 --- a/dev/player.html +++ b/dev/player.html @@ -38,6 +38,8 @@ const input = new Metamuxer.Input({ source }); +console.log(await input.getMimeType()) + const videoTrack = await input.getPrimaryVideoTrack(); const audioTrack = await input.getPrimaryAudioTrack(); diff --git a/src/codec.ts b/src/codec.ts index d8e4e97..c3ad718 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -168,7 +168,7 @@ export const buildVideoCodecString = (codec: VideoCodec, width: number, height: return `vp09.${profile}.${levelInfo.level.toString().padStart(2, '0')}.${bitDepth}`; } else if (codec === 'av1') { - const profile = 0; // Main Profile + const profile = 0; // Main Profile, single digit const pictureSize = width * height; const levelInfo = AV1_LEVEL_TABLE.find( @@ -273,59 +273,12 @@ export const extractVideoCodecString = (trackInfo: { throw new Error('Missing AV1 codec info - unable to construct codec string.'); } - const profile = av1CodecInfo.seqProfile; + const profile = av1CodecInfo.seqProfile; // Single digit const level = av1CodecInfo.seqLevelIdx0.toString().padStart(2, '0'); const tier = av1CodecInfo.seqTier0 ? 'H' : 'M'; const bitDepth = av1CodecInfo.bitDepth.toString().padStart(2, '0'); return `av01.${profile}.${level}${tier}.${bitDepth}`; - - /* - const chunk = await videoTrack._backing.getFirstChunk({}); - if (!chunk) { - throw new Error('ahh'); - } - - const buffer = new ArrayBuffer(chunk.byteLength); - chunk.copyTo(buffer); - - console.log(new Uint8Array(buffer)); - - const view = new DataView(buffer); - let pos = 0; - const byte = view.getUint8(pos++); - - const obuType = (byte & 0b1111000) >> 3; - const obuExtensionFlag = (byte & 0b100) >> 2; - const obuHasSizeField = (byte & 0b10) >> 1; - - if (obuExtensionFlag) { - pos++; - } - - function leb128() { - let value = 0; - - for (let i = 0; i < 8; i++) { - const leb128_byte = view.getUint8(pos); - value |= (leb128_byte & 0x7f) << (i * 7); // Extract the lower 7 bits and shift them accordingly - pos++; - - // Check if the most significant bit (MSB) is 0, signaling the end of the LEB128 sequence - if ((leb128_byte & 0x80) === 0) { - break; - } - } - - return value; - } - - if (obuHasSizeField) { - console.log(leb128()); - } - - // Some extra stuff, todo - */ } throw new TypeError(`Unhandled codec '${codec}'.`); diff --git a/src/isobmff/isobmff-boxes.ts b/src/isobmff/isobmff-boxes.ts index a18b513..9deb4f0 100644 --- a/src/isobmff/isobmff-boxes.ts +++ b/src/isobmff/isobmff-boxes.ts @@ -596,7 +596,7 @@ export const vpcC = (trackData: IsobmffVideoTrackData) => { const decoderConfig = trackData.info.decoderConfig; assert(decoderConfig.colorSpace); // This is guaranteed by an earlier validation step - const parts = decoderConfig.codec.split('.'); + const parts = decoderConfig.codec.split('.'); // We can derive the required values from the codec string const profile = Number(parts[1]); const level = Number(parts[2]); @@ -604,10 +604,15 @@ export const vpcC = (trackData: IsobmffVideoTrackData) => { const chromaSubsampling = 0; const thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + Number(decoderConfig.colorSpace.fullRange); - // Set all to undetermined. We could determine them using the codec color space info, but there's no need. - const colourPrimaries = 2; - const transferCharacteristics = 2; - const matrixCoefficients = 2; + 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; return fullBox('vpcC', 1, 0, [ u8(profile), // Profile @@ -621,21 +626,45 @@ export const vpcC = (trackData: IsobmffVideoTrackData) => { }; /** AV1 Configuration Box: Provides additional information to the decoder. */ -export const av1C = () => { +export const av1C = (trackData: IsobmffVideoTrackData) => { // Reference: https://aomediacodec.github.io/av1-isobmff/ + const decoderConfig = trackData.info.decoderConfig; + const parts = decoderConfig.codec.split('.'); // We can derive the required values from the codec string + const marker = 1; const version = 1; const firstByte = (marker << 7) + version; - // The box contents are not correct like this, but its length is. Getting the values for the last three bytes - // requires peeking into the bitstream of the coded chunks. Might come back later. - // TODO + const profile = Number(parts[1]); + const levelAndTier = parts[2]!; + const level = Number(levelAndTier.slice(0, -1)); + const tier = levelAndTier.slice(-1) === 'H' ? 1 : 0; + const bitDepth = Number(parts[3]); + const secondByte = (profile << 5) + level; + + // The following values are educated, very likely guesses (typical 4:2:0 chroma subsampling) + const twelveBit = 0; + const monochrome = 0; + const chromaSubsamplingX = 1; + const chromaSubsamplingY = 1; + const chromaSamplePosition = 1; // CSP_VERTICAL + const thirdByte = (tier << 7) + + ((bitDepth === 8 ? 0 : 1) << 6) + + (twelveBit << 5) + + (monochrome << 4) + + (chromaSubsamplingX << 3) + + (chromaSubsamplingY << 2) + + chromaSamplePosition; + + const initialPresentationDelayPresent = 0; // Should be fine + const fourthByte = initialPresentationDelayPresent; + return box('av1C', [ firstByte, - 0, - 0, - 0, + secondByte, + thirdByte, + fourthByte, ]); };