Blame view

node_modules/mux.js/lib/m2ts/m2ts.js 16.4 KB
2a09d1a4   liuqimichale   添加宜春 天水 宣化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
  /**
   * mux.js
   *
   * Copyright (c) 2015 Brightcove
   * All rights reserved.
   *
   * A stream-based mp2t to mp4 converter. This utility can be used to
   * deliver mp4s to a SourceBuffer on platforms that support native
   * Media Source Extensions.
   */
  'use strict';
  var Stream = require('../utils/stream.js'),
    CaptionStream = require('./caption-stream'),
    StreamTypes = require('./stream-types'),
    TimestampRolloverStream = require('./timestamp-rollover-stream').TimestampRolloverStream;
  
  var m2tsStreamTypes = require('./stream-types.js');
  
  // object types
  var TransportPacketStream, TransportParseStream, ElementaryStream;
  
  // constants
  var
    MP2T_PACKET_LENGTH = 188, // bytes
    SYNC_BYTE = 0x47;
  
  /**
   * Splits an incoming stream of binary data into MPEG-2 Transport
   * Stream packets.
   */
  TransportPacketStream = function() {
    var
      buffer = new Uint8Array(MP2T_PACKET_LENGTH),
      bytesInBuffer = 0;
  
    TransportPacketStream.prototype.init.call(this);
  
     // Deliver new bytes to the stream.
  
    this.push = function(bytes) {
      var
        startIndex = 0,
        endIndex = MP2T_PACKET_LENGTH,
        everything;
  
      // If there are bytes remaining from the last segment, prepend them to the
      // bytes that were pushed in
      if (bytesInBuffer) {
        everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
        everything.set(buffer.subarray(0, bytesInBuffer));
        everything.set(bytes, bytesInBuffer);
        bytesInBuffer = 0;
      } else {
        everything = bytes;
      }
  
      // While we have enough data for a packet
      while (endIndex < everything.byteLength) {
        // Look for a pair of start and end sync bytes in the data..
        if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
          // We found a packet so emit it and jump one whole packet forward in
          // the stream
          this.trigger('data', everything.subarray(startIndex, endIndex));
          startIndex += MP2T_PACKET_LENGTH;
          endIndex += MP2T_PACKET_LENGTH;
          continue;
        }
        // If we get here, we have somehow become de-synchronized and we need to step
        // forward one byte at a time until we find a pair of sync bytes that denote
        // a packet
        startIndex++;
        endIndex++;
      }
  
      // If there was some data left over at the end of the segment that couldn't
      // possibly be a whole packet, keep it because it might be the start of a packet
      // that continues in the next segment
      if (startIndex < everything.byteLength) {
        buffer.set(everything.subarray(startIndex), 0);
        bytesInBuffer = everything.byteLength - startIndex;
      }
    };
  
    this.flush = function() {
      // If the buffer contains a whole packet when we are being flushed, emit it
      // and empty the buffer. Otherwise hold onto the data because it may be
      // important for decoding the next segment
      if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
        this.trigger('data', buffer);
        bytesInBuffer = 0;
      }
      this.trigger('done');
    };
  };
  TransportPacketStream.prototype = new Stream();
  
  /**
   * Accepts an MP2T TransportPacketStream and emits data events with parsed
   * forms of the individual transport stream packets.
   */
  TransportParseStream = function() {
    var parsePsi, parsePat, parsePmt, self;
    TransportParseStream.prototype.init.call(this);
    self = this;
  
    this.packetsWaitingForPmt = [];
    this.programMapTable = undefined;
  
    parsePsi = function(payload, psi) {
      var offset = 0;
  
      // PSI packets may be split into multiple sections and those
      // sections may be split into multiple packets. If a PSI
      // section starts in this packet, the payload_unit_start_indicator
      // will be true and the first byte of the payload will indicate
      // the offset from the current position to the start of the
      // section.
      if (psi.payloadUnitStartIndicator) {
        offset += payload[offset] + 1;
      }
  
      if (psi.type === 'pat') {
        parsePat(payload.subarray(offset), psi);
      } else {
        parsePmt(payload.subarray(offset), psi);
      }
    };
  
    parsePat = function(payload, pat) {
      pat.section_number = payload[7]; // eslint-disable-line camelcase
      pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  
      // skip the PSI header and parse the first PMT entry
      self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
      pat.pmtPid = self.pmtPid;
    };
  
    /**
     * Parse out the relevant fields of a Program Map Table (PMT).
     * @param payload {Uint8Array} the PMT-specific portion of an MP2T
     * packet. The first byte in this array should be the table_id
     * field.
     * @param pmt {object} the object that should be decorated with
     * fields parsed from the PMT.
     */
    parsePmt = function(payload, pmt) {
      var sectionLength, tableEnd, programInfoLength, offset;
  
      // 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 (!(payload[5] & 0x01)) {
        return;
      }
  
      // overwrite any existing program map table
      self.programMapTable = {
        video: null,
        audio: null,
        'timed-metadata': {}
      };
  
      // the mapping table ends at the end of the current section
      sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
      tableEnd = 3 + sectionLength - 4;
  
      // to determine where the table is, we have to figure out how
      // long the program info descriptors are
      programInfoLength = (payload[10] & 0x0f) << 8 | payload[11];
  
      // advance the offset to the first entry in the mapping table
      offset = 12 + programInfoLength;
      while (offset < tableEnd) {
        var streamType = payload[offset];
        var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2];
  
        // only map a single elementary_pid for audio and video stream types
        // TODO: should this be done for metadata too? for now maintain behavior of
        //       multiple metadata streams
        if (streamType === StreamTypes.H264_STREAM_TYPE &&
            self.programMapTable.video === null) {
          self.programMapTable.video = pid;
        } else if (streamType === StreamTypes.ADTS_STREAM_TYPE &&
                   self.programMapTable.audio === null) {
          self.programMapTable.audio = pid;
        } else if (streamType === StreamTypes.METADATA_STREAM_TYPE) {
          // map pid to stream type for metadata streams
          self.programMapTable['timed-metadata'][pid] = streamType;
        }
  
        // move to the next table entry
        // skip past the elementary stream descriptors, if present
        offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
      }
  
      // record the map on the packet as well
      pmt.programMapTable = self.programMapTable;
    };
  
    /**
     * Deliver a new MP2T packet to the stream.
     */
    this.push = function(packet) {
      var
        result = {},
        offset = 4;
  
      result.payloadUnitStartIndicator = !!(packet[1] & 0x40);
  
      // pid is a 13-bit field starting at the last bit of packet[1]
      result.pid = packet[1] & 0x1f;
      result.pid <<= 8;
      result.pid |= packet[2];
  
      // 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[offset] + 1;
      }
  
      // parse the rest of the packet based on the type
      if (result.pid === 0) {
        result.type = 'pat';
        parsePsi(packet.subarray(offset), result);
        this.trigger('data', result);
      } else if (result.pid === this.pmtPid) {
        result.type = 'pmt';
        parsePsi(packet.subarray(offset), result);
        this.trigger('data', result);
  
        // if there are any packets waiting for a PMT to be found, process them now
        while (this.packetsWaitingForPmt.length) {
          this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
        }
      } else if (this.programMapTable === undefined) {
        // When we have not seen a PMT yet, defer further processing of
        // PES packets until one has been parsed
        this.packetsWaitingForPmt.push([packet, offset, result]);
      } else {
        this.processPes_(packet, offset, result);
      }
    };
  
    this.processPes_ = function(packet, offset, result) {
      // set the appropriate stream type
      if (result.pid === this.programMapTable.video) {
        result.streamType = StreamTypes.H264_STREAM_TYPE;
      } else if (result.pid === this.programMapTable.audio) {
        result.streamType = StreamTypes.ADTS_STREAM_TYPE;
      } else {
        // if not video or audio, it is timed-metadata or unknown
        // if unknown, streamType will be undefined
        result.streamType = this.programMapTable['timed-metadata'][result.pid];
      }
  
      result.type = 'pes';
      result.data = packet.subarray(offset);
  
      this.trigger('data', result);
    };
  
  };
  TransportParseStream.prototype = new Stream();
  TransportParseStream.STREAM_TYPES  = {
    h264: 0x1b,
    adts: 0x0f
  };
  
  /**
   * Reconsistutes program elementary stream (PES) packets from parsed
   * transport stream packets. That is, if you pipe an
   * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
   * events will be events which capture the bytes for individual PES
   * packets plus relevant metadata that has been extracted from the
   * container.
   */
  ElementaryStream = function() {
    var
      self = this,
      // PES packet fragments
      video = {
        data: [],
        size: 0
      },
      audio = {
        data: [],
        size: 0
      },
      timedMetadata = {
        data: [],
        size: 0
      },
      parsePes = function(payload, pes) {
        var ptsDtsFlags;
  
        // get the packet length, this will be 0 for video
        pes.packetLength = 6 + ((payload[4] << 8) | payload[5]);
  
        // find out if this packets starts a new keyframe
        pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0;
        // 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 = payload[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) {
          // 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 = (payload[9] & 0x0E) << 27 |
            (payload[10] & 0xFF) << 20 |
            (payload[11] & 0xFE) << 12 |
            (payload[12] & 0xFF) <<  5 |
            (payload[13] & 0xFE) >>>  3;
          pes.pts *= 4; // Left shift by 2
          pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
          pes.dts = pes.pts;
          if (ptsDtsFlags & 0x40) {
            pes.dts = (payload[14] & 0x0E) << 27 |
              (payload[15] & 0xFF) << 20 |
              (payload[16] & 0xFE) << 12 |
              (payload[17] & 0xFF) << 5 |
              (payload[18] & 0xFE) >>> 3;
            pes.dts *= 4; // Left shift by 2
            pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
          }
        }
        // the data section starts immediately after the PES header.
        // pes_header_data_length specifies the number of header bytes
        // that follow the last byte of the field.
        pes.data = payload.subarray(9 + payload[8]);
      },
      flushStream = function(stream, type, forceFlush) {
        var
          packetData = new Uint8Array(stream.size),
          event = {
            type: type
          },
          i = 0,
          offset = 0,
          packetFlushable = false,
          fragment;
  
        // do nothing if there is not enough buffered data for a complete
        // PES header
        if (!stream.data.length || stream.size < 9) {
          return;
        }
        event.trackId = stream.data[0].pid;
  
        // reassemble the packet
        for (i = 0; i < stream.data.length; i++) {
          fragment = stream.data[i];
  
          packetData.set(fragment.data, offset);
          offset += fragment.data.byteLength;
        }
  
        // parse assembled packet's PES header
        parsePes(packetData, event);
  
        // non-video PES packets MUST have a non-zero PES_packet_length
        // check that there is enough stream data to fill the packet
        packetFlushable = type === 'video' || event.packetLength <= stream.size;
  
        // flush pending packets if the conditions are right
        if (forceFlush || packetFlushable) {
          stream.size = 0;
          stream.data.length = 0;
        }
  
        // only emit packets that are complete. this is to avoid assembling
        // incomplete PES packets due to poor segmentation
        if (packetFlushable) {
          self.trigger('data', event);
        }
      };
  
    ElementaryStream.prototype.init.call(this);
  
    this.push = function(data) {
      ({
        pat: function() {
          // we have to wait for the PMT to arrive as well before we
          // have any meaningful metadata
        },
        pes: function() {
          var stream, streamType;
  
          switch (data.streamType) {
          case StreamTypes.H264_STREAM_TYPE:
          case m2tsStreamTypes.H264_STREAM_TYPE:
            stream = video;
            streamType = 'video';
            break;
          case StreamTypes.ADTS_STREAM_TYPE:
            stream = audio;
            streamType = 'audio';
            break;
          case StreamTypes.METADATA_STREAM_TYPE:
            stream = timedMetadata;
            streamType = 'timed-metadata';
            break;
          default:
            // ignore unknown stream types
            return;
          }
  
          // if a new packet is starting, we can flush the completed
          // packet
          if (data.payloadUnitStartIndicator) {
            flushStream(stream, streamType, true);
          }
  
          // buffer this fragment until we are sure we've received the
          // complete payload
          stream.data.push(data);
          stream.size += data.data.byteLength;
        },
        pmt: function() {
          var
            event = {
              type: 'metadata',
              tracks: []
            },
            programMapTable = data.programMapTable;
  
          // translate audio and video streams to tracks
          if (programMapTable.video !== null) {
            event.tracks.push({
              timelineStartInfo: {
                baseMediaDecodeTime: 0
              },
              id: +programMapTable.video,
              codec: 'avc',
              type: 'video'
            });
          }
          if (programMapTable.audio !== null) {
            event.tracks.push({
              timelineStartInfo: {
                baseMediaDecodeTime: 0
              },
              id: +programMapTable.audio,
              codec: 'adts',
              type: 'audio'
            });
          }
  
          self.trigger('data', event);
        }
      })[data.type]();
    };
  
    /**
     * Flush any remaining input. Video PES packets may be of variable
     * length. Normally, the start of a new video packet can trigger the
     * finalization of the previous packet. That is not possible if no
     * more video is forthcoming, however. In that case, some other
     * mechanism (like the end of the file) has to be employed. When it is
     * clear that no additional data is forthcoming, calling this method
     * will flush the buffered packets.
     */
    this.flush = function() {
      // !!THIS ORDER IS IMPORTANT!!
      // video first then audio
      flushStream(video, 'video');
      flushStream(audio, 'audio');
      flushStream(timedMetadata, 'timed-metadata');
      this.trigger('done');
    };
  };
  ElementaryStream.prototype = new Stream();
  
  var m2ts = {
    PAT_PID: 0x0000,
    MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
    TransportPacketStream: TransportPacketStream,
    TransportParseStream: TransportParseStream,
    ElementaryStream: ElementaryStream,
    TimestampRolloverStream: TimestampRolloverStream,
    CaptionStream: CaptionStream.CaptionStream,
    Cea608Stream: CaptionStream.Cea608Stream,
    MetadataStream: require('./metadata-stream')
  };
  
  for (var type in StreamTypes) {
    if (StreamTypes.hasOwnProperty(type)) {
      m2ts[type] = StreamTypes[type];
    }
  }
  
  module.exports = m2ts;