Migrate Matroska demuxer to new packet system

This commit is contained in:
Vanilagy
2025-12-21 19:46:47 +01:00
parent 6722592e27
commit 3285d5ea7d
4 changed files with 130 additions and 68 deletions
+96 -66
View File
@@ -42,10 +42,12 @@ import {
last,
MATRIX_COEFFICIENTS_MAP_INVERSE,
normalizeRotation,
ResultValue,
Rotation,
roundIfAlmostInteger,
TRANSFER_CHARACTERISTICS_MAP_INVERSE,
UNDETERMINED_LANGUAGE,
Yo,
} from '../misc';
import { EncodedPacket, EncodedPacketSideData, PLACEHOLDER_DATA } from '../packet';
import {
@@ -265,12 +267,6 @@ export class MatroskaDemuxer extends Demuxer {
this.reader = input._reader;
}
override async computeDuration() {
const tracks = await this.getTracks();
const trackDurations = await Promise.all(tracks.map(x => x.computeDuration()));
return Math.max(0, ...trackDurations);
}
async getTracks() {
await this.readMetadata();
return this.segments.flatMap(segment => segment.tracks.map(track => track.inputTrack!));
@@ -587,9 +583,9 @@ export class MatroskaDemuxer extends Demuxer {
this.currentSegment = null;
}
async readCluster(startPos: number, segment: Segment) {
async readCluster(res: ResultValue<Cluster>, startPos: number, segment: Segment): Promise<Yo> {
if (segment.lastReadCluster?.elementStartPos === startPos) {
return segment.lastReadCluster;
return res.set(segment.lastReadCluster);
}
let headerSlice = this.reader.requestSliceRange(startPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
@@ -719,7 +715,7 @@ export class MatroskaDemuxer extends Demuxer {
}
segment.lastReadCluster = cluster;
return cluster;
return res.set(cluster);
}
getTrackDataInCluster(cluster: Cluster, trackNumber: number) {
@@ -1838,11 +1834,6 @@ export class MatroskaDemuxer extends Demuxer {
}
abstract class MatroskaTrackBacking implements InputTrackBacking {
packetToClusterLocation = new WeakMap<EncodedPacket, {
cluster: Cluster;
blockIndex: number;
}>();
constructor(public internalTrack: InternalTrack) {}
getId() {
@@ -1857,11 +1848,6 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
return this.internalTrack.codecId;
}
async computeDuration() {
const lastPacket = await this.getPacket(Infinity, { metadataOnly: true });
return (lastPacket?.timestamp ?? 0) + (lastPacket?.duration ?? 0);
}
getName() {
return this.internalTrack.name;
}
@@ -1870,11 +1856,6 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
return this.internalTrack.languageCode;
}
async getFirstTimestamp() {
const firstPacket = await this.getFirstPacket({ metadataOnly: true });
return firstPacket?.timestamp ?? 0;
}
getTimeResolution() {
return this.internalTrack.segment.timestampFactor;
}
@@ -1883,8 +1864,9 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
return this.internalTrack.disposition;
}
async getFirstPacket(options: PacketRetrievalOptions) {
async getFirstPacket(res: ResultValue<EncodedPacket | null>, options: PacketRetrievalOptions): Promise<Yo> {
return this.performClusterLookup(
res,
null,
(cluster) => {
const trackData = cluster.trackData.get(this.internalTrack.id);
@@ -1913,10 +1895,15 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
return roundIfAlmostInteger(timestamp * this.internalTrack.segment.timestampFactor);
}
async getPacket(timestamp: number, options: PacketRetrievalOptions) {
async getPacket(
res: ResultValue<EncodedPacket | null>,
timestamp: number,
options: PacketRetrievalOptions,
): Promise<Yo> {
const timestampInTimescale = this.intoTimescale(timestamp);
return this.performClusterLookup(
res,
null,
(cluster) => {
const trackData = cluster.trackData.get(this.internalTrack.id);
@@ -1941,21 +1928,31 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
);
}
async getNextPacket(packet: EncodedPacket, options: PacketRetrievalOptions) {
const locationInCluster = this.packetToClusterLocation.get(packet);
if (locationInCluster === undefined) {
async getNextPacket(
res: ResultValue<EncodedPacket | null>,
packet: EncodedPacket,
options: PacketRetrievalOptions,
): Promise<Yo> {
const clusterStartPos = packet._internal;
if (clusterStartPos === undefined) {
throw new Error('Packet was not created from this track.');
}
assert(typeof clusterStartPos === 'number');
// sequenceNumber = clusterStartPos + blockIndex, so:
const blockIndex = packet.sequenceNumber - clusterStartPos;
return this.performClusterLookup(
locationInCluster.cluster,
res,
clusterStartPos,
(cluster) => {
if (cluster === locationInCluster.cluster) {
if (cluster.elementStartPos === clusterStartPos) {
const trackData = cluster.trackData.get(this.internalTrack.id)!;
if (locationInCluster.blockIndex + 1 < trackData.blocks.length) {
if (blockIndex + 1 < trackData.blocks.length) {
// We can simply take the next block in the cluster
return {
blockIndex: locationInCluster.blockIndex + 1,
blockIndex: blockIndex + 1,
correctBlockFound: true,
};
}
@@ -1980,10 +1977,15 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
);
}
async getKeyPacket(timestamp: number, options: PacketRetrievalOptions) {
async getKeyPacket(
res: ResultValue<EncodedPacket | null>,
timestamp: number,
options: PacketRetrievalOptions,
): Promise<Yo> {
const timestampInTimescale = this.intoTimescale(timestamp);
return this.performClusterLookup(
res,
null,
(cluster) => {
const trackData = cluster.trackData.get(this.internalTrack.id);
@@ -2007,19 +2009,29 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
);
}
async getNextKeyPacket(packet: EncodedPacket, options: PacketRetrievalOptions) {
const locationInCluster = this.packetToClusterLocation.get(packet);
if (locationInCluster === undefined) {
async getNextKeyPacket(
res: ResultValue<EncodedPacket | null>,
packet: EncodedPacket,
options: PacketRetrievalOptions,
): Promise<Yo> {
const clusterStartPos = packet._internal;
if (clusterStartPos === undefined) {
throw new Error('Packet was not created from this track.');
}
assert(typeof clusterStartPos === 'number');
// sequenceNumber = clusterStartPos + blockIndex, so:
const blockIndex = packet.sequenceNumber - clusterStartPos;
return this.performClusterLookup(
locationInCluster.cluster,
res,
clusterStartPos,
(cluster) => {
if (cluster === locationInCluster.cluster) {
if (cluster.elementStartPos === clusterStartPos) {
const trackData = cluster.trackData.get(this.internalTrack.id)!;
const nextKeyFrameIndex = trackData.blocks.findIndex(
(x, i) => x.isKeyFrame && i > locationInCluster.blockIndex,
(x, i) => x.isKeyFrame && i > blockIndex,
);
if (nextKeyFrameIndex !== -1) {
@@ -2053,9 +2065,14 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
);
}
private async fetchPacketInCluster(cluster: Cluster, blockIndex: number, options: PacketRetrievalOptions) {
private fetchPacketInCluster(
res: ResultValue<EncodedPacket | null>,
cluster: Cluster,
blockIndex: number,
options: PacketRetrievalOptions,
): Yo {
if (blockIndex === -1) {
return null;
return res.set(null);
}
const trackData = cluster.trackData.get(this.internalTrack.id)!;
@@ -2083,20 +2100,21 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
block.isKeyFrame ? 'key' : 'delta',
timestamp,
duration,
cluster.dataStartPos + blockIndex,
cluster.elementStartPos + blockIndex,
block.data.byteLength,
sideData,
);
this.packetToClusterLocation.set(packet, { cluster, blockIndex });
packet._internal = cluster.elementStartPos;
return packet;
return res.set(packet);
}
/** Looks for a packet in the clusters while trying to load as few clusters as possible to retrieve it. */
private async performClusterLookup(
// The cluster where we start looking
startCluster: Cluster | null,
res: ResultValue<EncodedPacket | null>,
// The position where we'll start the lookup
startOffset: number | null,
// This function returns the best-matching block in a given cluster
getMatchInCluster: (cluster: Cluster) => { blockIndex: number; correctBlockFound: boolean },
// The timestamp with which we can search the lookup table
@@ -2104,20 +2122,25 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
// The timestamp for which we know the correct block will not come after it
latestTimestamp: number,
options: PacketRetrievalOptions,
): Promise<EncodedPacket | null> {
): Promise<Yo> {
const { demuxer, segment } = this.internalTrack;
let currentPos = startOffset ?? 0;
let currentCluster: Cluster | null = null;
let bestCluster: Cluster | null = null;
let bestBlockIndex = -1;
if (startCluster) {
if (startOffset !== null && segment.lastReadCluster?.elementStartPos === startOffset) {
const startCluster = segment.lastReadCluster;
const { blockIndex, correctBlockFound } = getMatchInCluster(startCluster);
if (correctBlockFound) {
return this.fetchPacketInCluster(startCluster, blockIndex, options);
return this.fetchPacketInCluster(res, startCluster, blockIndex, options);
}
currentPos = startCluster.elementEndPos; // Start reading from the next cluster
currentCluster = startCluster;
if (blockIndex !== -1) {
bestCluster = startCluster;
bestBlockIndex = blockIndex;
@@ -2150,18 +2173,14 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
positionCacheEntry?.elementStartPos ?? 0,
) || null;
let currentPos: number;
if (lookupEntryPosition !== null && lookupEntryPosition > currentPos) {
// Use the lookup entry
currentPos = lookupEntryPosition;
currentCluster = null;
}
if (!startCluster) {
if (startOffset === null) {
currentPos = lookupEntryPosition ?? segment.clusterSeekStartPos;
} else {
if (lookupEntryPosition === null || startCluster.elementStartPos >= lookupEntryPosition) {
currentPos = startCluster.elementEndPos;
currentCluster = startCluster;
} else {
// Use the lookup entry
currentPos = lookupEntryPosition;
}
}
while (segment.elementEndPos === null || currentPos <= segment.elementEndPos - MIN_HEADER_SIZE) {
@@ -2207,13 +2226,18 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
const dataStartPos = slice.filePos;
if (id === EBMLId.Cluster) {
currentCluster = await demuxer.readCluster(elementStartPos, segment);
const result = new ResultValue<Cluster>();
const promise = demuxer.readCluster(result, elementStartPos, segment);
if (result.pending) await promise;
currentCluster = result.value;
// readCluster computes the proper size even if it's undefined in the header, so let's use that instead
size = currentCluster.elementEndPos - dataStartPos;
const { blockIndex, correctBlockFound } = getMatchInCluster(currentCluster);
if (correctBlockFound) {
return this.fetchPacketInCluster(currentCluster, blockIndex, options);
return this.fetchPacketInCluster(res, currentCluster, blockIndex, options);
}
if (blockIndex !== -1) {
@@ -2267,15 +2291,17 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
assert(!previousCuePoint || previousCuePoint.time < cuePoint.time);
const newSearchTimestamp = previousCuePoint?.time ?? -Infinity;
return this.performClusterLookup(null, getMatchInCluster, newSearchTimestamp, latestTimestamp, options);
return this.performClusterLookup(
res, null, getMatchInCluster, newSearchTimestamp, latestTimestamp, options,
);
}
if (bestCluster) {
// If we finished looping but didn't find a perfect match, still return the best match we found
return this.fetchPacketInCluster(bestCluster, bestBlockIndex, options);
return this.fetchPacketInCluster(res, bestCluster, bestBlockIndex, options);
}
return null;
return res.set(null);
}
}
@@ -2333,7 +2359,11 @@ class MatroskaVideoTrackBacking extends MatroskaTrackBacking implements InputVid
|| (this.internalTrack.info.codec === 'hevc' && !this.internalTrack.info.codecDescription);
if (needsPacketForAdditionalInfo) {
firstPacket = await this.getFirstPacket({});
const result = new ResultValue<EncodedPacket | null>();
const promise = this.getFirstPacket(result, {});
if (result.pending) await promise;
firstPacket = result.value;
}
return {
+1 -1
View File
@@ -175,7 +175,7 @@ export const readBytes = (slice: FileSlice, length: number) => {
export const readU8 = (slice: FileSlice) => {
checkIsInRange(slice, 1);
return slice.view.getUint8(slice.bufferPos++);
return slice.bytes[slice.bufferPos++]!;
};
export const readU16 = (slice: FileSlice, littleEndian: boolean) => {