mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 21:33:52 +02:00
Rewrite mediaSamplesAtTimestamps to fix B-frame bug, fix incorrect timestamps when cloning samples
This commit is contained in:
+43
-18
@@ -16,8 +16,21 @@
|
|||||||
source
|
source
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
let timestamps = [];
|
||||||
const timestamps = [
|
const videoTrack = await input.getPrimaryVideoTrack();
|
||||||
|
|
||||||
|
const packetSink = new Metamuxer.EncodedPacketSink(videoTrack);
|
||||||
|
for await (const packet of packetSink.packets()) {
|
||||||
|
if (packet.timestamp >= 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamps.push(packet.timestamp);
|
||||||
|
}
|
||||||
|
timestamps.push(11);
|
||||||
|
|
||||||
|
timestamps.sort((a, b) => a-b)
|
||||||
|
timestamps = [
|
||||||
4.16,
|
4.16,
|
||||||
4.266666666666667,
|
4.266666666666667,
|
||||||
4.373333333333333,
|
4.373333333333333,
|
||||||
@@ -49,20 +62,6 @@
|
|||||||
7.1466666666666665,
|
7.1466666666666665,
|
||||||
7.253333333333333
|
7.253333333333333
|
||||||
];
|
];
|
||||||
*/
|
|
||||||
const timestamps = [];
|
|
||||||
const videoTrack = await input.getPrimaryVideoTrack();
|
|
||||||
|
|
||||||
const packetSink = new Metamuxer.EncodedPacketSink(videoTrack);
|
|
||||||
for await (const packet of packetSink.packets()) {
|
|
||||||
if (packet.timestamp >= 1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamps.push(packet.timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamps.sort((a, b) => a-b)
|
|
||||||
|
|
||||||
console.log(timestamps);
|
console.log(timestamps);
|
||||||
//return;
|
//return;
|
||||||
@@ -73,12 +72,38 @@
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const sink = new Metamuxer.VideoSampleSink(videoTrack);
|
||||||
const sink = new Metamuxer.CanvasSink(videoTrack, { width: 320 });
|
for await (const sample of sink.samplesAtTimestamps(timestamps)) {
|
||||||
|
console.log("wee", sample.timestamp);
|
||||||
|
sample.close();
|
||||||
|
}
|
||||||
|
console.log("don")
|
||||||
|
|
||||||
|
/*
|
||||||
|
const sink = new Metamuxer.CanvasSink(videoTrack, { width: 320 });
|
||||||
for await (const wrappedCanvas of sink.canvasesAtTimestamps(timestamps)) {
|
for await (const wrappedCanvas of sink.canvasesAtTimestamps(timestamps)) {
|
||||||
console.log(wrappedCanvas);
|
console.log(wrappedCanvas);
|
||||||
}
|
}
|
||||||
|
console.log("don")
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
const count = Math.floor(50 * Math.random());
|
||||||
|
const timestamps = Array.from({ length: count }, () => Math.random() * 20).sort((a, b) => a-b);
|
||||||
|
|
||||||
|
for await (const wrappedCanvas of sink.canvasesAtTimestamps(timestamps)) {
|
||||||
|
//console.log(wrappedCanvas);
|
||||||
|
}
|
||||||
|
console.log("don")
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("DONE!")
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
+71
-51
@@ -504,83 +504,103 @@ export abstract class BaseMediaSampleSink<
|
|||||||
});
|
});
|
||||||
|
|
||||||
const packetSink = this._createPacketSink();
|
const packetSink = this._createPacketSink();
|
||||||
let lastKeyPacket: EncodedPacket | null = null;
|
|
||||||
let lastPacket: EncodedPacket | null = null;
|
let lastPacket: EncodedPacket | null = null;
|
||||||
|
let lastKeyPacket: EncodedPacket | null = null;
|
||||||
|
|
||||||
|
// The end sequence number (inclusive) in the next batch of packets that will be decoded. The batch starts
|
||||||
|
// at the last key frame and goes until this sequence number.
|
||||||
|
let maxSequenceNumber = -1;
|
||||||
|
|
||||||
|
const decodePackets = async () => {
|
||||||
|
assert(lastKeyPacket);
|
||||||
|
|
||||||
|
// Start at the current key packet
|
||||||
|
let currentPacket = lastKeyPacket;
|
||||||
|
decoder.decode(currentPacket);
|
||||||
|
|
||||||
|
while (currentPacket.sequenceNumber < maxSequenceNumber) {
|
||||||
|
const maxQueueSize = computeMaxQueueSize(sampleQueue.length);
|
||||||
|
while (sampleQueue.length + decoder.getDecodeQueueSize() > maxQueueSize && !terminated) {
|
||||||
|
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
||||||
|
await queueDequeue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terminated) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPacket = await packetSink.getNextPacket(currentPacket);
|
||||||
|
assert(nextPacket);
|
||||||
|
|
||||||
|
currentPacket = nextPacket;
|
||||||
|
decoder.decode(nextPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
maxSequenceNumber = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const flushDecoder = async () => {
|
||||||
|
await decoder.flush();
|
||||||
|
|
||||||
|
// We don't expect this list to have any elements in it anymore, but in case it does, let's emit
|
||||||
|
// nulls for every remaining element, then clear it.
|
||||||
|
for (let i = 0; i < timestampsOfInterest.length; i++) {
|
||||||
|
pushToQueue(null);
|
||||||
|
}
|
||||||
|
timestampsOfInterest.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
for await (const timestamp of timestampIterator) {
|
for await (const timestamp of timestampIterator) {
|
||||||
validateTimestamp(timestamp);
|
validateTimestamp(timestamp);
|
||||||
|
|
||||||
const maxQueueSize = computeMaxQueueSize(sampleQueue.length);
|
|
||||||
while (sampleQueue.length + decoder.getDecodeQueueSize() > maxQueueSize && !terminated) {
|
|
||||||
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
|
||||||
await queueDequeue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (terminated) {
|
if (terminated) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetPacket = await packetSink.getPacket(timestamp);
|
const targetPacket = await packetSink.getPacket(timestamp);
|
||||||
if (!targetPacket) {
|
const keyPacket = targetPacket && await packetSink.getKeyPacket(timestamp);
|
||||||
pushToQueue(null);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log('target', targetPacket);
|
|
||||||
|
|
||||||
const keyPacket = await packetSink.getKeyPacket(timestamp);
|
|
||||||
if (!keyPacket) {
|
if (!keyPacket) {
|
||||||
|
if (maxSequenceNumber !== -1) {
|
||||||
|
await decodePackets();
|
||||||
|
await flushDecoder();
|
||||||
|
}
|
||||||
|
|
||||||
pushToQueue(null);
|
pushToQueue(null);
|
||||||
|
lastPacket = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastPacket && targetPacket.sequenceNumber < lastPacket.sequenceNumber) {
|
// Check if the key packet has changed or if we're going back in time
|
||||||
// console.log('FLUSH?', lastPacket, targetPacket.sequenceNumber, lastPacket.sequenceNumber);
|
|
||||||
// We're going back in time with this one, let's flush and reset to a clean state
|
|
||||||
await decoder.flush();
|
|
||||||
timestampsOfInterest.length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
lastKeyPacket
|
lastPacket
|
||||||
&& keyPacket.sequenceNumber === lastKeyPacket.sequenceNumber // => they're the same packet
|
&& (
|
||||||
// && targetPacket.timestamp >= lastPacket!.timestamp
|
keyPacket.sequenceNumber !== lastKeyPacket!.sequenceNumber
|
||||||
&& targetPacket.sequenceNumber >= lastPacket!.sequenceNumber
|
|| targetPacket.timestamp < lastPacket.timestamp
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
assert(lastPacket);
|
await decodePackets();
|
||||||
|
|
||||||
if (
|
if (targetPacket.timestamp < lastPacket.timestamp) {
|
||||||
targetPacket.sequenceNumber === lastPacket.sequenceNumber
|
// We're going back in time with this one, let's flush and reset to a clean state
|
||||||
&& timestampsOfInterest.length === 0
|
await flushDecoder();
|
||||||
) {
|
|
||||||
// Special case: We have a repeat packet, but the sample for that packet has already been
|
|
||||||
// decoded. Therefore, we need to push the sample here instead of in the decoder callback.
|
|
||||||
if (lastUsedSample) {
|
|
||||||
pushToQueue(lastUsedSample.clone() as MediaSample);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
timestampsOfInterest.push(targetPacket.timestamp);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// console.log('==== RESET');
|
|
||||||
// The key packet has changed
|
|
||||||
lastKeyPacket = keyPacket;
|
|
||||||
lastPacket = keyPacket;
|
|
||||||
decoder.decode(keyPacket);
|
|
||||||
timestampsOfInterest.push(targetPacket.timestamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (lastPacket.sequenceNumber < targetPacket.sequenceNumber) {
|
timestampsOfInterest.push(targetPacket.timestamp);
|
||||||
const nextPacket = await packetSink.getNextPacket(lastPacket);
|
maxSequenceNumber = Math.max(targetPacket.sequenceNumber, maxSequenceNumber);
|
||||||
assert(nextPacket);
|
|
||||||
|
|
||||||
lastPacket = nextPacket;
|
lastPacket = targetPacket;
|
||||||
decoder.decode(nextPacket);
|
lastKeyPacket = keyPacket;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!terminated) {
|
if (!terminated) {
|
||||||
await decoder.flush();
|
if (maxSequenceNumber !== -1) {
|
||||||
|
// We still need to decode packets
|
||||||
|
await decodePackets();
|
||||||
|
}
|
||||||
|
|
||||||
|
await flushDecoder();
|
||||||
lastUsedSample?.close();
|
lastUsedSample?.close();
|
||||||
}
|
}
|
||||||
decoder.close();
|
decoder.close();
|
||||||
|
|||||||
+4
-5
@@ -28,11 +28,10 @@ export class EncodedPacket {
|
|||||||
/** The duration of this packet in seconds. */
|
/** The duration of this packet in seconds. */
|
||||||
public readonly duration: number,
|
public readonly duration: number,
|
||||||
/**
|
/**
|
||||||
* The sequence number of this packet. The sequence number indicates the decode order of the packets. Packet A
|
* The sequence number indicates the decode order of the packets. Packet A must be decoded before packet B if A
|
||||||
* must be decoded before packet B if A has a lower sequence number than B. If two packets have the same
|
* has a lower sequence number than B. If two packets have the same sequence number, they are the same packet.
|
||||||
* sequence number, they are the same packet. Otherwise, sequence numbers are arbitrary and are not guaranteed
|
* Otherwise, sequence numbers are arbitrary and are not guaranteed to have any meaning besides their relative
|
||||||
* to have any meaning besides their relative ordering. Negative sequence numbers mean the sequence number
|
* ordering. Negative sequence numbers mean the sequence number is undefined.
|
||||||
* is undefined.
|
|
||||||
*/
|
*/
|
||||||
public readonly sequenceNumber = -1,
|
public readonly sequenceNumber = -1,
|
||||||
/**
|
/**
|
||||||
|
|||||||
+8
-3
@@ -225,7 +225,10 @@ export class VideoSample {
|
|||||||
assert(this._data !== null);
|
assert(this._data !== null);
|
||||||
|
|
||||||
if (isVideoFrame(this._data)) {
|
if (isVideoFrame(this._data)) {
|
||||||
return new VideoSample(this._data.clone());
|
return new VideoSample(this._data.clone(), {
|
||||||
|
timestamp: this.timestamp,
|
||||||
|
duration: this.duration,
|
||||||
|
});
|
||||||
} else if (this._data instanceof Uint8Array) {
|
} else if (this._data instanceof Uint8Array) {
|
||||||
return new VideoSample(this._data.slice(), {
|
return new VideoSample(this._data.slice(), {
|
||||||
format: this.format!,
|
format: this.format!,
|
||||||
@@ -812,10 +815,12 @@ export class AudioSample {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isAudioData(this._data)) {
|
if (isAudioData(this._data)) {
|
||||||
return new AudioSample(this._data.clone());
|
const sample = new AudioSample(this._data.clone());
|
||||||
|
sample.setTimestamp(this.timestamp); // Make sure the timestamp is precise (beyond microsecond accuracy)
|
||||||
|
|
||||||
|
return sample;
|
||||||
} else {
|
} else {
|
||||||
return new AudioSample({
|
return new AudioSample({
|
||||||
|
|
||||||
format: this.format,
|
format: this.format,
|
||||||
sampleRate: this.sampleRate,
|
sampleRate: this.sampleRate,
|
||||||
numberOfFrames: this.numberOfFrames,
|
numberOfFrames: this.numberOfFrames,
|
||||||
|
|||||||
Reference in New Issue
Block a user