299 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			299 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * 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
 | |
| }; | 
