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,
]);
};