UNPKG

mpegts-demuxer

Version:

Demuxes an MPEG Transport Stream into elementary packets.

496 lines (353 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getStream = getStream; exports.getStreamType = getStreamType; exports.getMediaType = getMediaType; exports.decodeTs = decodeTs; exports.decodePat = decodePat; exports.memcpy = memcpy; exports.decodePmt = decodePmt; exports.demuxPacket = demuxPacket; exports.getSafeDataView = getSafeDataView; var _bitwiseOperators = require("./bitwiseOperators"); var _constants = require("../constants"); var _classes = require("../classes"); // This is one of those rare applications where we actually need bitwise operators // because we are actually dealing with bit level data / specifications. /* eslint-disable no-bitwise */ // The original library relied heavily on property reassignment. // Changing this is a goal, but will be a heavy lift. /* eslint-disable no-param-reassign */ function getStream(pids, pid) { if (!pids.has(pid)) { pids.set(pid, new _classes.Stream()); } // https://github.com/microsoft/TypeScript/issues/13086 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return pids.get(pid); } function getStreamType(type_id) { switch (type_id) { case 0x01: case 0x02: return _constants.STREAM_TYPES.mpeg2_video; case 0x80: return _constants.STREAM_TYPES.mpeg2_video; case 0x1b: return _constants.STREAM_TYPES.h264_video; case 0xea: return _constants.STREAM_TYPES.vc1_video; case 0x81: case 0x06: return _constants.STREAM_TYPES.ac3_audio; case 0x03: case 0x04: return _constants.STREAM_TYPES.mpeg2_audio; case 0x0f: return _constants.STREAM_TYPES.aac_audio; default: return _constants.STREAM_TYPES.data; } } function getMediaType(type_id) { switch (type_id) { case 0x01: // mpeg2_video case 0x02: // mpeg2_video case 0x80: // mpeg2_video case 0x1b: // h264_video case 0xea: // vc1_video return _constants.MEDIA_TYPES.video; case 0x81: // ac3_audio case 0x06: // ac3_audio case 0x03: // mpeg2_audio case 0x04: // mpeg2_audio case 0x0f: // aac_audio return _constants.MEDIA_TYPES.audio; default: return _constants.MEDIA_TYPES.unknown; } } function decodeTs(mem, p) { const pieces = [(0, _bitwiseOperators.bitMask)(mem.getUint8(p), 0b00001110), (0, _bitwiseOperators.bitMask)(mem.getUint8(p + 1), 0b11111111), (0, _bitwiseOperators.bitMask)(mem.getUint8(p + 2), 0b11111110), (0, _bitwiseOperators.bitMask)(mem.getUint8(p + 3), 0b11111111), (0, _bitwiseOperators.bitMask)(mem.getUint8(p + 4), 0b11111110)]; return (0, _bitwiseOperators.leftShift)(pieces[0], 29) + (0, _bitwiseOperators.leftShift)(pieces[1], 22) + (0, _bitwiseOperators.leftShift)(pieces[2], 14) + (0, _bitwiseOperators.leftShift)(pieces[3], 7) + (0, _bitwiseOperators.rightShift)(pieces[4], 1); } function decodePat(mem, startingPtr, startingLen, pids, pstart) { let ptr = startingPtr; let len = startingLen; if (pstart) { if (len < 1) { return 6; } // Incomplete PES Packet (Possibly PAT) ptr += 1; // skip pointer field len -= 1; } // check table ID if (mem.getUint8(ptr) !== 0x00) { return 0; } // not a PAT after all if (len < 8) { return 7; } // Incomplete PAT // check flag bits and length let l = mem.getUint16(ptr + 1); if ((l & 0xb000) !== 0xb000) { return 8; } // Invalid PAT Header l &= 0x0fff; len -= 3; if (l > len) { return 9; } // PAT Overflows File Length len -= 5; ptr += 8; l -= 5 + 4; if (l % 4) { return 10; } // PAT Body Isn't a Multiple of the Entry Size (32 bits) const n = l / 4; for (let i = 0; i < n; i += 1) { const program = mem.getUint16(ptr); let pid = mem.getUint16(ptr + 2); // 3 reserved bits should be on if ((pid & 0xe000) !== 0xe000) { return 11; } // Invalid PAT Entry pid &= 0x1fff; ptr += 4; const s = getStream(pids, pid); s.program = program; s.type = 0xff; } return 0; } function memcpy(dstm, dstp, srcm, srcp, len) { const dsta = new Uint8Array(dstm.buffer, dstm.byteOffset + dstp, len); const srca = new Uint8Array(srcm.buffer, srcm.byteOffset + srcp, len); dsta.set(srca); } // Pmt = program map table function decodePmt(pmt, mem, startingPtr, startingLen, pids, s, pstart) { let ptr = startingPtr; let len = startingLen; if (pstart) { if (len < 1) { return 12; } // Incomplete PES Packet (Possibly PMT) ptr += 1; // skip pointer field len -= 1; if (mem.getUint8(ptr) !== 0x02) { return 0; } // not a PMT after all if (len < 12) { return 13; } // Incomplete PMT // check flag bits and length let l = mem.getUint16(ptr + 1); if ((l & 0x3000) !== 0x3000) { return 14; } // Invalid PMT Header l = (l & 0x0fff) + 3; if (l > 512) { return 15; } // PMT Length Too Large pmt.reset(l); if (len < l) l = len; memcpy(pmt.mem, pmt.ptr, mem, ptr, l); pmt.offset += l; if (pmt.offset < pmt.len) { return 0; } // wait for next part } else { if (!pmt.offset) { return 16; } // PMT Doesn't Start at Beginning of TS Packet Payload let l = pmt.len - pmt.offset; if (len < l) l = len; memcpy(pmt.mem, pmt.ptr + pmt.offset, mem, ptr, l); pmt.offset += l; if (pmt.offset < pmt.len) { return 0; } // wait for next part } let { ptr: pmtPtr, len: l } = pmt; const pmtMem = pmt.mem; const n = (pmtMem.getUint16(pmtPtr + 10) & 0x0fff) + 12; if (n > l) { return 17; } // Program Info Oveflows PMT Length pmtPtr += n; l -= n + 4; while (l) { if (l < 5) { return 18; } // Incomplete Elementary Stream Info let pid = pmtMem.getUint16(pmtPtr + 1); if ((pid & 0xe000) !== 0xe000) { return 19; } // Invalid Elementary Stream Header pid &= 0x1fff; const ll = (pmtMem.getUint16(pmtPtr + 3) & 0x0fff) + 5; if (ll > l) { return 20; } // Elementary Stream Data Overflows PMT const type = getStreamType(pmtMem.getUint8(pmtPtr)); pmtPtr += ll; l -= ll; const ss = getStream(pids, pid); if (ss.program !== s.program || ss.type !== type) { ss.program = s.program; ss.type = type; ss.id = s.id; s.id += 1; ss.content_type = getMediaType(type); } } return 0; } // Pes = Packetized Elementary Stream function decodePes(mem, startingPtr, startingLen, s, pstart, cb, copy) { let ptr = startingPtr; let len = startingLen; // PES (Packetized Elementary Stream) if (pstart) { // PES header if (len < 6) { return 21; } // Incomplete PES Packet Header if (mem.getUint16(ptr) !== 0 || mem.getUint8(ptr + 2) !== 1) { return 22; // Invalid PES Header } const streamId = mem.getUint8(ptr + 3); let l = mem.getUint16(ptr + 4); ptr += 6; len -= 6; if (streamId < 0xbd || streamId > 0xfe || streamId > 0xbf && streamId < 0xc0 || streamId > 0xdf && streamId < 0xe0 || streamId > 0xef && streamId < 0xfa) { s.stream_id = 0; } else { // PES header extension if (len < 3) { return 23; } // PES Packet Not Long Enough for Extended Header const bitmap = mem.getUint8(ptr + 1); const hlen = mem.getUint8(ptr + 2) + 3; if (len < hlen) { return 24; } // PES Header Overflows File Length if (l > 0) { l -= hlen; } switch (bitmap & 0xc0) { case 0x80: { // PTS only if (hlen < 8) { break; } const pts = decodeTs(mem, ptr + 3); if (s.has_dts && pts !== s.dts) { s.frame_ticks = pts - s.dts; } if (pts > s.last_pts || !s.has_pts) { s.last_pts = pts; } if (s.first_pts === 0 && s.frame_num === (s.content_type === _constants.MEDIA_TYPES.video ? 1 : 0)) { s.first_pts = pts; } s.dts = pts; s.has_dts = true; s.has_pts = true; break; } case 0xc0: { // PTS,DTS if (hlen < 13) { break; } const pts = decodeTs(mem, ptr + 3); const dts = decodeTs(mem, ptr + 8); if (s.has_dts && dts > s.dts) { s.frame_ticks = dts - s.dts; } if (pts > s.last_pts || !s.has_pts) { s.last_pts = pts; } if (s.first_pts === 0 && s.frame_num === (s.content_type === _constants.MEDIA_TYPES.video ? 1 : 0)) { s.first_pts = pts; } s.dts = dts; s.has_dts = true; s.has_pts = true; break; } default: } ptr += hlen; len -= hlen; s.stream_id = streamId; s.frame_num += 1; } } if (s.stream_id && s.content_type !== _constants.MEDIA_TYPES.unknown) { const packet = s.write(mem, ptr, len, pstart, copy); if (packet) cb(packet); } return 0; } function demuxPacket(pmt, mem, startingPtr, pids, cb, copy) { let ptr = startingPtr; if (mem.getUint8(ptr) !== 0x47) { return 2; } // invalid packet sync byte let pid = mem.getUint16(ptr + 1); const flags = mem.getUint8(ptr + 3); if (pid & 0x8000) { return 3; } // transport error if (flags & 0xc0) { return 4; } // scrambled const payloadStart = pid & 0x4000; pid &= 0x1fff; // check if payload exists if (pid === 0x1fff || !(flags & 0x10)) { return 0; } ptr += 4; let len = _constants.PACKET_LEN - 4; if (flags & 0x20) { // skip adaptation field const l = mem.getUint8(ptr) + 1; if (l > len) { return 5; } // Adaptation Field Overflows File Length ptr += l; len -= l; } if (!pid) { return decodePat(mem, ptr, len, pids, payloadStart); } const s = getStream(pids, pid); if (s.program === 0xffff) { return 0; } if (s.type === 0xff) { return decodePmt(pmt, mem, ptr, len, pids, s, payloadStart); } return decodePes(mem, ptr, len, s, payloadStart, cb, copy); } /** * Returns a DataView that is safe, regardless of whether or not the input * Buffer is smaller than the buffer pool size.pool. * * The slicing and dicing is what makes it safe. * * For more information about why this is necessary view: * - https://github.com/tvkitchen/utilities/issues/13 * * @param {Buffer} data [description] * @return {DataView} [description] */ function getSafeDataView(data) { return new DataView(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)); }