UNPKG

mpegts-demuxer

Version:

Demuxes an MPEG Transport Stream into elementary packets.

408 lines (341 loc) 9.59 kB
// 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 */ import { leftShift, rightShift, bitMask, } from './bitwiseOperators' import type { Pmt, Packet, } from '../classes' import { STREAM_TYPES, MEDIA_TYPES, PACKET_LEN, } from '../constants' import { Stream, } from '../classes' export function getStream(pids: Map<number, Stream>, pid: number): Stream { if (!pids.has(pid)) { pids.set(pid, new Stream()) } // https://github.com/microsoft/TypeScript/issues/13086 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return pids.get(pid)! } export function getStreamType(type_id: number): number { switch (type_id) { case 0x01: case 0x02: return STREAM_TYPES.mpeg2_video case 0x80: return STREAM_TYPES.mpeg2_video case 0x1b: return STREAM_TYPES.h264_video case 0xea: return STREAM_TYPES.vc1_video case 0x81: case 0x06: return STREAM_TYPES.ac3_audio case 0x03: case 0x04: return STREAM_TYPES.mpeg2_audio case 0x0f: return STREAM_TYPES.aac_audio default: return STREAM_TYPES.data } } export function getMediaType(type_id: number): number { switch (type_id) { case 0x01: // mpeg2_video case 0x02: // mpeg2_video case 0x80: // mpeg2_video case 0x1b: // h264_video case 0xea: // vc1_video return MEDIA_TYPES.video case 0x81: // ac3_audio case 0x06: // ac3_audio case 0x03: // mpeg2_audio case 0x04: // mpeg2_audio case 0x0f: // aac_audio return MEDIA_TYPES.audio default: return MEDIA_TYPES.unknown } } export function decodeTs(mem: DataView, p: number): number { const pieces = [ bitMask(mem.getUint8(p), 0b00001110), bitMask(mem.getUint8(p + 1), 0b11111111), bitMask(mem.getUint8(p + 2), 0b11111110), bitMask(mem.getUint8(p + 3), 0b11111111), bitMask(mem.getUint8(p + 4), 0b11111110), ] return leftShift(pieces[0], 29) + leftShift(pieces[1], 22) + leftShift(pieces[2], 14) + leftShift(pieces[3], 7) + rightShift(pieces[4], 1) } export function decodePat( mem: DataView, startingPtr: number, startingLen: number, pids: Map<number, Stream>, pstart: number, ): number { 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 } export function memcpy( dstm: DataView, dstp: number, srcm: DataView, srcp: number, len: number, ): void { 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 export function decodePmt( pmt: Pmt, mem: DataView, startingPtr: number, startingLen: number, pids: Map<number, Stream>, s: Stream, pstart: number, ): number { 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: DataView, startingPtr: number, startingLen: number, s: Stream, pstart: number, cb: (p: Packet) => void, copy: boolean, ): number { 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 === 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 === 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 !== MEDIA_TYPES.unknown) { const packet = s.write(mem, ptr, len, pstart, copy) if (packet) cb(packet) } return 0 } export function demuxPacket( pmt: Pmt, mem: DataView, startingPtr: number, pids: Map<number, Stream>, cb: (p: Packet) => void, copy: boolean, ): number { 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 = 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] */ export function getSafeDataView(data: Buffer): DataView { return new DataView( data.buffer.slice( data.byteOffset, data.byteOffset + data.byteLength, ), ) }