mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 02:43:48 +02:00
Read only headers, no payload, when possible
This commit is contained in:
+195
-106
@@ -76,16 +76,19 @@ type ElementaryStream = {
|
|||||||
type ElementaryVideoStream = ElementaryStream & { info: { type: 'video' } };
|
type ElementaryVideoStream = ElementaryStream & { info: { type: 'video' } };
|
||||||
type ElementaryAudioStream = ElementaryStream & { info: { type: 'audio' } };
|
type ElementaryAudioStream = ElementaryStream & { info: { type: 'audio' } };
|
||||||
|
|
||||||
type PacketHeader = {
|
type TsPacketHeader = {
|
||||||
payloadUnitStartIndicator: number;
|
payloadUnitStartIndicator: number;
|
||||||
pid: number;
|
pid: number;
|
||||||
adaptationFieldControl: number;
|
adaptationFieldControl: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TsPacket = TsPacketHeader & {
|
||||||
body: Uint8Array<ArrayBufferLike>;
|
body: Uint8Array<ArrayBufferLike>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Section = {
|
type Section = {
|
||||||
startPos: number;
|
startPos: number;
|
||||||
endPos: number;
|
endPos: number | null; // null if the section was not read fully
|
||||||
pid: number;
|
pid: number;
|
||||||
payload: Uint8Array<ArrayBufferLike>;
|
payload: Uint8Array<ArrayBufferLike>;
|
||||||
};
|
};
|
||||||
@@ -136,7 +139,7 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
let hasProgramMap = false;
|
let hasProgramMap = false;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const section = await this.readSection(currentPos);
|
const section = await this.readSection(currentPos, true);
|
||||||
if (!section) {
|
if (!section) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -316,6 +319,7 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(section.endPos !== null);
|
||||||
currentPos = section.endPos;
|
currentPos = section.endPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,12 +370,13 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async readSection(startPos: number): Promise<Section | null> {
|
async readSection(startPos: number, full: boolean): Promise<Section | null> {
|
||||||
let endPos = startPos;
|
let endPos = startPos;
|
||||||
let currentPos = startPos;
|
let currentPos = startPos;
|
||||||
const chunks: Uint8Array[] = [];
|
const chunks: Uint8Array[] = [];
|
||||||
|
let chunksByteLength = 0;
|
||||||
|
|
||||||
let firstPacket: PacketHeader | null = null;
|
let firstPacket: TsPacket | null = null;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const packet = await this.readPacket(currentPos);
|
const packet = await this.readPacket(currentPos);
|
||||||
@@ -408,12 +413,19 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
if (hasPayload) {
|
if (hasPayload) {
|
||||||
if (adaptationFieldLength === 0) {
|
if (adaptationFieldLength === 0) {
|
||||||
chunks.push(packet.body);
|
chunks.push(packet.body);
|
||||||
|
chunksByteLength += packet.body.byteLength;
|
||||||
} else {
|
} else {
|
||||||
chunks.push(packet.body.subarray(adaptationFieldLength));
|
chunks.push(packet.body.subarray(adaptationFieldLength));
|
||||||
|
chunksByteLength += packet.body.byteLength - adaptationFieldLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
endPos = currentPos;
|
endPos = currentPos;
|
||||||
|
|
||||||
|
// 64 is just "a bit of data", enough for the PES packet header
|
||||||
|
if (!full && chunksByteLength >= 64) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!firstPacket) {
|
if (!firstPacket) {
|
||||||
@@ -435,13 +447,49 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
startPos,
|
startPos,
|
||||||
endPos,
|
endPos: full ? endPos : null,
|
||||||
pid: firstPacket.pid,
|
pid: firstPacket.pid,
|
||||||
payload: merged,
|
payload: merged,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async readPacket(pos: number): Promise<PacketHeader | null> {
|
async readPacketHeader(pos: number): Promise<TsPacketHeader | null> {
|
||||||
|
let slice = this.reader.requestSlice(pos, 4);
|
||||||
|
if (slice instanceof Promise) slice = await slice;
|
||||||
|
|
||||||
|
if (!slice) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncByte = readU8(slice);
|
||||||
|
if (syncByte !== 0x47) {
|
||||||
|
throw new Error('Invalid TS packet sync byte. Likely an internal bug, please report this file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextTwoBytes = readU16Be(slice);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const transportErrorIndicator = nextTwoBytes >> 15;
|
||||||
|
const payloadUnitStartIndicator = (nextTwoBytes >> 14) & 0x1;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const transportPriority = (nextTwoBytes >> 13) & 0x1;
|
||||||
|
const pid = nextTwoBytes & 0x1FFF;
|
||||||
|
|
||||||
|
const nextByte = readU8(slice);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const transportScramblingControl = nextByte >> 6;
|
||||||
|
const adaptationFieldControl = (nextByte >> 4) & 0x3;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const continuityCounter = nextByte & 0xF;
|
||||||
|
|
||||||
|
return {
|
||||||
|
payloadUnitStartIndicator,
|
||||||
|
pid,
|
||||||
|
adaptationFieldControl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async readPacket(pos: number): Promise<TsPacket | null> {
|
||||||
|
// Code in here is duplicated from readPacketHeader for performance reasons
|
||||||
let slice = this.reader.requestSlice(pos, TS_PACKET_SIZE);
|
let slice = this.reader.requestSlice(pos, TS_PACKET_SIZE);
|
||||||
if (slice instanceof Promise) slice = await slice;
|
if (slice instanceof Promise) slice = await slice;
|
||||||
|
|
||||||
@@ -473,19 +521,22 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
payloadUnitStartIndicator,
|
payloadUnitStartIndicator,
|
||||||
pid,
|
pid,
|
||||||
adaptationFieldControl,
|
adaptationFieldControl,
|
||||||
body: readBytes(slice, 184),
|
body: readBytes(slice, TS_PACKET_SIZE - 4),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type PesPacket = {
|
type PesPacketHeader = {
|
||||||
sectionStartPos: number;
|
sectionStartPos: number;
|
||||||
sectionEndPos: number;
|
sectionEndPos: number | null; // null if the section wasn't read fully
|
||||||
pts: number;
|
pts: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PesPacket = PesPacketHeader & {
|
||||||
data: Uint8Array<ArrayBufferLike>;
|
data: Uint8Array<ArrayBufferLike>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const readPesPacket = (section: Section): PesPacket | null => {
|
const readPesPacketHeader = (section: Section): PesPacketHeader | null => {
|
||||||
const bitstream = new Bitstream(section.payload);
|
const bitstream = new Bitstream(section.payload);
|
||||||
|
|
||||||
const startCodePrefix = bitstream.readBits(24);
|
const startCodePrefix = bitstream.readBits(24);
|
||||||
@@ -494,8 +545,7 @@ const readPesPacket = (section: Section): PesPacket | null => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const streamId = bitstream.readBits(8);
|
const streamId = bitstream.readBits(8);
|
||||||
const pesPacketLength = bitstream.readBits(16);
|
bitstream.skipBits(16);
|
||||||
const BYTES_UNTIL_END_OF_PES_PACKET_LENGTH = 6;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
streamId === 0b10111100 // program_stream_map
|
streamId === 0b10111100 // program_stream_map
|
||||||
@@ -514,9 +564,7 @@ const readPesPacket = (section: Section): PesPacket | null => {
|
|||||||
|
|
||||||
const ptsDtsFlags = bitstream.readBits(2);
|
const ptsDtsFlags = bitstream.readBits(2);
|
||||||
|
|
||||||
bitstream.skipBits(6);
|
bitstream.skipBits(14);
|
||||||
const pesHeaderDataLength = bitstream.readBits(8);
|
|
||||||
const pesHeaderEndPos = bitstream.pos + 8 * pesHeaderDataLength;
|
|
||||||
|
|
||||||
if (ptsDtsFlags !== 0b10 && ptsDtsFlags !== 0b11) {
|
if (ptsDtsFlags !== 0b10 && ptsDtsFlags !== 0b11) {
|
||||||
return null; // Support only timestamped packets
|
return null; // Support only timestamped packets
|
||||||
@@ -534,6 +582,30 @@ const readPesPacket = (section: Section): PesPacket | null => {
|
|||||||
return null; // Support only timestamped packets
|
return null; // Support only timestamped packets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sectionStartPos: section.startPos,
|
||||||
|
sectionEndPos: section.endPos,
|
||||||
|
pts,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const readPesPacket = (section: Section): PesPacket | null => {
|
||||||
|
assert(section.endPos !== null); // Can only read full PES packets from fully read sections
|
||||||
|
|
||||||
|
const header = readPesPacketHeader(section);
|
||||||
|
if (!header) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bitstream = new Bitstream(section.payload);
|
||||||
|
bitstream.skipBits(32);
|
||||||
|
const pesPacketLength = bitstream.readBits(16);
|
||||||
|
const BYTES_UNTIL_END_OF_PES_PACKET_LENGTH = 6;
|
||||||
|
|
||||||
|
bitstream.skipBits(16);
|
||||||
|
const pesHeaderDataLength = bitstream.readBits(8);
|
||||||
|
const pesHeaderEndPos = bitstream.pos + 8 * pesHeaderDataLength;
|
||||||
|
|
||||||
bitstream.pos = pesHeaderEndPos;
|
bitstream.pos = pesHeaderEndPos;
|
||||||
|
|
||||||
const bytePos = pesHeaderEndPos / 8;
|
const bytePos = pesHeaderEndPos / 8;
|
||||||
@@ -550,9 +622,7 @@ const readPesPacket = (section: Section): PesPacket | null => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sectionStartPos: section.startPos,
|
...header,
|
||||||
sectionEndPos: section.endPos,
|
|
||||||
pts,
|
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -562,7 +632,7 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
* Reference PES packets, spread throughout the file, to be used to speed up random access and perform
|
* Reference PES packets, spread throughout the file, to be used to speed up random access and perform
|
||||||
* binary search for packets.
|
* binary search for packets.
|
||||||
*/
|
*/
|
||||||
referencePesPackets: PesPacket[] = [];
|
referencePesPackets: PesPacketHeader[] = [];
|
||||||
endReferencePesPacketAdded = false;
|
endReferencePesPacketAdded = false;
|
||||||
readingContexts = new WeakMap<EncodedPacket, PacketReadingContext>();
|
readingContexts = new WeakMap<EncodedPacket, PacketReadingContext>();
|
||||||
|
|
||||||
@@ -609,35 +679,35 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
abstract getPacketType(packetData: Uint8Array): PacketType;
|
abstract getPacketType(packetData: Uint8Array): PacketType;
|
||||||
abstract markNextPacket(context: PacketReadingContext): Promise<void>;
|
abstract markNextPacket(context: PacketReadingContext): Promise<void>;
|
||||||
|
|
||||||
maybeInsertReferencePacket(pesPacket: PesPacket, force: boolean) {
|
maybeInsertReferencePacket(pesPacketHeader: PesPacketHeader, force: boolean) {
|
||||||
const index = binarySearchLessOrEqual(this.referencePesPackets, pesPacket.pts, x => x.pts);
|
const index = binarySearchLessOrEqual(this.referencePesPackets, pesPacketHeader.pts, x => x.pts);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
// Since pts and file position don't necessarily have a monotonic relationship (since pts can go crazy),
|
// Since pts and file position don't necessarily have a monotonic relationship (since pts can go crazy),
|
||||||
// let's see if inserting at the given index would violate the file position order. If so, return.
|
// let's see if inserting at the given index would violate the file position order. If so, return.
|
||||||
const entry = this.referencePesPackets[index]!;
|
const entry = this.referencePesPackets[index]!;
|
||||||
if (pesPacket.sectionStartPos <= entry.sectionStartPos) {
|
if (pesPacketHeader.sectionStartPos <= entry.sectionStartPos) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Too close temporally
|
// Too close temporally
|
||||||
if (!force && pesPacket.pts - entry.pts < TIMESCALE / 2) {
|
if (!force && pesPacketHeader.pts - entry.pts < TIMESCALE / 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index < this.referencePesPackets.length - 1) {
|
if (index < this.referencePesPackets.length - 1) {
|
||||||
const nextEntry = this.referencePesPackets[index + 1]!;
|
const nextEntry = this.referencePesPackets[index + 1]!;
|
||||||
if (nextEntry.sectionStartPos < pesPacket.sectionStartPos) {
|
if (nextEntry.sectionStartPos < pesPacketHeader.sectionStartPos) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Too close temporally
|
// Too close temporally
|
||||||
if (!force && nextEntry.pts - pesPacket.pts < TIMESCALE / 2) {
|
if (!force && nextEntry.pts - pesPacketHeader.pts < TIMESCALE / 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.referencePesPackets.splice(index + 1, 0, pesPacket);
|
this.referencePesPackets.splice(index + 1, 0, pesPacketHeader);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -732,30 +802,30 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
&& !this.endReferencePesPacketAdded;
|
&& !this.endReferencePesPacketAdded;
|
||||||
if (needsToLookForLastPacket) {
|
if (needsToLookForLastPacket) {
|
||||||
let currentPos = reader.fileSize! - demuxer.packetStride + demuxer.packetOffset;
|
let currentPos = reader.fileSize! - demuxer.packetStride + demuxer.packetOffset;
|
||||||
let packet = await demuxer.readPacket(currentPos);
|
let packetHeader = await demuxer.readPacketHeader(currentPos);
|
||||||
if (!packet) {
|
if (!packetHeader) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (packet.pid !== this.elementaryStream.pid || packet.payloadUnitStartIndicator === 0) {
|
while (packetHeader.pid !== this.elementaryStream.pid || packetHeader.payloadUnitStartIndicator === 0) {
|
||||||
currentPos -= demuxer.packetStride;
|
currentPos -= demuxer.packetStride;
|
||||||
const previousPacket = await demuxer.readPacket(currentPos);
|
const previousPacketHeader = await demuxer.readPacketHeader(currentPos);
|
||||||
if (!previousPacket) {
|
if (!previousPacketHeader) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
packet = previousPacket;
|
packetHeader = previousPacketHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
const section = await demuxer.readSection(currentPos);
|
const section = await demuxer.readSection(currentPos, false);
|
||||||
assert(section);
|
assert(section);
|
||||||
|
|
||||||
const pesPacket = readPesPacket(section);
|
const pesPacketHeader = readPesPacketHeader(section);
|
||||||
if (!pesPacket) {
|
if (!pesPacketHeader) {
|
||||||
throw new Error(MISSING_PES_PACKET_ERROR);
|
throw new Error(MISSING_PES_PACKET_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.maybeInsertReferencePacket(pesPacket, true);
|
this.maybeInsertReferencePacket(pesPacketHeader, true);
|
||||||
this.endReferencePesPacketAdded = true;
|
this.endReferencePesPacketAdded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,20 +849,20 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
demuxer.packetStride,
|
demuxer.packetStride,
|
||||||
) + demuxer.packetOffset;
|
) + demuxer.packetOffset;
|
||||||
let currentPos = midpoint;
|
let currentPos = midpoint;
|
||||||
let packet = await demuxer.readPacket(currentPos);
|
let packetHeader = await demuxer.readPacketHeader(currentPos);
|
||||||
assert(packet);
|
assert(packetHeader);
|
||||||
|
|
||||||
while (
|
while (
|
||||||
currentPos < nextEntry.sectionStartPos
|
currentPos < nextEntry.sectionStartPos
|
||||||
&& (packet.pid !== this.elementaryStream.pid || packet.payloadUnitStartIndicator === 0)
|
&& (packetHeader.pid !== this.elementaryStream.pid || packetHeader.payloadUnitStartIndicator === 0)
|
||||||
) {
|
) {
|
||||||
currentPos += demuxer.packetStride;
|
currentPos += demuxer.packetStride;
|
||||||
const previousPacket = await demuxer.readPacket(currentPos);
|
const previousPacketHeader = await demuxer.readPacketHeader(currentPos);
|
||||||
if (!previousPacket) {
|
if (!previousPacketHeader) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
packet = previousPacket;
|
packetHeader = previousPacketHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPos >= nextEntry.sectionStartPos) {
|
if (currentPos >= nextEntry.sectionStartPos) {
|
||||||
@@ -800,33 +870,40 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const section = await demuxer.readSection(currentPos);
|
const section = await demuxer.readSection(currentPos, false);
|
||||||
assert(section);
|
assert(section);
|
||||||
|
|
||||||
const pesPacket = readPesPacket(section);
|
const pesPacketHeader = readPesPacketHeader(section);
|
||||||
if (!pesPacket) {
|
if (!pesPacketHeader) {
|
||||||
throw new Error(MISSING_PES_PACKET_ERROR);
|
throw new Error(MISSING_PES_PACKET_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
const addedPoint = this.maybeInsertReferencePacket(pesPacket, false);
|
const addedPoint = this.maybeInsertReferencePacket(pesPacketHeader, false);
|
||||||
if (!addedPoint) {
|
if (!addedPoint) {
|
||||||
break; // Should rarely kick
|
break; // Should rarely kick
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pesPacket.pts <= searchPts) {
|
if (pesPacketHeader.pts <= searchPts) {
|
||||||
// The midpoint packet is to the left of our search timestamp, so continue with the right half now
|
// The midpoint packet is to the left of our search timestamp, so continue with the right half now
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentPesPacket = this.referencePesPackets[currentIndex]!;
|
let currentPesPacketHeader = this.referencePesPackets[currentIndex]!;
|
||||||
assert(currentPesPacket.pts <= searchPts);
|
assert(currentPesPacketHeader.pts <= searchPts);
|
||||||
|
|
||||||
/** Stores the best PES packet we've found so far (that meets all required criteria). */
|
/** Stores the best PES packet we've found so far (that meets all required criteria). */
|
||||||
let bestPesPacket: PesPacket | null = null;
|
let bestPesPacketHeader: PesPacketHeader | null = null;
|
||||||
|
|
||||||
const pesPacketHasKeyframe = async (pesPacket: PesPacket) => {
|
const pesPacketHasKeyframe = async (sectionStartPos: number) => {
|
||||||
const context = new PacketReadingContext(this, pesPacket, false);
|
const section = await demuxer.readSection(sectionStartPos, true);
|
||||||
|
assert(section);
|
||||||
|
assert(section.pid === this.elementaryStream.pid);
|
||||||
|
|
||||||
|
const fullPesPacket = readPesPacket(section);
|
||||||
|
assert(fullPesPacket);
|
||||||
|
|
||||||
|
const context = new PacketReadingContext(this, fullPesPacket, false);
|
||||||
await this.markNextPacket(context);
|
await this.markNextPacket(context);
|
||||||
|
|
||||||
if (!context.suppliedPacket) {
|
if (!context.suppliedPacket) {
|
||||||
@@ -836,62 +913,63 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
return this.getPacketType(context.suppliedPacket.data);
|
return this.getPacketType(context.suppliedPacket.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!keyframesOnly || await pesPacketHasKeyframe(currentPesPacket)) {
|
if (!keyframesOnly || await pesPacketHasKeyframe(currentPesPacketHeader.sectionStartPos)) {
|
||||||
bestPesPacket = currentPesPacket;
|
bestPesPacketHeader = currentPesPacketHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
const advancedPesPackets = [bestPesPacket];
|
// "advanced" as in "moved past"
|
||||||
|
const advancedPesPacketHeaders = [bestPesPacketHeader];
|
||||||
|
|
||||||
// Starting from the binary search guess, let's now find the moment where the packet timestamps cross the
|
// Starting from the binary search guess, let's now find the moment where the packet timestamps cross the
|
||||||
// search timestamp. This point will then be used as the center around which we search.
|
// search timestamp. This point will then be used as the center around which we search.
|
||||||
outer:
|
outer:
|
||||||
while (true) {
|
while (true) {
|
||||||
let currentPos = currentPesPacket.sectionEndPos;
|
let currentPos = currentPesPacketHeader.sectionStartPos + demuxer.packetStride;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const packet = await demuxer.readPacket(currentPos);
|
const packetHeader = await demuxer.readPacketHeader(currentPos);
|
||||||
if (!packet) {
|
if (!packetHeader) {
|
||||||
break outer; // End of file
|
break outer; // End of file
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.pid === this.elementaryStream.pid) {
|
if (packetHeader.pid === this.elementaryStream.pid && packetHeader.payloadUnitStartIndicator === 1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPos += demuxer.packetStride;
|
currentPos += demuxer.packetStride;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextSection = await demuxer.readSection(currentPos);
|
const nextSection = await demuxer.readSection(currentPos, false);
|
||||||
if (!nextSection) {
|
if (!nextSection) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextPesPacket = readPesPacket(nextSection);
|
const nextPesPacketHeader = readPesPacketHeader(nextSection);
|
||||||
if (!nextPesPacket) {
|
if (!nextPesPacketHeader) {
|
||||||
throw new Error(MISSING_PES_PACKET_ERROR);
|
throw new Error(MISSING_PES_PACKET_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextPesPacket.pts > searchPts) {
|
if (nextPesPacketHeader.pts > searchPts) {
|
||||||
// The timestamps cross the search timestamp, stop
|
// The timestamps cross the search timestamp, stop
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect matching packets we find along the way
|
// Collect matching packets we find along the way
|
||||||
if (
|
if (
|
||||||
(bestPesPacket === null || bestPesPacket.pts < nextPesPacket.pts)
|
(bestPesPacketHeader === null || bestPesPacketHeader.pts < nextPesPacketHeader.pts)
|
||||||
&& nextPesPacket.pts <= searchPts
|
&& nextPesPacketHeader.pts <= searchPts
|
||||||
&& (!keyframesOnly || await pesPacketHasKeyframe(nextPesPacket))
|
&& (!keyframesOnly || await pesPacketHasKeyframe(currentPos))
|
||||||
) {
|
) {
|
||||||
bestPesPacket = nextPesPacket;
|
bestPesPacketHeader = nextPesPacketHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPesPacket = nextPesPacket;
|
currentPesPacketHeader = nextPesPacketHeader;
|
||||||
advancedPesPackets.push(nextPesPacket);
|
advancedPesPacketHeaders.push(nextPesPacketHeader);
|
||||||
|
|
||||||
if (reader.fileSize === null) {
|
if (reader.fileSize === null) {
|
||||||
// If the file size is undefined, that means that the binary search step is skipped, meaning no
|
// If the file size is undefined, that means that the binary search step is skipped, meaning no
|
||||||
// reference packets are inserted. So, let's instead insert reference packets in the linear search step.
|
// reference packets are inserted. So, let's instead insert reference packets in the linear search step.
|
||||||
this.maybeInsertReferencePacket(nextPesPacket, false);
|
this.maybeInsertReferencePacket(nextPesPacketHeader, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -902,8 +980,8 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
|
|
||||||
// Depending on how long the previous scan went, we might not need to do the full lookbehind, or even none at
|
// Depending on how long the previous scan went, we might not need to do the full lookbehind, or even none at
|
||||||
// all if we're lucky
|
// all if we're lucky
|
||||||
const lookbehindNeeded = Math.max(lookaround - advancedPesPackets.length + 1, 0);
|
const lookbehindNeeded = Math.max(lookaround - advancedPesPacketHeaders.length + 1, 0);
|
||||||
let minPos = advancedPesPackets[0]!.sectionStartPos;
|
let minPos = advancedPesPacketHeaders[0]!.sectionStartPos;
|
||||||
|
|
||||||
/** Scans `n` contiguous PES packets in succession. */
|
/** Scans `n` contiguous PES packets in succession. */
|
||||||
const doLinearScan = async (startPos: number, n: number) => {
|
const doLinearScan = async (startPos: number, n: number) => {
|
||||||
@@ -912,36 +990,39 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
outer:
|
outer:
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const packet = await demuxer.readPacket(currentPos);
|
const packetHeader = await demuxer.readPacketHeader(currentPos);
|
||||||
if (!packet) {
|
if (!packetHeader) {
|
||||||
break outer; // End of file
|
break outer; // End of file
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.pid === this.elementaryStream.pid) {
|
if (
|
||||||
|
packetHeader.pid === this.elementaryStream.pid
|
||||||
|
&& packetHeader.payloadUnitStartIndicator === 1
|
||||||
|
) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPos += demuxer.packetStride;
|
currentPos += demuxer.packetStride;
|
||||||
}
|
}
|
||||||
|
|
||||||
const section = await demuxer.readSection(currentPos);
|
const section = await demuxer.readSection(currentPos, false);
|
||||||
assert(section);
|
assert(section);
|
||||||
assert(section.pid === this.elementaryStream.pid);
|
assert(section.pid === this.elementaryStream.pid);
|
||||||
|
|
||||||
const pesPacket = readPesPacket(section);
|
const pesPacketHeader = readPesPacketHeader(section);
|
||||||
if (!pesPacket) {
|
if (!pesPacketHeader) {
|
||||||
throw new Error(MISSING_PES_PACKET_ERROR);
|
throw new Error(MISSING_PES_PACKET_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(bestPesPacket === null || bestPesPacket.pts < pesPacket.pts)
|
(bestPesPacketHeader === null || bestPesPacketHeader.pts < pesPacketHeader.pts)
|
||||||
&& pesPacket.pts <= searchPts
|
&& pesPacketHeader.pts <= searchPts
|
||||||
&& (!keyframesOnly || await pesPacketHasKeyframe(pesPacket))
|
&& (!keyframesOnly || await pesPacketHasKeyframe(currentPos))
|
||||||
) {
|
) {
|
||||||
bestPesPacket = pesPacket;
|
bestPesPacketHeader = pesPacketHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPos = section.endPos;
|
currentPos += demuxer.packetStride;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -954,12 +1035,15 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
while (true) {
|
while (true) {
|
||||||
currentPos -= demuxer.packetStride;
|
currentPos -= demuxer.packetStride;
|
||||||
|
|
||||||
const packet = await demuxer.readPacket(currentPos);
|
const packetHeader = await demuxer.readPacketHeader(currentPos);
|
||||||
if (!packet) {
|
if (!packetHeader) {
|
||||||
break outer;
|
break outer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.pid === this.elementaryStream.pid && packet.payloadUnitStartIndicator === 1) {
|
if (
|
||||||
|
packetHeader.pid === this.elementaryStream.pid
|
||||||
|
&& packetHeader.payloadUnitStartIndicator === 1
|
||||||
|
) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -971,43 +1055,47 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lookahead
|
// Lookahead
|
||||||
await doLinearScan(currentPesPacket.sectionEndPos, lookaround);
|
await doLinearScan(currentPesPacketHeader.sectionStartPos + demuxer.packetStride, lookaround);
|
||||||
|
|
||||||
// If we're looking specifically for a keyframe but haven't found one yet, that means we'll need to go left
|
// If we're looking specifically for a keyframe but haven't found one yet, that means we'll need to go left
|
||||||
// until we find one.
|
// until we find one.
|
||||||
if (!bestPesPacket && keyframesOnly) {
|
if (!bestPesPacketHeader && keyframesOnly) {
|
||||||
let currentPos = minPos;
|
let currentPos = minPos;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
currentPos -= demuxer.packetStride;
|
currentPos -= demuxer.packetStride;
|
||||||
|
|
||||||
const packet = await demuxer.readPacket(currentPos);
|
const packetHeader = await demuxer.readPacketHeader(currentPos);
|
||||||
if (!packet) {
|
if (!packetHeader) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.pid === this.elementaryStream.pid && packet.payloadUnitStartIndicator === 1) {
|
if (packetHeader.pid === this.elementaryStream.pid && packetHeader.payloadUnitStartIndicator === 1) {
|
||||||
const section = await demuxer.readSection(currentPos);
|
const section = await demuxer.readSection(currentPos, false);
|
||||||
assert(section);
|
assert(section);
|
||||||
|
|
||||||
const pesPacket = readPesPacket(section);
|
const pesPacketHeader = readPesPacketHeader(section);
|
||||||
if (!pesPacket) {
|
if (!pesPacketHeader) {
|
||||||
throw new Error(MISSING_PES_PACKET_ERROR);
|
throw new Error(MISSING_PES_PACKET_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pesPacket.pts <= searchPts && (await pesPacketHasKeyframe(pesPacket))) {
|
if (pesPacketHeader.pts <= searchPts && (await pesPacketHasKeyframe(currentPos))) {
|
||||||
bestPesPacket = pesPacket;
|
bestPesPacketHeader = pesPacketHeader;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bestPesPacket) {
|
if (!bestPesPacketHeader) {
|
||||||
// Nothing was found
|
// Nothing was found
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bestSection = await demuxer.readSection(bestPesPacketHeader.sectionStartPos, true); // Read it in full
|
||||||
|
let bestPesPacket = readPesPacket(bestSection!);
|
||||||
|
assert(bestPesPacket);
|
||||||
|
|
||||||
// Final stage: we found the best PES packet, but that PES packet might contain multiple individual encoded
|
// Final stage: we found the best PES packet, but that PES packet might contain multiple individual encoded
|
||||||
// packets. Or, it might not be the start of an encoded packet, and simply a continuation of a previous one.
|
// packets. Or, it might not be the start of an encoded packet, and simply a continuation of a previous one.
|
||||||
// So, we have one last search to do.
|
// So, we have one last search to do.
|
||||||
@@ -1046,14 +1134,14 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
while (true) {
|
while (true) {
|
||||||
currentPos -= demuxer.packetStride;
|
currentPos -= demuxer.packetStride;
|
||||||
|
|
||||||
const packet = await demuxer.readPacket(currentPos);
|
const packetHeader = await demuxer.readPacketHeader(currentPos);
|
||||||
if (!packet) {
|
if (!packetHeader) {
|
||||||
// Past start of file
|
// Past start of file
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.pid === this.elementaryStream.pid && packet.payloadUnitStartIndicator === 1) {
|
if (packetHeader.pid === this.elementaryStream.pid && packetHeader.payloadUnitStartIndicator === 1) {
|
||||||
const section = await demuxer.readSection(currentPos);
|
const section = await demuxer.readSection(currentPos, true);
|
||||||
assert(section);
|
assert(section);
|
||||||
|
|
||||||
const pesPacket = readPesPacket(section);
|
const pesPacket = readPesPacket(section);
|
||||||
@@ -1392,21 +1480,22 @@ class PacketReadingContext {
|
|||||||
} else {
|
} else {
|
||||||
// Find the next PES packet
|
// Find the next PES packet
|
||||||
let currentPos = last(this.pesPackets)!.sectionEndPos;
|
let currentPos = last(this.pesPackets)!.sectionEndPos;
|
||||||
|
assert(currentPos !== null);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const packet = await this.demuxer.readPacket(currentPos);
|
const packetHeader = await this.demuxer.readPacketHeader(currentPos);
|
||||||
if (!packet) {
|
if (!packetHeader) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.pid === this.pid) {
|
if (packetHeader.pid === this.pid) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPos += this.demuxer.packetStride;
|
currentPos += this.demuxer.packetStride;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextSection = await this.demuxer.readSection(currentPos);
|
const nextSection = await this.demuxer.readSection(currentPos, true);
|
||||||
if (!nextSection) {
|
if (!nextSection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user