mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-29 03:43:50 +02:00
Migrate Matroska demuxer to new packet system
This commit is contained in:
@@ -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
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user