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.
Showing
2 changed files
with
384 additions
and
45 deletions
... | @@ -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); | ... | ... |
... | @@ -24,7 +24,18 @@ var | ... | @@ -24,7 +24,18 @@ var |
24 | PacketStream = videojs.mp2t.PacketStream, | 24 | PacketStream = videojs.mp2t.PacketStream, |
25 | packetStream, | 25 | packetStream, |
26 | ParseStream = videojs.mp2t.ParseStream, | 26 | ParseStream = videojs.mp2t.ParseStream, |
27 | parseStream; | 27 | parseStream, |
28 | ProgramStream = videojs.mp2t.ProgramStream, | ||
29 | programStream, | ||
30 | |||
31 | MP2T_PACKET_LENGTH = videojs.mp2t.MP2T_PACKET_LENGTH, | ||
32 | H264_STREAM_TYPE = videojs.mp2t.H264_STREAM_TYPE, | ||
33 | ADTS_STREAM_TYPE = videojs.mp2t.ADTS_STREAM_TYPE, | ||
34 | packetize, | ||
35 | |||
36 | PAT, | ||
37 | PMT, | ||
38 | standalonePes; | ||
28 | 39 | ||
29 | module('MP2T Packet Stream', { | 40 | module('MP2T Packet Stream', { |
30 | setup: function() { | 41 | setup: function() { |
... | @@ -91,9 +102,12 @@ test('buffers extra after multiple packets', function() { | ... | @@ -91,9 +102,12 @@ test('buffers extra after multiple packets', function() { |
91 | equal(188, datas[2].length, 'parsed the finel packet'); | 102 | equal(188, datas[2].length, 'parsed the finel packet'); |
92 | }); | 103 | }); |
93 | 104 | ||
94 | module('MP2T Parse Stream', { | 105 | module('MP2T ParseStream', { |
95 | setup: function() { | 106 | setup: function() { |
107 | packetStream = new PacketStream(); | ||
96 | parseStream = new ParseStream(); | 108 | parseStream = new ParseStream(); |
109 | |||
110 | packetStream.pipe(parseStream); | ||
97 | } | 111 | } |
98 | }); | 112 | }); |
99 | 113 | ||
... | @@ -122,6 +136,21 @@ test('parses generic packet properties', function() { | ... | @@ -122,6 +136,21 @@ test('parses generic packet properties', function() { |
122 | ok(packet.pid, 'parsed PID'); | 136 | ok(packet.pid, 'parsed PID'); |
123 | }); | 137 | }); |
124 | 138 | ||
139 | test('parses piped data events', function() { | ||
140 | var packet; | ||
141 | parseStream.on('data', function(data) { | ||
142 | packet = data; | ||
143 | }); | ||
144 | |||
145 | parseStream.push(new Uint8Array([ | ||
146 | 0x47, // sync byte | ||
147 | // tei:0 pusi:1 tp:0 pid:0 0000 0000 0001 tsc:01 afc:10 cc:11 padding: 00 | ||
148 | 0x40, 0x01, 0x6c | ||
149 | ])); | ||
150 | |||
151 | ok(packet, 'parsed a packet'); | ||
152 | }); | ||
153 | |||
125 | test('parses a data packet with adaptation fields', function() { | 154 | test('parses a data packet with adaptation fields', function() { |
126 | var packet; | 155 | var packet; |
127 | parseStream.on('data', function(data) { | 156 | parseStream.on('data', function(data) { |
... | @@ -193,13 +222,7 @@ otherwise: | ... | @@ -193,13 +222,7 @@ otherwise: |
193 | | pn | pn | r pmp:5 | pmp | | 222 | | pn | pn | r pmp:5 | pmp | |
194 | */ | 223 | */ |
195 | 224 | ||
196 | test('parses the program map table pid from the program association table (PAT)', function() { | 225 | PAT = [ |
197 | var packet; | ||
198 | parseStream.on('data', function(data) { | ||
199 | packet = data; | ||
200 | }); | ||
201 | |||
202 | parseStream.push(new Uint8Array([ | ||
203 | 0x47, // sync byte | 226 | 0x47, // sync byte |
204 | // tei:0 pusi:1 tp:0 pid:0 0000 0000 0000 | 227 | // tei:0 pusi:1 tp:0 pid:0 0000 0000 0000 |
205 | 0x40, 0x00, | 228 | 0x40, 0x00, |
... | @@ -217,19 +240,20 @@ test('parses the program map table pid from the program association table (PAT)' | ... | @@ -217,19 +240,20 @@ test('parses the program map table pid from the program association table (PAT)' |
217 | 0x00, 0x10, | 240 | 0x00, 0x10, |
218 | // crc32:0000 0000 0000 0000 0000 0000 0000 0000 | 241 | // crc32:0000 0000 0000 0000 0000 0000 0000 0000 |
219 | 0x00, 0x00, 0x00, 0x00 | 242 | 0x00, 0x00, 0x00, 0x00 |
220 | ])); | 243 | ]; |
221 | ok(packet, 'parsed a packet'); | ||
222 | strictEqual(0x0010, parseStream.pmtPid, 'parsed PMT pid'); | ||
223 | }); | ||
224 | 244 | ||
225 | test('parse the elementary streams from a program map table', function() { | 245 | test('parses the program map table pid from the program association table (PAT)', function() { |
226 | var packet; | 246 | var packet; |
227 | parseStream.on('data', function(data) { | 247 | parseStream.on('data', function(data) { |
228 | packet = data; | 248 | packet = data; |
229 | }); | 249 | }); |
230 | parseStream.pmtPid = 0x0010; | ||
231 | 250 | ||
232 | parseStream.push(new Uint8Array([ | 251 | parseStream.push(new Uint8Array(PAT)); |
252 | ok(packet, 'parsed a packet'); | ||
253 | strictEqual(0x0010, parseStream.pmtPid, 'parsed PMT pid'); | ||
254 | }); | ||
255 | |||
256 | PMT = [ | ||
233 | 0x47, // sync byte | 257 | 0x47, // sync byte |
234 | // tei:0 pusi:1 tp:0 pid:0 0000 0010 0000 | 258 | // tei:0 pusi:1 tp:0 pid:0 0000 0010 0000 |
235 | 0x40, 0x10, | 259 | 0x40, 0x10, |
... | @@ -257,7 +281,16 @@ test('parse the elementary streams from a program map table', function() { | ... | @@ -257,7 +281,16 @@ test('parse the elementary streams from a program map table', function() { |
257 | 0x00, 0x00, | 281 | 0x00, 0x00, |
258 | // crc | 282 | // crc |
259 | 0x00, 0x00, 0x00, 0x00 | 283 | 0x00, 0x00, 0x00, 0x00 |
260 | ])); | 284 | ]; |
285 | |||
286 | test('parse the elementary streams from a program map table', function() { | ||
287 | var packet; | ||
288 | parseStream.on('data', function(data) { | ||
289 | packet = data; | ||
290 | }); | ||
291 | parseStream.pmtPid = 0x0010; | ||
292 | |||
293 | parseStream.push(new Uint8Array(PMT)); | ||
261 | 294 | ||
262 | ok(packet, 'parsed a packet'); | 295 | ok(packet, 'parsed a packet'); |
263 | ok(parseStream.programMapTable, 'parsed a program map'); | 296 | ok(parseStream.programMapTable, 'parsed a program map'); |
... | @@ -351,23 +384,44 @@ test('parses an elementary stream packet with a pts and dts', function() { | ... | @@ -351,23 +384,44 @@ test('parses an elementary stream packet with a pts and dts', function() { |
351 | equal(2 / 90, packet.dts, 'parsed the dts'); | 384 | equal(2 / 90, packet.dts, 'parsed the dts'); |
352 | }); | 385 | }); |
353 | 386 | ||
354 | test('parses an elementary stream packet without a pts or dts', function() { | 387 | standalonePes = [ |
355 | |||
356 | var packet; | ||
357 | parseStream.on('data', function(data) { | ||
358 | packet = data; | ||
359 | }); | ||
360 | |||
361 | parseStream.programMapTable = { | ||
362 | 0x11: 0x1b // pid 0x11 is h264 data | ||
363 | }; | ||
364 | |||
365 | parseStream.push(new Uint8Array([ | ||
366 | 0x47, // sync byte | 388 | 0x47, // sync byte |
367 | // tei:0 pusi:1 tp:0 pid:0 0000 0001 0001 | 389 | // tei:0 pusi:1 tp:0 pid:0 0000 0001 0001 |
368 | 0x40, 0x11, | 390 | 0x40, 0x11, |
369 | // tsc:01 afc:01 cc:0000 | 391 | // tsc:01 afc:11 cc:0000 |
370 | 0x50, | 392 | 0x70, |
393 | // afl:1010 1100 | ||
394 | 0xac, | ||
395 | // di:0 rai:0 espi:0 pf:0 of:0 spf:0 tpdf:0 afef:0 | ||
396 | 0x00, | ||
397 | // stuffing_bytes (171 bytes) | ||
398 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
399 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
400 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
401 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
402 | |||
403 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
404 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
405 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
406 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
407 | |||
408 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
409 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
410 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
411 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
412 | |||
413 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
414 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
415 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
416 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
417 | |||
418 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
419 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
420 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
421 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
422 | |||
423 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
424 | 0xff, 0xff, 0xff, | ||
371 | // pscp:0000 0000 0000 0000 0000 0001 | 425 | // pscp:0000 0000 0000 0000 0000 0001 |
372 | 0x00, 0x00, 0x01, | 426 | 0x00, 0x00, 0x01, |
373 | // sid:0000 0000 ppl:0000 0000 0000 0101 | 427 | // sid:0000 0000 ppl:0000 0000 0000 0101 |
... | @@ -380,7 +434,21 @@ test('parses an elementary stream packet without a pts or dts', function() { | ... | @@ -380,7 +434,21 @@ test('parses an elementary stream packet without a pts or dts', function() { |
380 | 0x00, | 434 | 0x00, |
381 | // "data":1010 1111 0000 0001 | 435 | // "data":1010 1111 0000 0001 |
382 | 0xaf, 0x01 | 436 | 0xaf, 0x01 |
383 | ])); | 437 | ]; |
438 | |||
439 | test('parses an elementary stream packet without a pts or dts', function() { | ||
440 | |||
441 | var packet; | ||
442 | parseStream.on('data', function(data) { | ||
443 | packet = data; | ||
444 | }); | ||
445 | |||
446 | // pid 0x11 is h264 data | ||
447 | parseStream.programMapTable = { | ||
448 | 0x11: H264_STREAM_TYPE | ||
449 | }; | ||
450 | |||
451 | parseStream.push(new Uint8Array(standalonePes)); | ||
384 | 452 | ||
385 | ok(packet, 'parsed a packet'); | 453 | ok(packet, 'parsed a packet'); |
386 | equal('pes', packet.type, 'recognized a PES packet'); | 454 | equal('pes', packet.type, 'recognized a PES packet'); |
... | @@ -392,4 +460,153 @@ test('parses an elementary stream packet without a pts or dts', function() { | ... | @@ -392,4 +460,153 @@ test('parses an elementary stream packet without a pts or dts', function() { |
392 | ok(!packet.dts, 'did not parse a dts'); | 460 | ok(!packet.dts, 'did not parse a dts'); |
393 | }); | 461 | }); |
394 | 462 | ||
463 | module('MP2T ProgramStream', { | ||
464 | setup: function() { | ||
465 | programStream = new ProgramStream(); | ||
466 | } | ||
467 | }); | ||
468 | |||
469 | packetize = function(data) { | ||
470 | var packet = new Uint8Array(MP2T_PACKET_LENGTH); | ||
471 | packet.set(data); | ||
472 | return packet; | ||
473 | }; | ||
474 | |||
475 | test('parses metadata events from PSI packets', function() { | ||
476 | var metadatas = 0, datas = 0; | ||
477 | programStream.on('data', function(data) { | ||
478 | if (data.type === 'pat' || data.type === 'pmt') { | ||
479 | metadatas++; | ||
480 | } | ||
481 | datas++; | ||
482 | }); | ||
483 | programStream.push({ | ||
484 | type: 'pat' | ||
485 | }); | ||
486 | programStream.push({ | ||
487 | type: 'pmt' | ||
488 | }); | ||
489 | |||
490 | equal(2, datas, 'data fired'); | ||
491 | equal(2, metadatas, 'metadata generated'); | ||
492 | }); | ||
493 | |||
494 | test('parses standalone program stream packets', function() { | ||
495 | var packets = []; | ||
496 | programStream.on('data', function(packet) { | ||
497 | packets.push(packet); | ||
498 | }); | ||
499 | programStream.push({ | ||
500 | type: 'pes', | ||
501 | data: new Uint8Array(19) | ||
502 | }); | ||
503 | programStream.end(); | ||
504 | |||
505 | equal(1, packets.length, 'built one packet'); | ||
506 | equal('audio', packets[0].type, 'identified audio data'); | ||
507 | equal(19, packets[0].data.byteLength, 'parsed the correct payload size'); | ||
508 | }); | ||
509 | |||
510 | test('aggregates program stream packets from the transport stream', function() { | ||
511 | var events = []; | ||
512 | programStream.on('data', function(event) { | ||
513 | events.push(event); | ||
514 | }); | ||
515 | |||
516 | programStream.push({ | ||
517 | type: 'pes', | ||
518 | streamType: H264_STREAM_TYPE, | ||
519 | data: new Uint8Array(7) | ||
520 | }); | ||
521 | equal(0, events.length, 'buffers partial packets'); | ||
522 | |||
523 | programStream.push({ | ||
524 | type: 'pes', | ||
525 | streamType: H264_STREAM_TYPE, | ||
526 | data: new Uint8Array(13) | ||
527 | }); | ||
528 | programStream.end(); | ||
529 | equal(1, events.length, 'built one packet'); | ||
530 | equal('video', events[0].type, 'identified video data'); | ||
531 | equal(20, events[0].data.byteLength, 'concatenated transport packets'); | ||
532 | }); | ||
533 | |||
534 | test('buffers audio and video program streams individually', function() { | ||
535 | var events = []; | ||
536 | programStream.on('data', function(event) { | ||
537 | events.push(event); | ||
538 | }); | ||
539 | |||
540 | programStream.push({ | ||
541 | type: 'pes', | ||
542 | payloadUnitStartIndicator: true, | ||
543 | streamType: H264_STREAM_TYPE, | ||
544 | data: new Uint8Array(1) | ||
545 | }); | ||
546 | programStream.push({ | ||
547 | type: 'pes', | ||
548 | payloadUnitStartIndicator: true, | ||
549 | streamType: ADTS_STREAM_TYPE, | ||
550 | data: new Uint8Array(1) | ||
551 | }); | ||
552 | equal(0, events.length, 'buffers partial packets'); | ||
553 | |||
554 | programStream.push({ | ||
555 | type: 'pes', | ||
556 | streamType: H264_STREAM_TYPE, | ||
557 | data: new Uint8Array(1) | ||
558 | }); | ||
559 | programStream.push({ | ||
560 | type: 'pes', | ||
561 | streamType: ADTS_STREAM_TYPE, | ||
562 | data: new Uint8Array(1) | ||
563 | }); | ||
564 | programStream.end(); | ||
565 | equal(2, events.length, 'parsed a complete packet'); | ||
566 | equal('video', events[0].type, 'identified video data'); | ||
567 | equal('audio', events[1].type, 'identified audio data'); | ||
568 | }); | ||
569 | |||
570 | test('flushes the buffered packets when a new one of that type is started', function() { | ||
571 | var packets = []; | ||
572 | programStream.on('data', function(packet) { | ||
573 | packets.push(packet); | ||
574 | }); | ||
575 | programStream.push({ | ||
576 | type: 'pes', | ||
577 | payloadUnitStartIndicator: true, | ||
578 | streamType: H264_STREAM_TYPE, | ||
579 | data: new Uint8Array(1) | ||
580 | }); | ||
581 | programStream.push({ | ||
582 | type: 'pes', | ||
583 | payloadUnitStartIndicator: true, | ||
584 | streamType: ADTS_STREAM_TYPE, | ||
585 | data: new Uint8Array(7) | ||
586 | }); | ||
587 | programStream.push({ | ||
588 | type: 'pes', | ||
589 | streamType: H264_STREAM_TYPE, | ||
590 | data: new Uint8Array(1) | ||
591 | }); | ||
592 | equal(0, packets.length, 'buffers packets by type'); | ||
593 | |||
594 | programStream.push({ | ||
595 | type: 'pes', | ||
596 | payloadUnitStartIndicator: true, | ||
597 | streamType: H264_STREAM_TYPE, | ||
598 | data: new Uint8Array(1) | ||
599 | }); | ||
600 | equal(1, packets.length, 'built one packet'); | ||
601 | equal('video', packets[0].type, 'identified video data'); | ||
602 | equal(2, packets[0].data.byteLength, 'concatenated packets'); | ||
603 | |||
604 | programStream.end(); | ||
605 | equal(3, packets.length, 'built tow more packets'); | ||
606 | equal('video', packets[1].type, 'identified video data'); | ||
607 | equal(1, packets[1].data.byteLength, 'parsed the video payload'); | ||
608 | equal('audio', packets[2].type, 'identified audio data'); | ||
609 | equal(7, packets[2].data.byteLength, 'parsed the audio payload'); | ||
610 | }); | ||
611 | |||
395 | })(window, window.videojs); | 612 | })(window, window.videojs); | ... | ... |
-
Please register or sign in to post a comment