You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
299 lines
8.8 KiB
299 lines
8.8 KiB
/**
|
|
* mux.js
|
|
*
|
|
* Copyright (c) Brightcove
|
|
* Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
|
|
*
|
|
* Utilities to detect basic properties and metadata about TS Segments.
|
|
*/
|
|
'use strict';
|
|
|
|
var StreamTypes = require('./stream-types.js');
|
|
|
|
var parsePid = function parsePid(packet) {
|
|
var pid = packet[1] & 0x1f;
|
|
pid <<= 8;
|
|
pid |= packet[2];
|
|
return pid;
|
|
};
|
|
|
|
var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
|
|
return !!(packet[1] & 0x40);
|
|
};
|
|
|
|
var parseAdaptionField = function parseAdaptionField(packet) {
|
|
var offset = 0; // if an adaption field is present, its length is specified by the
|
|
// fifth byte of the TS packet header. The adaptation field is
|
|
// used to add stuffing to PES packets that don't fill a complete
|
|
// TS packet, and to specify some forms of timing and control data
|
|
// that we do not currently use.
|
|
|
|
if ((packet[3] & 0x30) >>> 4 > 0x01) {
|
|
offset += packet[4] + 1;
|
|
}
|
|
|
|
return offset;
|
|
};
|
|
|
|
var parseType = function parseType(packet, pmtPid) {
|
|
var pid = parsePid(packet);
|
|
|
|
if (pid === 0) {
|
|
return 'pat';
|
|
} else if (pid === pmtPid) {
|
|
return 'pmt';
|
|
} else if (pmtPid) {
|
|
return 'pes';
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
var parsePat = function parsePat(packet) {
|
|
var pusi = parsePayloadUnitStartIndicator(packet);
|
|
var offset = 4 + parseAdaptionField(packet);
|
|
|
|
if (pusi) {
|
|
offset += packet[offset] + 1;
|
|
}
|
|
|
|
return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
|
|
};
|
|
|
|
var parsePmt = function parsePmt(packet) {
|
|
var programMapTable = {};
|
|
var pusi = parsePayloadUnitStartIndicator(packet);
|
|
var payloadOffset = 4 + parseAdaptionField(packet);
|
|
|
|
if (pusi) {
|
|
payloadOffset += packet[payloadOffset] + 1;
|
|
} // PMTs can be sent ahead of the time when they should actually
|
|
// take effect. We don't believe this should ever be the case
|
|
// for HLS but we'll ignore "forward" PMT declarations if we see
|
|
// them. Future PMT declarations have the current_next_indicator
|
|
// set to zero.
|
|
|
|
|
|
if (!(packet[payloadOffset + 5] & 0x01)) {
|
|
return;
|
|
}
|
|
|
|
var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
|
|
|
|
sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
|
|
tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
|
|
// long the program info descriptors are
|
|
|
|
programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
|
|
|
|
var offset = 12 + programInfoLength;
|
|
|
|
while (offset < tableEnd) {
|
|
var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
|
|
|
|
programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
|
|
// skip past the elementary stream descriptors, if present
|
|
|
|
offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
|
|
}
|
|
|
|
return programMapTable;
|
|
};
|
|
|
|
var parsePesType = function parsePesType(packet, programMapTable) {
|
|
var pid = parsePid(packet);
|
|
var type = programMapTable[pid];
|
|
|
|
switch (type) {
|
|
case StreamTypes.H264_STREAM_TYPE:
|
|
return 'video';
|
|
|
|
case StreamTypes.ADTS_STREAM_TYPE:
|
|
return 'audio';
|
|
|
|
case StreamTypes.METADATA_STREAM_TYPE:
|
|
return 'timed-metadata';
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
};
|
|
|
|
var parsePesTime = function parsePesTime(packet) {
|
|
var pusi = parsePayloadUnitStartIndicator(packet);
|
|
|
|
if (!pusi) {
|
|
return null;
|
|
}
|
|
|
|
var offset = 4 + parseAdaptionField(packet);
|
|
|
|
if (offset >= packet.byteLength) {
|
|
// From the H 222.0 MPEG-TS spec
|
|
// "For transport stream packets carrying PES packets, stuffing is needed when there
|
|
// is insufficient PES packet data to completely fill the transport stream packet
|
|
// payload bytes. Stuffing is accomplished by defining an adaptation field longer than
|
|
// the sum of the lengths of the data elements in it, so that the payload bytes
|
|
// remaining after the adaptation field exactly accommodates the available PES packet
|
|
// data."
|
|
//
|
|
// If the offset is >= the length of the packet, then the packet contains no data
|
|
// and instead is just adaption field stuffing bytes
|
|
return null;
|
|
}
|
|
|
|
var pes = null;
|
|
var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
|
|
// and a DTS value. Determine what combination of values is
|
|
// available to work with.
|
|
|
|
ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
|
|
// performs all bitwise operations on 32-bit integers but javascript
|
|
// supports a much greater range (52-bits) of integer using standard
|
|
// mathematical operations.
|
|
// We construct a 31-bit value using bitwise operators over the 31
|
|
// most significant bits and then multiply by 4 (equal to a left-shift
|
|
// of 2) before we add the final 2 least significant bits of the
|
|
// timestamp (equal to an OR.)
|
|
|
|
if (ptsDtsFlags & 0xC0) {
|
|
pes = {}; // the PTS and DTS are not written out directly. For information
|
|
// on how they are encoded, see
|
|
// http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
|
|
|
|
pes.pts = (packet[offset + 9] & 0x0E) << 27 | (packet[offset + 10] & 0xFF) << 20 | (packet[offset + 11] & 0xFE) << 12 | (packet[offset + 12] & 0xFF) << 5 | (packet[offset + 13] & 0xFE) >>> 3;
|
|
pes.pts *= 4; // Left shift by 2
|
|
|
|
pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
|
|
|
|
pes.dts = pes.pts;
|
|
|
|
if (ptsDtsFlags & 0x40) {
|
|
pes.dts = (packet[offset + 14] & 0x0E) << 27 | (packet[offset + 15] & 0xFF) << 20 | (packet[offset + 16] & 0xFE) << 12 | (packet[offset + 17] & 0xFF) << 5 | (packet[offset + 18] & 0xFE) >>> 3;
|
|
pes.dts *= 4; // Left shift by 2
|
|
|
|
pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
|
|
}
|
|
}
|
|
|
|
return pes;
|
|
};
|
|
|
|
var parseNalUnitType = function parseNalUnitType(type) {
|
|
switch (type) {
|
|
case 0x05:
|
|
return 'slice_layer_without_partitioning_rbsp_idr';
|
|
|
|
case 0x06:
|
|
return 'sei_rbsp';
|
|
|
|
case 0x07:
|
|
return 'seq_parameter_set_rbsp';
|
|
|
|
case 0x08:
|
|
return 'pic_parameter_set_rbsp';
|
|
|
|
case 0x09:
|
|
return 'access_unit_delimiter_rbsp';
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
};
|
|
|
|
var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
|
|
var offset = 4 + parseAdaptionField(packet);
|
|
var frameBuffer = packet.subarray(offset);
|
|
var frameI = 0;
|
|
var frameSyncPoint = 0;
|
|
var foundKeyFrame = false;
|
|
var nalType; // advance the sync point to a NAL start, if necessary
|
|
|
|
for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
|
|
if (frameBuffer[frameSyncPoint + 2] === 1) {
|
|
// the sync point is properly aligned
|
|
frameI = frameSyncPoint + 5;
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (frameI < frameBuffer.byteLength) {
|
|
// look at the current byte to determine if we've hit the end of
|
|
// a NAL unit boundary
|
|
switch (frameBuffer[frameI]) {
|
|
case 0:
|
|
// skip past non-sync sequences
|
|
if (frameBuffer[frameI - 1] !== 0) {
|
|
frameI += 2;
|
|
break;
|
|
} else if (frameBuffer[frameI - 2] !== 0) {
|
|
frameI++;
|
|
break;
|
|
}
|
|
|
|
if (frameSyncPoint + 3 !== frameI - 2) {
|
|
nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
|
|
|
|
if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
|
|
foundKeyFrame = true;
|
|
}
|
|
} // drop trailing zeroes
|
|
|
|
|
|
do {
|
|
frameI++;
|
|
} while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
|
|
|
|
frameSyncPoint = frameI - 2;
|
|
frameI += 3;
|
|
break;
|
|
|
|
case 1:
|
|
// skip past non-sync sequences
|
|
if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
|
|
frameI += 3;
|
|
break;
|
|
}
|
|
|
|
nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
|
|
|
|
if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
|
|
foundKeyFrame = true;
|
|
}
|
|
|
|
frameSyncPoint = frameI - 2;
|
|
frameI += 3;
|
|
break;
|
|
|
|
default:
|
|
// the current byte isn't a one or zero, so it cannot be part
|
|
// of a sync sequence
|
|
frameI += 3;
|
|
break;
|
|
}
|
|
}
|
|
|
|
frameBuffer = frameBuffer.subarray(frameSyncPoint);
|
|
frameI -= frameSyncPoint;
|
|
frameSyncPoint = 0; // parse the final nal
|
|
|
|
if (frameBuffer && frameBuffer.byteLength > 3) {
|
|
nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
|
|
|
|
if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
|
|
foundKeyFrame = true;
|
|
}
|
|
}
|
|
|
|
return foundKeyFrame;
|
|
};
|
|
|
|
module.exports = {
|
|
parseType: parseType,
|
|
parsePat: parsePat,
|
|
parsePmt: parsePmt,
|
|
parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
|
|
parsePesType: parsePesType,
|
|
parsePesTime: parsePesTime,
|
|
videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
|
|
}; |