bed2b294 by David LaPalomento

Parse program stream features out of transport streams

Add a program stream parser that aggregates program stream packets and re-emits them when the entire payload is available. Fix the test PES value to accurately use stuffing bytes. Update mp4 muxer page to exercise the TS parsing infrastructure.
1 parent 92b03b90
...@@ -14,9 +14,12 @@ ...@@ -14,9 +14,12 @@
14 (function(window, videojs, undefined) { 14 (function(window, videojs, undefined) {
15 'use strict'; 15 'use strict';
16 16
17 var PacketStream, ParseStream, MP2T_PACKET_LENGTH; 17 var PacketStream, ParseStream, ProgramStream, Transmuxer, MP2T_PACKET_LENGTH, H264_STREAM_TYPE, ADTS_STREAM_TYPE, mp4;
18 18
19 MP2T_PACKET_LENGTH = 188; // bytes 19 MP2T_PACKET_LENGTH = 188; // bytes
20 H264_STREAM_TYPE = 0x1b;
21 ADTS_STREAM_TYPE = 0x0f;
22 mp4 = videojs.mp4;
20 23
21 /** 24 /**
22 * Splits an incoming stream of binary data into MP2T packets. 25 * Splits an incoming stream of binary data into MP2T packets.
...@@ -78,7 +81,7 @@ PacketStream.prototype = new videojs.Hls.Stream(); ...@@ -78,7 +81,7 @@ PacketStream.prototype = new videojs.Hls.Stream();
78 */ 81 */
79 ParseStream = function() { 82 ParseStream = function() {
80 var parsePsi, parsePat, parsePmt, parsePes, self; 83 var parsePsi, parsePat, parsePmt, parsePes, self;
81 PacketStream.prototype.init.call(this); 84 ParseStream.prototype.init.call(this);
82 self = this; 85 self = this;
83 86
84 this.programMapTable = {}; 87 this.programMapTable = {};
...@@ -160,11 +163,12 @@ ParseStream = function() { ...@@ -160,11 +163,12 @@ ParseStream = function() {
160 }; 163 };
161 164
162 parsePes = function(payload, pes) { 165 parsePes = function(payload, pes) {
163 var ptsDtsFlags, 166 var ptsDtsFlags;
164 pesLength;
165 167
166 // PES packet length 168 if (!pes.payloadUnitStartIndicator) {
167 pesLength = payload[4] << 8 | payload[5]; 169 pes.data = payload;
170 return;
171 }
168 172
169 // find out if this packets starts a new keyframe 173 // find out if this packets starts a new keyframe
170 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; 174 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0;
...@@ -224,10 +228,11 @@ ParseStream = function() { ...@@ -224,10 +228,11 @@ ParseStream = function() {
224 result.pid <<= 8; 228 result.pid <<= 8;
225 result.pid |= packet[2]; 229 result.pid |= packet[2];
226 230
227 // if an adaption field is present, its length is specified by 231 // if an adaption field is present, its length is specified by the
228 // the fifth byte of the PES header. The adaptation field is 232 // fifth byte of the TS packet header. The adaptation field is
229 // used to specify some forms of timing and control data that we 233 // used to add stuffing to PES packets that don't fill a complete
230 // do not currently use. 234 // TS packet, and to specify some forms of timing and control data
235 // that we do not currently use.
231 if (((packet[3] & 0x30) >>> 4) > 0x01) { 236 if (((packet[3] & 0x30) >>> 4) > 0x01) {
232 offset += packet[offset] + 1; 237 offset += packet[offset] + 1;
233 } 238 }
...@@ -254,12 +259,129 @@ ParseStream.STREAM_TYPES = { ...@@ -254,12 +259,129 @@ ParseStream.STREAM_TYPES = {
254 adts: 0x0f 259 adts: 0x0f
255 }; 260 };
256 261
262 ProgramStream = function() {
263 var
264 // PES packet fragments
265 video = {
266 data: [],
267 size: 0
268 },
269 audio = {
270 data: [],
271 size: 0
272 },
273 flushStream = function(stream, type) {
274 var
275 event = {
276 type: type,
277 data: new Uint8Array(stream.size)
278 },
279 i = 0,
280 fragment;
281
282 // do nothing if there is no buffered data
283 if (!stream.data.length) {
284 return;
285 }
286
287 // reassemble the packet
288 while (stream.data.length) {
289 fragment = stream.data.shift();
290
291 event.data.set(fragment.data, i);
292 i += fragment.data.byteLength;
293 }
294 stream.size = 0;
295
296 self.trigger('data', event);
297 },
298 self;
299
300 ProgramStream.prototype.init.call(this);
301 self = this;
302
303 this.push = function(data) {
304 ({
305 pat: function() {
306 self.trigger('data', data);
307 },
308 pes: function() {
309 var stream, streamType;
310
311 if (data.streamType === H264_STREAM_TYPE) {
312 stream = video;
313 streamType = 'video';
314 } else {
315 stream = audio;
316 streamType = 'audio';
317 }
318
319 // if a new packet is starting, we can flush the completed
320 // packet
321 if (data.payloadUnitStartIndicator) {
322 flushStream(stream, streamType);
323 }
324
325 // buffer this fragment until we are sure we've received the
326 // complete payload
327 stream.data.push(data);
328 stream.size += data.data.byteLength;
329 },
330 pmt: function() {
331 self.trigger('data', data);
332 }
333 })[data.type]();
334 };
335
336 /**
337 * Flush any remaining input. Video PES packets may be of variable
338 * length. Normally, the start of a new video packet can trigger the
339 * finalization of the previous packet. That is not possible if no
340 * more video is forthcoming, however. In that case, some other
341 * mechanism (like the end of the file) has to be employed. When it is
342 * clear that no additional data is forthcoming, calling this method
343 * will flush the buffered packets.
344 */
345 this.end = function() {
346 flushStream(video, 'video');
347 flushStream(audio, 'audio');
348 };
349 };
350 ProgramStream.prototype = new videojs.Hls.Stream();
351
352 Transmuxer = function() {
353 var self = this, packetStream, parseStream, programStream;
354 Transmuxer.prototype.init.call(this);
355
356 // set up the parsing pipeline
357 packetStream = new PacketStream();
358 parseStream = new ParseStream();
359 programStream = new ProgramStream();
360
361 packetStream.pipe(parseStream);
362 parseStream.pipe(programStream);
363
364 programStream.on('data', function(data) {
365 self.trigger('data', data);
366 });
367
368 // feed incoming data to the front of the parsing pipeline
369 this.push = function(data) {
370 packetStream.push(data);
371 };
372 // flush any buffered data
373 this.end = programStream.end;
374 };
375 Transmuxer.prototype = new videojs.Hls.Stream();
376
257 window.videojs.mp2t = { 377 window.videojs.mp2t = {
258 PAT_PID: 0x0000, 378 PAT_PID: 0x0000,
259 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH, 379 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
260 H264_STREAM_TYPE: 0x1b, 380 H264_STREAM_TYPE: H264_STREAM_TYPE,
261 ADTS_STREAM_TYPE: 0x0f, 381 ADTS_STREAM_TYPE: ADTS_STREAM_TYPE,
262 PacketStream: PacketStream, 382 PacketStream: PacketStream,
263 ParseStream: ParseStream 383 ParseStream: ParseStream,
384 ProgramStream: ProgramStream,
385 Transmuxer: Transmuxer
264 }; 386 };
265 })(window, window.videojs); 387 })(window, window.videojs);
......