Add support for null TS packets in HLS

The timestamp parser in our HLS parser should not fail on null TS
packets.  These are indicates by a packet ID of 8191 (0x1fff), and
according to the spec, should be skipped by any receiver.

We will not skip such packets and continue through the segment to find
a PES packet with a timestamp.

In addition to the comments in #2546, I found this document helpful:
https://www.mikrocontroller.net/attachment/27265/mpeg2ts.pdf

Closes #2546

Change-Id: Id9dc16160bbde03969199150ca50122f12de77f4
This commit is contained in:
Joey Parrish
2020-05-13 09:55:06 -07:00
parent a7d46ee3d1
commit 7bce3f452b
2 changed files with 56 additions and 1 deletions
+10 -1
View File
@@ -1966,9 +1966,18 @@ shaka.hls.HlsParser = class {
}
const flagsAndPacketId = reader.readUint16();
const packetId = flagsAndPacketId & 0x1fff;
if (packetId == 0x1fff) {
// A "null" TS packet. Skip this TS packet and try again.
skipPacket();
continue;
}
const hasPesPacket = flagsAndPacketId & 0x4000;
if (!hasPesPacket) {
fail();
// Not a PES packet yet. Skip this TS packet and try again.
skipPacket();
continue;
}
const flags = reader.readUint8();
+46
View File
@@ -1973,6 +1973,8 @@ describe('HlsParser', () => {
let segmentDataStartTime;
/** @type {!Uint8Array} */
let tsSegmentData;
/** @type {!Uint8Array} */
let nullTsPacketData;
const master = [
'#EXTM3U\n',
@@ -2026,6 +2028,10 @@ describe('HlsParser', () => {
// 180000 (TS PTS) divided by fixed TS timescale (90000) = 2s.
// 2000 (MP4 PTS) divided by parsed MP4 timescale (1000) = 2s.
segmentDataStartTime = 2;
nullTsPacketData = new Uint8Array([
0x47, // TS sync byte (fixed value)
0x1f, 0xff, // null packet (packet ID 8191)
]);
});
it('parses start time from mp4 segment', async () => {
@@ -2091,6 +2097,46 @@ describe('HlsParser', () => {
partialEndByte);
});
it('parses start time from ts segments with null packets', async () => {
const tsMediaPlaylist = media.replace(/\.mp4/g, '.ts');
// Each packet is 188 bytes, so allocate space for 3.
const tsSegmentWithNullPackets = new Uint8Array(188 * 3);
// The first two are "null" packets.
tsSegmentWithNullPackets.set(nullTsPacketData, /* offset= */ 0);
tsSegmentWithNullPackets.set(nullTsPacketData, /* offset= */ 188);
// The third has a timestamp.
tsSegmentWithNullPackets.set(tsSegmentData, /* offset= */ 188 * 2);
fakeNetEngine
.setResponseText('test:/master', master)
.setResponseText('test:/video', tsMediaPlaylist)
.setResponseValue('test:/main.ts', tsSegmentWithNullPackets);
const expectedRef = ManifestParser.makeReference(
/* uri= */ 'test:/main.ts',
/* startTime= */ 0,
/* endTime= */ 5,
/* baseUri= */ '',
expectedStartByte,
expectedEndByte);
// In VOD content, we set the timestampOffset to align the
// content to presentation time 0.
expectedRef.timestampOffset = -segmentDataStartTime;
const manifest = await parser.start('test:/master', playerInterface);
const video = manifest.variants[0].video;
await video.createSegmentIndex();
ManifestParser.verifySegmentIndex(video, [expectedRef]);
// Make sure the segment data was fetched with the correct byte
// range.
fakeNetEngine.expectRangeRequest(
'test:/main.ts',
expectedStartByte,
partialEndByte);
});
// We want to make sure that we can interrupt the parser while it is getting
// the start time. This is a regression test for Issue #1788 where
// interrupting the partial network request would be misinterpreted as the