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
135 additions
and
13 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); | ... | ... |
This diff is collapsed.
Click to expand it.
-
Please register or sign in to post a comment