Fix Matroska files where no audio packets are key packets (fixes #192)

This commit is contained in:
Vanilagy
2025-11-12 16:07:17 +01:00
parent 327696666b
commit 02b08e036b
3 changed files with 22 additions and 14 deletions
+2 -2
View File
@@ -14,8 +14,8 @@
source: new Mediabunny.BlobSource(file), source: new Mediabunny.BlobSource(file),
}); });
const videoTrack = await input.getPrimaryVideoTrack(); const audioTrack = await input.getPrimaryAudioTrack();
const sink = new Mediabunny.EncodedPacketSink(videoTrack); const sink = new Mediabunny.EncodedPacketSink(audioTrack);
for await (const packet of sink.packets()) { for await (const packet of sink.packets()) {
console.log(packet.timestamp, packet.duration, packet.type) console.log(packet.timestamp, packet.duration, packet.type)
+9 -1
View File
@@ -1374,9 +1374,17 @@ export class MatroskaDemuxer extends Demuxer {
const relativeTimestamp = readI16Be(slice); const relativeTimestamp = readI16Be(slice);
const flags = readU8(slice); const flags = readU8(slice);
const isKeyFrame = !!(flags & 0x80);
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
let isKeyFrame = !!(flags & 0x80);
if (trackData.track.info?.type === 'audio' && trackData.track.info.codec) {
// Some files don't mark their audio packets as key packets (I'm looking at you, Firefox). But, we
// can fix this in most cases: if we recognize the codec of the track, then we know every packet is
// necessarily a key packet, no matter what the container says.
// https://github.com/Vanilagy/mediabunny/issues/192
isKeyFrame = true;
}
const blockData = readBytes(slice, size - (slice.filePos - dataStartPos)); const blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
const hasDecodingInstructions = trackData.track.decodingInstructions.length > 0; const hasDecodingInstructions = trackData.track.decodingInstructions.length > 0;
+11 -11
View File
@@ -46,21 +46,21 @@ export abstract class Muxer {
private trackTimestampInfo = new WeakMap<OutputTrack, { private trackTimestampInfo = new WeakMap<OutputTrack, {
maxTimestamp: number; maxTimestamp: number;
maxTimestampBeforeLastKeyFrame: number; maxTimestampBeforeLastKeyPacket: number;
}>(); }>();
protected validateAndNormalizeTimestamp(track: OutputTrack, timestampInSeconds: number, isKeyFrame: boolean) { protected validateAndNormalizeTimestamp(track: OutputTrack, timestampInSeconds: number, isKeyPacket: boolean) {
timestampInSeconds += track.source._timestampOffset; timestampInSeconds += track.source._timestampOffset;
let timestampInfo = this.trackTimestampInfo.get(track); let timestampInfo = this.trackTimestampInfo.get(track);
if (!timestampInfo) { if (!timestampInfo) {
if (!isKeyFrame) { if (!isKeyPacket) {
throw new Error('First frame must be a key frame.'); throw new Error('First packet must be a key packet.');
} }
timestampInfo = { timestampInfo = {
maxTimestamp: timestampInSeconds, maxTimestamp: timestampInSeconds,
maxTimestampBeforeLastKeyFrame: timestampInSeconds, maxTimestampBeforeLastKeyPacket: timestampInSeconds,
}; };
this.trackTimestampInfo.set(track, timestampInfo); this.trackTimestampInfo.set(track, timestampInfo);
} }
@@ -69,15 +69,15 @@ export abstract class Muxer {
throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`); throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`);
} }
if (isKeyFrame) { if (isKeyPacket) {
timestampInfo.maxTimestampBeforeLastKeyFrame = timestampInfo.maxTimestamp; timestampInfo.maxTimestampBeforeLastKeyPacket = timestampInfo.maxTimestamp;
} }
if (timestampInSeconds < timestampInfo.maxTimestampBeforeLastKeyFrame) { if (timestampInSeconds < timestampInfo.maxTimestampBeforeLastKeyPacket) {
throw new Error( throw new Error(
`Timestamps cannot be smaller than the highest timestamp of the previous GOP (a GOP begins with a key` `Timestamps cannot be smaller than the largest timestamp of the previous GOP (a GOP begins with a key`
+ ` frame and ends right before the next key frame). Got ${timestampInSeconds}s, but highest timestamp` + ` packet and ends right before the next key packet). Got ${timestampInSeconds}s, but largest`
+ ` is ${timestampInfo.maxTimestampBeforeLastKeyFrame}s.`, + ` timestamp is ${timestampInfo.maxTimestampBeforeLastKeyPacket}s.`,
); );
} }