4b96b9ca by David LaPalomento

Merge pull request #8 from dlapalomento/feature/perf-fixes

Grow FLV tags as necessary and make some performance tweaks
2 parents 64f30e95 961e0073
...@@ -46,7 +46,7 @@ module.exports = function(grunt) { ...@@ -46,7 +46,7 @@ module.exports = function(grunt) {
46 }, 46 },
47 }, 47 },
48 qunit: { 48 qunit: {
49 files: ['test/**/*.html'] 49 files: ['test/**/*.html', '!test/perf.html']
50 }, 50 },
51 jshint: { 51 jshint: {
52 gruntfile: { 52 gruntfile: {
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
52 videojs.options.flash.swf = 'node_modules/videojs-media-sources/video-js-with-mse.swf'; 52 videojs.options.flash.swf = 'node_modules/videojs-media-sources/video-js-with-mse.swf';
53 video = videojs('video',{},function(){ 53 video = videojs('video',{},function(){
54 this.playbackController = new window.videojs.hls.HLSPlaybackController(this); 54 this.playbackController = new window.videojs.hls.HLSPlaybackController(this);
55 this.playbackController.loadManifest('http://localhost:7070/test/basic-playback/zencoder/gogo/manifest.m3u8', function(data) { 55 this.playbackController.loadManifest('test/fixtures/prog_index.m3u8', function(data) {
56 console.log(data); 56 console.log(data);
57 }); 57 });
58 }); 58 });
......
1 (function(window) { 1 (function(window) {
2 2
3 var hls = window.videojs.hls; 3 var
4 hls = window.videojs.hls,
5
6 // commonly used metadata properties
7 widthBytes = new Uint8Array('width'.length),
8 heightBytes = new Uint8Array('height'.length),
9 videocodecidBytes = new Uint8Array('videocodecid'.length),
10 i;
11
12 // calculating the bytes of common metadata names ahead of time makes the
13 // corresponding writes faster because we don't have to loop over the
14 // characters
15 // re-test with test/perf.html if you're planning on changing this
16 for (i in 'width') {
17 widthBytes[i] = 'width'.charCodeAt(i);
18 }
19 for (i in 'height') {
20 heightBytes[i] = 'height'.charCodeAt(i);
21 }
22 for (i in 'videocodecid') {
23 videocodecidBytes[i] = 'videocodecid'.charCodeAt(i);
24 }
4 25
5 // (type:uint, extraData:Boolean = false) extends ByteArray 26 // (type:uint, extraData:Boolean = false) extends ByteArray
6 hls.FlvTag = function(type, extraData) { 27 hls.FlvTag = function(type, extraData) {
7 var 28 var
8 // Counter if this is a metadata tag, nal start marker if this is a video 29 // Counter if this is a metadata tag, nal start marker if this is a video
9 // tag. unused if this is an audio tag 30 // tag. unused if this is an audio tag
10 adHoc = 0; // :uint 31 adHoc = 0, // :uint
32
33 // checks whether the FLV tag has enough capacity to accept the proposed
34 // write and re-allocates the internal buffers if necessary
35 prepareWrite = function(flv, count) {
36 var
37 bytes,
38 minLength = flv.position + count;
39 if (minLength < flv.bytes.byteLength) {
40 // there's enough capacity so do nothing
41 return;
42 }
43
44 // allocate a new buffer and copy over the data that will not be modified
45 bytes = new Uint8Array(minLength * 2);
46 bytes.set(flv.bytes.subarray(0, flv.position), 0);
47 flv.bytes = bytes;
48 flv.view = new DataView(flv.bytes.buffer);
49 };
11 50
12 this.keyFrame = false; // :Boolean 51 this.keyFrame = false; // :Boolean
13 52
...@@ -27,7 +66,6 @@ hls.FlvTag = function(type, extraData) { ...@@ -27,7 +66,6 @@ hls.FlvTag = function(type, extraData) {
27 throw("Error Unknown TagType"); 66 throw("Error Unknown TagType");
28 } 67 }
29 68
30 // XXX: I have no idea if 16k is enough to buffer arbitrary FLV tags
31 this.bytes = new Uint8Array(16384); 69 this.bytes = new Uint8Array(16384);
32 this.view = new DataView(this.bytes.buffer); 70 this.view = new DataView(this.bytes.buffer);
33 this.bytes[0] = type; 71 this.bytes[0] = type;
...@@ -37,24 +75,26 @@ hls.FlvTag = function(type, extraData) { ...@@ -37,24 +75,26 @@ hls.FlvTag = function(type, extraData) {
37 // presentation timestamp 75 // presentation timestamp
38 this.pts = 0; 76 this.pts = 0;
39 // decoder timestamp 77 // decoder timestamp
40 this.dts = 0; 78 this.dts = 0;
41 79
42 // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0) 80 // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0)
43 this.writeBytes = function(bytes, offset, length) { 81 this.writeBytes = function(bytes, offset, length) {
44 offset = offset || 0; 82 var
83 start = offset || 0,
84 end;
45 length = length || bytes.byteLength; 85 length = length || bytes.byteLength;
86 end = start + length;
87
88 prepareWrite(this, length);
89 this.bytes.set(bytes.subarray(start, end), this.position);
46 90
47 try {
48 this.bytes.set(bytes.subarray(offset, offset + length), this.position);
49 } catch(e) {
50 throw e;
51 }
52 this.position += length; 91 this.position += length;
53 this.length = Math.max(this.length, this.position); 92 this.length = Math.max(this.length, this.position);
54 }; 93 };
55 94
56 // ByteArray#writeByte(value:int):void 95 // ByteArray#writeByte(value:int):void
57 this.writeByte = function(byte) { 96 this.writeByte = function(byte) {
97 prepareWrite(this, 1);
58 this.bytes[this.position] = byte; 98 this.bytes[this.position] = byte;
59 this.position++; 99 this.position++;
60 this.length = Math.max(this.length, this.position); 100 this.length = Math.max(this.length, this.position);
...@@ -62,6 +102,7 @@ hls.FlvTag = function(type, extraData) { ...@@ -62,6 +102,7 @@ hls.FlvTag = function(type, extraData) {
62 102
63 // ByteArray#writeShort(value:int):void 103 // ByteArray#writeShort(value:int):void
64 this.writeShort = function(short) { 104 this.writeShort = function(short) {
105 prepareWrite(this, 2);
65 this.view.setUint16(this.position, short); 106 this.view.setUint16(this.position, short);
66 this.position += 2; 107 this.position += 2;
67 this.length = Math.max(this.length, this.position); 108 this.length = Math.max(this.length, this.position);
...@@ -80,7 +121,7 @@ hls.FlvTag = function(type, extraData) { ...@@ -80,7 +121,7 @@ hls.FlvTag = function(type, extraData) {
80 if (adHoc === 0) { 121 if (adHoc === 0) {
81 return 0; 122 return 0;
82 } 123 }
83 124
84 return this.length - (adHoc + 4); 125 return this.length - (adHoc + 4);
85 }; 126 };
86 127
...@@ -115,7 +156,7 @@ hls.FlvTag = function(type, extraData) { ...@@ -115,7 +156,7 @@ hls.FlvTag = function(type, extraData) {
115 this.position = this.length; 156 this.position = this.length;
116 157
117 if (nalContainer) { 158 if (nalContainer) {
118 // Add the tag to the NAL unit 159 // Add the tag to the NAL unit
119 nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength)); 160 nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength));
120 } 161 }
121 } 162 }
...@@ -123,20 +164,47 @@ hls.FlvTag = function(type, extraData) { ...@@ -123,20 +164,47 @@ hls.FlvTag = function(type, extraData) {
123 adHoc = 0; 164 adHoc = 0;
124 }; 165 };
125 166
167 /**
168 * Write out a 64-bit floating point valued metadata property. This method is
169 * called frequently during a typical parse and needs to be fast.
170 */
126 // (key:String, val:Number):void 171 // (key:String, val:Number):void
127 this.writeMetaDataDouble = function(key, val) { 172 this.writeMetaDataDouble = function(key, val) {
128 var i; 173 var i;
174 prepareWrite(this, 2 + key.length + 9);
175
176 // write size of property name
129 this.view.setUint16(this.position, key.length); 177 this.view.setUint16(this.position, key.length);
130 this.position += 2; 178 this.position += 2;
131 for (i in key) { 179
132 console.assert(key.charCodeAt(i) < 255); 180 // this next part looks terrible but it improves parser throughput by
133 this.bytes[this.position] = key.charCodeAt(i); 181 // 10kB/s in my testing
134 this.position++; 182
183 // write property name
184 if (key === 'width') {
185 this.bytes.set(widthBytes, this.position);
186 this.position += 5;
187 } else if (key === 'height') {
188 this.bytes.set(heightBytes, this.position);
189 this.position += 6;
190 } else if (key === 'videocodecid') {
191 this.bytes.set(videocodecidBytes, this.position);
192 this.position += 12;
193 } else {
194 for (i in key) {
195 this.bytes[this.position] = key.charCodeAt(i);
196 this.position++;
197 }
135 } 198 }
136 this.view.setUint8(this.position, 0x00); 199
200 // skip null byte
137 this.position++; 201 this.position++;
202
203 // write property value
138 this.view.setFloat64(this.position, val); 204 this.view.setFloat64(this.position, val);
139 this.position += 8; 205 this.position += 8;
206
207 // update flv tag length
140 this.length = Math.max(this.length, this.position); 208 this.length = Math.max(this.length, this.position);
141 ++adHoc; 209 ++adHoc;
142 }; 210 };
...@@ -144,13 +212,16 @@ hls.FlvTag = function(type, extraData) { ...@@ -144,13 +212,16 @@ hls.FlvTag = function(type, extraData) {
144 // (key:String, val:Boolean):void 212 // (key:String, val:Boolean):void
145 this.writeMetaDataBoolean = function(key, val) { 213 this.writeMetaDataBoolean = function(key, val) {
146 var i; 214 var i;
215 prepareWrite(this, 2);
147 this.view.setUint16(this.position, key.length); 216 this.view.setUint16(this.position, key.length);
148 this.position += 2; 217 this.position += 2;
149 for (i in key) { 218 for (i in key) {
150 console.assert(key.charCodeAt(i) < 255); 219 console.assert(key.charCodeAt(i) < 255);
220 prepareWrite(this, 1);
151 this.bytes[this.position] = key.charCodeAt(i); 221 this.bytes[this.position] = key.charCodeAt(i);
152 this.position++; 222 this.position++;
153 } 223 }
224 prepareWrite(this, 2);
154 this.view.setUint8(this.position, 0x01); 225 this.view.setUint8(this.position, 0x01);
155 this.position++; 226 this.position++;
156 this.view.setUint8(this.position, val ? 0x01 : 0x00); 227 this.view.setUint8(this.position, val ? 0x01 : 0x00);
...@@ -197,10 +268,8 @@ hls.FlvTag = function(type, extraData) { ...@@ -197,10 +268,8 @@ hls.FlvTag = function(type, extraData) {
197 this.position++; 268 this.position++;
198 this.view.setUint32(this.position, adHoc); 269 this.view.setUint32(this.position, adHoc);
199 this.position = this.length; 270 this.position = this.length;
200 // this.bytes.set([0, 0, 9], this.position); 271 this.bytes.set([0, 0, 9], this.position);
201 // this.position += 3; 272 this.position += 3; // End Data Tag
202 this.view.setUint32(this.position, 0x09); // End Data Tag
203 this.position += 4;
204 this.length = this.position; 273 this.length = this.position;
205 break; 274 break;
206 } 275 }
...@@ -261,7 +330,7 @@ hls.FlvTag.isKeyFrame = function(tag) { ...@@ -261,7 +330,7 @@ hls.FlvTag.isKeyFrame = function(tag) {
261 if (hls.FlvTag.isAudioFrame(tag)) { 330 if (hls.FlvTag.isAudioFrame(tag)) {
262 return true; 331 return true;
263 } 332 }
264 333
265 if (hls.FlvTag.isMetaData(tag)) { 334 if (hls.FlvTag.isMetaData(tag)) {
266 return true; 335 return true;
267 } 336 }
......
1 /* 1 /*
2 * h264-stream 2 * h264-stream
3 * 3 *
4 * 4 *
5 * Copyright (c) 2013 Brightcove 5 * Copyright (c) 2013 Brightcove
6 * All rights reserved. 6 * All rights reserved.
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
62 * NAL unit: Network abstraction layer unit. The combination of a NAL 62 * NAL unit: Network abstraction layer unit. The combination of a NAL
63 * header and an RBSP. 63 * header and an RBSP.
64 * NAL header: the encapsulation unit for transport-specific metadata in 64 * NAL header: the encapsulation unit for transport-specific metadata in
65 * an h264 stream. 65 * an h264 stream. Exactly one byte.
66 * RBSP: raw bit-stream payload. The actual encoded video data. 66 * RBSP: raw bit-stream payload. The actual encoded video data.
67 * 67 *
68 * SPS: sequence parameter set. Part of the RBSP. Metadata to be applied 68 * SPS: sequence parameter set. Part of the RBSP. Metadata to be applied
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
128 128
129 pic_width_in_mbs_minus1, // :int 129 pic_width_in_mbs_minus1, // :int
130 pic_height_in_map_units_minus1, // :int 130 pic_height_in_map_units_minus1, // :int
131 131
132 frame_mbs_only_flag, // :int 132 frame_mbs_only_flag, // :int
133 frame_cropping_flag, // :Boolean 133 frame_cropping_flag, // :Boolean
134 134
...@@ -283,17 +283,17 @@ ...@@ -283,17 +283,17 @@
283 283
284 oldExtraData = new H264ExtraData(), // :H264ExtraData 284 oldExtraData = new H264ExtraData(), // :H264ExtraData
285 newExtraData = new H264ExtraData(), // :H264ExtraData 285 newExtraData = new H264ExtraData(), // :H264ExtraData
286 286
287 nalUnitType = -1, // :int 287 nalUnitType = -1, // :int
288 288
289 state; // :uint; 289 state; // :uint;
290 290
291 this.tags = []; 291 this.tags = [];
292 292
293 //(pts:uint, dts:uint, dataAligned:Boolean):void 293 //(pts:uint, dts:uint, dataAligned:Boolean):void
294 this.setNextTimeStamp = function(pts, dts, dataAligned) { 294 this.setNextTimeStamp = function(pts, dts, dataAligned) {
295 if (0>pts_delta) { 295 if (0>pts_delta) {
296 // We assume the very first pts is less than 0x8FFFFFFF (max signed 296 // We assume the very first pts is less than 0x8FFFFFFF (max signed
297 // int32) 297 // int32)
298 pts_delta = pts; 298 pts_delta = pts;
299 } 299 }
...@@ -361,38 +361,40 @@ ...@@ -361,38 +361,40 @@
361 // A NAL unit may be split across two TS packets. Look back a bit to 361 // A NAL unit may be split across two TS packets. Look back a bit to
362 // make sure the prefix of the start code wasn't already written out. 362 // make sure the prefix of the start code wasn't already written out.
363 if (data[offset] <= 1) { 363 if (data[offset] <= 1) {
364 nalUnitSize = h264Frame ? h264Frame.nalUnitSize() : 0; 364 nalUnitSize = h264Frame ? h264Frame.nalUnitSize() : 0;
365 if (nalUnitSize >= 1 && h264Frame.negIndex(1) === 0) { 365 if (nalUnitSize >= 1 && h264Frame.negIndex(1) === 0) {
366 // ?? ?? 00 | O[01] ?? ?? 366 // ?? ?? 00 | O[01] ?? ??
367 if (1 === data[offset] && 2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) { 367 if (data[offset] === 1 &&
368 nalUnitSize >= 2 &&
369 h264Frame.negIndex(2) === 0) {
368 // ?? 00 00 : 01 370 // ?? 00 00 : 01
369 if (3 <= nalUnitSize && 0 === h264Frame.negIndex(3)) { 371 if (3 <= nalUnitSize && 0 === h264Frame.negIndex(3)) {
370 h264Frame.length -= 3; // 00 00 00 : 01 372 h264Frame.length -= 3; // 00 00 00 : 01
371 } else { 373 } else {
372 h264Frame.length -= 2; // 00 00 : 01 374 h264Frame.length -= 2; // 00 00 : 01
373 } 375 }
374 376
375 state = 3; 377 state = 3;
376 return this.writeBytes(data, offset + 1, length - 1); 378 return this.writeBytes(data, offset + 1, length - 1);
377 } 379 }
378 380
379 if (1 < length && 0 === data[offset] && 1 === data[offset + 1]) { 381 if (length > 1 && data[offset] === 0 && data[offset + 1] === 1) {
380 // ?? 00 | 00 01 382 // ?? 00 | 00 01
381 if (2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) { 383 if (nalUnitSize >= 2 && h264Frame.negIndex(2) === 0) {
382 h264Frame.length -= 2; // 00 00 : 00 01 384 h264Frame.length -= 2; // 00 00 : 00 01
383 } else { 385 } else {
384 h264Frame.length -= 1; // 00 : 00 01 386 h264Frame.length -= 1; // 00 : 00 01
385 } 387 }
386 388
387 state = 3; 389 state = 3;
388 return this.writeBytes(data, offset + 2, length - 2); 390 return this.writeBytes(data, offset + 2, length - 2);
389 } 391 }
390 392
391 if (2 < length && 393 if (length > 2 &&
392 0 === data[offset] && 394 data[offset] === 0 &&
393 0 === data[offset + 1] && 395 data[offset + 1] === 0 &&
394 1 === data[offset + 2]) { 396 data[offset + 2] === 1) {
395 // 00 | 00 00 01 397 // 00 : 00 00 01
396 h264Frame.length -= 1; 398 h264Frame.length -= 1;
397 state = 3; 399 state = 3;
398 return this.writeBytes(data, offset + 3, length - 3); 400 return this.writeBytes(data, offset + 3, length - 3);
...@@ -403,19 +405,22 @@ ...@@ -403,19 +405,22 @@
403 // bytes a second time. But that case will be VERY rare 405 // bytes a second time. But that case will be VERY rare
404 state = 2; 406 state = 2;
405 /* falls through */ 407 /* falls through */
406 case 2: // Look for start codes in data 408 case 2:
409 // Look for start codes in the data from the current offset forward
407 start = offset; 410 start = offset;
408 end = start + length; 411 end = start + length;
409 for (t = end - 3 ; offset < t ;) { 412 for (t = end - 3; offset < t;) {
410 if (1 < data[offset + 2]) { 413 if (data[offset + 2] > 1) {
411 offset += 3; // if data[offset + 2] is greater than 1, there is no way a start code can begin before offset+3 414 // if data[offset + 2] is greater than 1, there is no way a start
412 } else if (0 !== data[offset + 1]) { 415 // code can begin before offset + 3
416 offset += 3;
417 } else if (data[offset + 1] !== 0) {
413 offset += 2; 418 offset += 2;
414 } else if (0 !== data[offset]) { 419 } else if (data[offset] !== 0) {
415 offset += 1; 420 offset += 1;
416 } else { 421 } else {
417 // If we get here we have 00 00 00 or 00 00 01 422 // If we get here we have 00 00 00 or 00 00 01
418 if (1 === data[offset + 2]) { 423 if (data[offset + 2] === 1) {
419 if (offset > start) { 424 if (offset > start) {
420 h264Frame.writeBytes(data, start, offset - start); 425 h264Frame.writeBytes(data, start, offset - start);
421 } 426 }
...@@ -424,7 +429,9 @@ ...@@ -424,7 +429,9 @@
424 return this.writeBytes(data, offset, end - offset); 429 return this.writeBytes(data, offset, end - offset);
425 } 430 }
426 431
427 if (end - offset >= 4 && 0 === data[offset + 2] && 1 === data[offset + 3]) { 432 if (end - offset >= 4 &&
433 data[offset + 2] === 0 &&
434 data[offset + 3] === 1) {
428 if (offset > start) { 435 if (offset > start) {
429 h264Frame.writeBytes(data, start, offset - start); 436 h264Frame.writeBytes(data, start, offset - start);
430 } 437 }
...@@ -482,9 +489,10 @@ ...@@ -482,9 +489,10 @@
482 } 489 }
483 490
484 h264Frame.startNalUnit(); 491 h264Frame.startNalUnit();
485 state = 2; // We know there will not be an overlapping start code, so we can skip that test 492 // We know there will not be an overlapping start code, so we can skip
493 // that test
494 state = 2;
486 return this.writeBytes(data, offset, length); 495 return this.writeBytes(data, offset, length);
487 /*--------------------------------------------------------------------------------------------------------------------*/
488 } // switch 496 } // switch
489 }; 497 };
490 }; 498 };
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
39 duration = duration || 0; 39 duration = duration || 0;
40 audio = audio === undefined? true : audio; 40 audio = audio === undefined? true : audio;
41 video = video === undefined? true : video; 41 video = video === undefined? true : video;
42 42
43 // signature 43 // signature
44 head.setUint8(0, 0x46); // 'F' 44 head.setUint8(0, 0x46); // 'F'
45 head.setUint8(1, 0x4c); // 'L' 45 head.setUint8(1, 0x4c); // 'L'
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
47 47
48 // version 48 // version
49 head.setUint8(3, 0x01); 49 head.setUint8(3, 0x01);
50 50
51 // flags 51 // flags
52 head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00)); 52 head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00));
53 53
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
74 74
75 return result; 75 return result;
76 }; 76 };
77 77
78 self.flushTags = function() { 78 self.flushTags = function() {
79 h264Stream.finishFrame(); 79 h264Stream.finishFrame();
80 }; 80 };
...@@ -161,7 +161,7 @@ ...@@ -161,7 +161,7 @@
161 streamBuffer.set(dataSlice, streamBufferByteCount); 161 streamBuffer.set(dataSlice, streamBufferByteCount);
162 162
163 parseTSPacket(streamBuffer); 163 parseTSPacket(streamBuffer);
164 164
165 // reset the buffer 165 // reset the buffer
166 streamBuffer = new Uint8Array(m2tsPacketSize); 166 streamBuffer = new Uint8Array(m2tsPacketSize);
167 streamBufferByteCount = 0; 167 streamBufferByteCount = 0;
...@@ -174,7 +174,7 @@ ...@@ -174,7 +174,7 @@
174 // If there is no sync byte skip forward until we find one 174 // If there is no sync byte skip forward until we find one
175 // TODO if we find a sync byte, look 188 bytes in the future (if 175 // TODO if we find a sync byte, look 188 bytes in the future (if
176 // possible). If there is not a sync byte there, keep looking 176 // possible). If there is not a sync byte there, keep looking
177 dataPosition++; 177 dataPosition++;
178 } 178 }
179 179
180 // base case: not enough data to parse a m2ts packet 180 // base case: not enough data to parse a m2ts packet
...@@ -205,19 +205,18 @@ ...@@ -205,19 +205,18 @@
205 // packet! 205 // packet!
206 parseTSPacket = function(data) { // :ByteArray):Boolean { 206 parseTSPacket = function(data) { // :ByteArray):Boolean {
207 var 207 var
208 s = 0, //:uint 208 offset = 0, // :uint
209 o = s, // :uint 209 end = offset + m2tsPacketSize, // :uint
210 e = o + m2tsPacketSize, // :uint 210
211
212 // Don't look for a sync byte. We handle that in 211 // Don't look for a sync byte. We handle that in
213 // parseSegmentBinaryData() 212 // parseSegmentBinaryData()
214 213
215 // Payload Unit Start Indicator 214 // Payload Unit Start Indicator
216 pusi = !!(data[o + 1] & 0x40), // mask: 0100 0000 215 pusi = !!(data[offset + 1] & 0x40), // mask: 0100 0000
217 216
218 // PacketId 217 // PacketId
219 pid = (data[o + 1] & 0x1F) << 8 | data[o + 2], // mask: 0001 1111 218 pid = (data[offset + 1] & 0x1F) << 8 | data[offset + 2], // mask: 0001 1111
220 afflag = (data[o + 3] & 0x30 ) >>> 4, 219 afflag = (data[offset + 3] & 0x30 ) >>> 4,
221 220
222 aflen, // :uint 221 aflen, // :uint
223 patTableId, // :int 222 patTableId, // :int
...@@ -242,29 +241,29 @@ ...@@ -242,29 +241,29 @@
242 241
243 // Continuity Counter we could use this for sanity check, and 242 // Continuity Counter we could use this for sanity check, and
244 // corrupt stream detection 243 // corrupt stream detection
245 // cc = (data[o + 3] & 0x0F); 244 // cc = (data[offset + 3] & 0x0F);
245
246 // Done with TS header
247 offset += 4;
246 248
247 // Done with TS header
248 o += 4;
249
250 if (afflag > 0x01) { // skip most of the adaption field 249 if (afflag > 0x01) { // skip most of the adaption field
251 aflen = data[o]; 250 aflen = data[offset];
252 o += aflen + 1; 251 offset += aflen + 1;
253 } 252 }
254 253
255 if (0x0000 === pid) { 254 if (0x0000 === pid) {
256 // always test for PMT first! (becuse other variables default to 0) 255 // always test for PMT first! (becuse other variables default to 0)
257 256
258 // if pusi is set we must skip X bytes (PSI pointer field) 257 // if pusi is set we must skip X bytes (PSI pointer field)
259 o += pusi ? 1 + data[o] : 0; 258 offset += pusi ? 1 + data[offset] : 0;
260 patTableId = data[o]; 259 patTableId = data[offset];
261 260
262 console.assert(0x00 === patTableId, 'patTableId should be 0x00'); 261 console.assert(0x00 === patTableId, 'patTableId should be 0x00');
263 262
264 patCurrentNextIndicator = !!(data[o + 5] & 0x01); 263 patCurrentNextIndicator = !!(data[offset + 5] & 0x01);
265 if (patCurrentNextIndicator) { 264 if (patCurrentNextIndicator) {
266 patSectionLength = (data[o + 1] & 0x0F) << 8 | data[o + 2]; 265 patSectionLength = (data[offset + 1] & 0x0F) << 8 | data[offset + 2];
267 o += 8; // skip past PSI header 266 offset += 8; // skip past PSI header
268 267
269 // We currently only support streams with 1 program 268 // We currently only support streams with 1 program
270 patSectionLength = (patSectionLength - 9) / 4; 269 patSectionLength = (patSectionLength - 9) / 4;
...@@ -273,26 +272,26 @@ ...@@ -273,26 +272,26 @@
273 } 272 }
274 273
275 // if we ever support more that 1 program (unlikely) loop over them here 274 // if we ever support more that 1 program (unlikely) loop over them here
276 // var programNumber = data[o + 0] << 8 | data[o + 1]; 275 // var programNumber = data[offset + 0] << 8 | data[offset + 1];
277 // var programId = (data[o+2] & 0x1F) << 8 | data[o + 3]; 276 // var programId = (data[offset+2] & 0x1F) << 8 | data[offset + 3];
278 pmtPid = (data[o + 2] & 0x1F) << 8 | data[o + 3]; 277 pmtPid = (data[offset + 2] & 0x1F) << 8 | data[offset + 3];
279 } 278 }
280 279
281 // We could test the CRC here to detect corruption with extra CPU cost 280 // We could test the CRC here to detect corruption with extra CPU cost
282 } else if (videoPid === pid || audioPid === pid) { 281 } else if (videoPid === pid || audioPid === pid) {
283 if (pusi) { 282 if (pusi) {
284 // comment out for speed 283 // comment out for speed
285 if (0x00 !== data[o + 0] || 0x00 !== data[o + 1] || 0x01 !== data[o + 2]) { 284 if (0x00 !== data[offset + 0] || 0x00 !== data[offset + 1] || 0x01 !== data[offset + 2]) {
286 // look for PES start code 285 // look for PES start code
287 throw new Error("PES did not begin with start code"); 286 throw new Error("PES did not begin with start code");
288 } 287 }
289 288
290 // var sid:int = data[o+3]; // StreamID 289 // var sid:int = data[offset+3]; // StreamID
291 pesPacketSize = (data[o + 4] << 8) | data[o + 5]; 290 pesPacketSize = (data[offset + 4] << 8) | data[offset + 5];
292 dataAlignmentIndicator = !!((data[o + 6] & 0x04) >>> 2); 291 dataAlignmentIndicator = (data[offset + 6] & 0x04) !== 0;
293 ptsDtsIndicator = (data[o + 7] & 0xC0) >>> 6; 292 ptsDtsIndicator = data[offset + 7];
294 pesHeaderLength = data[o + 8]; // TODO sanity check header length 293 pesHeaderLength = data[offset + 8]; // TODO sanity check header length
295 o += 9; // Skip past PES header 294 offset += 9; // Skip past PES header
296 295
297 // PTS and DTS are normially stored as a 33 bit number. 296 // PTS and DTS are normially stored as a 33 bit number.
298 // JavaScript does not have a integer type larger than 32 bit 297 // JavaScript does not have a integer type larger than 32 bit
...@@ -300,26 +299,28 @@ ...@@ -300,26 +299,28 @@
300 // so what we are going to do instead, is drop the least 299 // so what we are going to do instead, is drop the least
301 // significant bit (the same as dividing by two) then we can 300 // significant bit (the same as dividing by two) then we can
302 // divide by 45 (45 * 2 = 90) to get ms. 301 // divide by 45 (45 * 2 = 90) to get ms.
303 if (ptsDtsIndicator & 0x03) { 302 if (ptsDtsIndicator & 0xC0) {
304 pts = (data[o + 0] & 0x0E) << 28 303 // the PTS and DTS are not written out directly. For information on
305 | (data[o + 1] & 0xFF) << 21 304 // how they are encoded, see
306 | (data[o + 2] & 0xFE) << 13 305 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
307 | (data[o + 3] & 0xFF) << 6 306 pts = (data[offset + 0] & 0x0E) << 28
308 | (data[o + 4] & 0xFE) >>> 2; 307 | (data[offset + 1] & 0xFF) << 21
308 | (data[offset + 2] & 0xFE) << 13
309 | (data[offset + 3] & 0xFF) << 6
310 | (data[offset + 4] & 0xFE) >>> 2;
309 pts /= 45; 311 pts /= 45;
310 if (ptsDtsIndicator & 0x01) {// DTS 312 dts = pts;
311 dts = (data[o + 5] & 0x0E ) << 28 313 if (ptsDtsIndicator & 0x40) {// DTS
312 | (data[o + 6] & 0xFF ) << 21 314 dts = (data[offset + 5] & 0x0E ) << 28
313 | (data[o + 7] & 0xFE ) << 13 315 | (data[offset + 6] & 0xFF ) << 21
314 | (data[o + 8] & 0xFF ) << 6 316 | (data[offset + 7] & 0xFE ) << 13
315 | (data[o + 9] & 0xFE ) >>> 2; 317 | (data[offset + 8] & 0xFF ) << 6
318 | (data[offset + 9] & 0xFE ) >>> 2;
316 dts /= 45; 319 dts /= 45;
317 } else {
318 dts = pts;
319 } 320 }
320 } 321 }
321 // Skip past "optional" portion of PTS header 322 // Skip past "optional" portion of PTS header
322 o += pesHeaderLength; 323 offset += pesHeaderLength;
323 324
324 if (videoPid === pid) { 325 if (videoPid === pid) {
325 // Stash this frame for future use. 326 // Stash this frame for future use.
...@@ -336,31 +337,31 @@ ...@@ -336,31 +337,31 @@
336 } 337 }
337 338
338 if (audioPid === pid) { 339 if (audioPid === pid) {
339 aacStream.writeBytes(data, o, e - o); 340 aacStream.writeBytes(data, offset, end - offset);
340 } else if (videoPid === pid) { 341 } else if (videoPid === pid) {
341 h264Stream.writeBytes(data, o, e - o); 342 h264Stream.writeBytes(data, offset, end - offset);
342 } 343 }
343 } else if (pmtPid === pid) { 344 } else if (pmtPid === pid) {
344 // TODO sanity check data[o] 345 // TODO sanity check data[offset]
345 // if pusi is set we must skip X bytes (PSI pointer field) 346 // if pusi is set we must skip X bytes (PSI pointer field)
346 o += (pusi ? 1 + data[o] : 0); 347 offset += (pusi ? 1 + data[offset] : 0);
347 pmtTableId = data[o]; 348 pmtTableId = data[offset];
348 349
349 console.assert(0x02 === pmtTableId); 350 console.assert(0x02 === pmtTableId);
350 351
351 pmtCurrentNextIndicator = !!(data[o + 5] & 0x01); 352 pmtCurrentNextIndicator = !!(data[offset + 5] & 0x01);
352 if (pmtCurrentNextIndicator) { 353 if (pmtCurrentNextIndicator) {
353 audioPid = videoPid = 0; 354 audioPid = videoPid = 0;
354 pmtSectionLength = (data[o + 1] & 0x0F) << 8 | data[o + 2]; 355 pmtSectionLength = (data[offset + 1] & 0x0F) << 8 | data[offset + 2];
355 // skip CRC and PSI data we dont care about 356 // skip CRC and PSI data we dont care about
356 pmtSectionLength -= 13; 357 pmtSectionLength -= 13;
357 358
358 o += 12; // skip past PSI header and some PMT data 359 offset += 12; // skip past PSI header and some PMT data
359 while (0 < pmtSectionLength) { 360 while (0 < pmtSectionLength) {
360 streamType = data[o + 0]; 361 streamType = data[offset + 0];
361 elementaryPID = (data[o + 1] & 0x1F) << 8 | data[o + 2]; 362 elementaryPID = (data[offset + 1] & 0x1F) << 8 | data[offset + 2];
362 ESInfolength = (data[o + 3] & 0x0F) << 8 | data[o + 4]; 363 ESInfolength = (data[offset + 3] & 0x0F) << 8 | data[offset + 4];
363 o += 5 + ESInfolength; 364 offset += 5 + ESInfolength;
364 pmtSectionLength -= 5 + ESInfolength; 365 pmtSectionLength -= 5 + ESInfolength;
365 366
366 if (0x1B === streamType) { 367 if (0x1B === streamType) {
...@@ -398,4 +399,4 @@ ...@@ -398,4 +399,4 @@
398 } 399 }
399 }; 400 };
400 }; 401 };
401 })(this); 402 })(window);
......
...@@ -44,4 +44,17 @@ test('writeShort writes a two byte sequence', function() { ...@@ -44,4 +44,17 @@ test('writeShort writes a two byte sequence', function() {
44 'the value is written'); 44 'the value is written');
45 }); 45 });
46 46
47 test('writeBytes grows the internal byte array dynamically', function() {
48 var
49 tag = new FlvTag(FlvTag.VIDEO_TAG),
50 tooManyBytes = new Uint8Array(tag.bytes.byteLength + 1);
51
52 try {
53 tag.writeBytes(tooManyBytes);
54 ok(true, 'the buffer grew to fit the data');
55 } catch(e) {
56 ok(!e, 'the buffer should grow');
57 }
58 });
59
47 })(this); 60 })(this);
......
1 <!doctype html>
2 <html>
3 <head>
4 <title>MPEG-TS Parser Performance Workbench</title>
5 <!-- video.js -->
6 <script src="../node_modules/video.js/video.dev.js"></script>
7
8 <!-- HLS plugin -->
9 <script src="../src/video-js-hls.js"></script>
10 <script src="../src/flv-tag.js"></script>
11 <script src="../src/exp-golomb.js"></script>
12 <script src="../src/h264-stream.js"></script>
13 <script src="../src/aac-stream.js"></script>
14 <script src="../src/segment-parser.js"></script>
15
16 <!-- MPEG-TS segment -->
17 <script src="tsSegment-bc.js"></script>
18 <style>
19 .desc {
20 background-color: #ddd;
21 border: thin solid #333;
22 padding: 8px;
23 }
24 </style>
25 </head>
26 <body>
27 <p class="desc">Select your number of iterations and then press "Run" to begin parsing MPEG-TS packets into FLV tags. This page can be handy for identifying segment parser performance bottlenecks.</p>
28 <form>
29 <input name="iterations" min="1" type="number" value="1">
30 <button type="sumbit">Run</button>
31 </form>
32 <table>
33 <thead>
34 <th>Iterations</th><th>Time</th><th>MB/second</th>
35 </thead>
36 <tbody class="results"></tbody>
37 </table>
38 <script>
39 var
40 button = document.querySelector('button'),
41 input = document.querySelector('input'),
42 results = document.querySelector('.results'),
43
44 reportResults = function(count, elapsed) {
45 var
46 row = document.createElement('tr'),
47 countCell = document.createElement('td'),
48 elapsedCell = document.createElement('td'),
49 throughputCell = document.createElement('td');
50
51 countCell.innerText = count;
52 elapsedCell.innerText = elapsed;
53 throughputCell.innerText = (((bcSegment.byteLength * count * 1000) / elapsed) / (Math.pow(2, 20))).toFixed(3);
54 row.appendChild(countCell);
55 row.appendChild(elapsedCell);
56 row.appendChild(throughputCell);
57
58 results.insertBefore(row, results.firstChild);
59 };
60
61 button.addEventListener('click', function(event) {
62 var
63 iterations = input.value,
64 parser = new window.videojs.hls.SegmentParser(),
65 start;
66
67 // setup
68 start = +new Date();
69
70 while (iterations--) {
71
72 // parse the segment
73 parser.parseSegmentBinaryData(window.bcSegment);
74
75 // finalize all the FLV tags
76 while (parser.tagsAvailable()) {
77 parser.getNextTag();
78 }
79 }
80
81 // report
82 reportResults(input.value, (+new Date()) - start);
83
84 // don't actually submit the form
85 event.preventDefault();
86 }, false);
87 </script>
88 </body>
89 </html>