b2ed4313 by David LaPalomento

HLS -> FLV translation working. Fairly extensive FLV validation testing.

Fix up a couple remaining issues with the HLS->FLV translation. At this point, we've validated that the generated file can be played back in VLC if you download it to your computer. Added another ts segment for testing purposes. Added unit testing that traverses the generated FLV and validates the tags are constructed correctly and seem consistent.
1 parent 7a41ceb5
...@@ -45,8 +45,12 @@ ...@@ -45,8 +45,12 @@
45 <script src="src/h264-stream.js"></script> 45 <script src="src/h264-stream.js"></script>
46 <script src="src/aac-stream.js"></script> 46 <script src="src/aac-stream.js"></script>
47 <script src="src/segment-parser.js"></script> 47 <script src="src/segment-parser.js"></script>
48 <!-- an example MPEG2-TS segment --> 48
49 <script src="test/tsSegment.js"></script> 49 <!-- example MPEG2-TS segments -->
50 <!-- bipbop -->
51 <!-- <script src="test/tsSegment.js"></script> -->
52 <!-- bunnies -->
53 <script src="test/tsSegment-bc.js"></script>
50 54
51 </head> 55 </head>
52 <body> 56 <body>
...@@ -67,20 +71,31 @@ ...@@ -67,20 +71,31 @@
67 mediaSource = new videojs.MediaSource(); 71 mediaSource = new videojs.MediaSource();
68 72
69 mediaSource.addEventListener('sourceopen', function(event){ 73 mediaSource.addEventListener('sourceopen', function(event){
70 var tag, bytes, parser; 74 var tag, bytes, parser, i, feedBytes, everything, old;
71 75
72 // feed parsed bytes into the player 76 // feed parsed bytes into the player
73 var sourceBuffer = mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'); 77 var sourceBuffer = mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"');
74 parser = new videojs.hls.SegmentParser(); 78 parser = new videojs.hls.SegmentParser();
75 var header = parser.getFlvHeader(); 79 // var header = parser.getFlvHeader();
76 sourceBuffer.appendBuffer(header); 80 everything = parser.getFlvHeader();
81 // sourceBuffer.appendBuffer(header);
82
83 parser.parseSegmentBinaryData(window.bcSegment);
77 84
78 parser.parseSegmentBinaryData(window.testSegment);
79 while (parser.tagsAvailable()) { 85 while (parser.tagsAvailable()) {
80 tag = parser.getNextTag(); 86 tag = parser.getNextTag();
81 console.log('sending bytes', tag.length); 87 old = everything;
82 sourceBuffer.appendBuffer(tag.bytes.subarray(0, tag.length), video); 88 everything = new Uint8Array(old.byteLength + tag.bytes.byteLength);
89 everything.set(old);
90 everything.set(tag.bytes, old.byteLength);
83 } 91 }
92 console.log('sending ' + everything.byteLength + 'B');
93 var iframe = document.createElement('iframe');
94 iframe.src = 'data:video/x-flv;base64,' + window.btoa((Array.prototype.map.call(everything, function(byte) {
95 return String.fromCharCode(byte);
96 })).join(''));
97 document.body.appendChild(iframe);
98 // sourceBuffer.appendBuffer(everything, video);
84 }, false); 99 }, false);
85 100
86 url = videojs.URL.createObjectURL(mediaSource); 101 url = videojs.URL.createObjectURL(mediaSource);
......
1 <!doctype html>
2 <html>
3 <head><title></title></head>
4 <body>
5 <!-- Start of Brightcove Player -->
6
7 <div style="display:none">
8
9 </div>
10
11 <!--
12 By use of this code snippet, I agree to the Brightcove Publisher T and C
13 found at https://accounts.brightcove.com/en/terms-and-conditions/.
14 -->
15
16 <script language="JavaScript" type="text/javascript" src="http://admin.brightcove.com/js/BrightcoveExperiences.js"></script>
17
18 <object id="myExperience791331688001" class="BrightcoveExperience">
19 <param name="bgcolor" value="#FFFFFF" />
20 <param name="width" value="500" />
21 <param name="height" value="470" />
22 <param name="playerID" value="2498485115001" />
23 <param name="playerKey" value="AQ~~,AAAAdgygTQk~,cL85eN46vuSRabEOn8tmRIgEmJahevxf" />
24 <param name="isVid" value="true" />
25 <param name="isUI" value="true" />
26 <param name="dynamicStreaming" value="true" />
27
28 <param name="@videoPlayer" value="791331688001" />
29 </object>
30
31 <!--
32 This script tag will cause the Brightcove Players defined above it to be created as soon
33 as the line is read by the browser. If you wish to have the player instantiated only after
34 the rest of the HTML is processed and the page load is complete, remove the line.
35 -->
36 <script type="text/javascript">brightcove.createExperiences();</script>
37
38 <!-- End of Brightcove Player -->
39 </body>
40 </html>
...@@ -53,8 +53,8 @@ window.videojs.hls.AacStream = function() { ...@@ -53,8 +53,8 @@ window.videojs.hls.AacStream = function() {
53 } 53 }
54 }; 54 };
55 55
56 // (pData:ByteArray, o:int = 0, l:int = 0):void 56 // (data:ByteArray, o:int = 0, l:int = 0):void
57 this.writeBytes = function(pData, o, l) { 57 this.writeBytes = function(data, o, l) {
58 var 58 var
59 e, // :int 59 e, // :int
60 newExtraData, // :uint 60 newExtraData, // :uint
...@@ -64,11 +64,11 @@ window.videojs.hls.AacStream = function() { ...@@ -64,11 +64,11 @@ window.videojs.hls.AacStream = function() {
64 o = o || 0; 64 o = o || 0;
65 l = l || 0; 65 l = l || 0;
66 66
67 // Do not allow more that 'pes_length' bytes to be written 67 // Do not allow more than 'pes_length' bytes to be written
68 l = (pes_length < l ? pes_length : l); 68 l = (pes_length < l ? pes_length : l);
69 pes_length -= l; 69 pes_length -= l;
70 e = o + l; 70 e = o + l;
71 while(o < e) { 71 while (o < e) {
72 switch (state) { 72 switch (state) {
73 default: 73 default:
74 state = 0; 74 state = 0;
...@@ -77,8 +77,8 @@ window.videojs.hls.AacStream = function() { ...@@ -77,8 +77,8 @@ window.videojs.hls.AacStream = function() {
77 if (o >= e) { 77 if (o >= e) {
78 return; 78 return;
79 } 79 }
80 if (0xFF !== pData[o]) { 80 if (0xFF !== data[o]) {
81 console.log("Error no ATDS header found"); 81 console.assert(false, 'Error no ATDS header found');
82 o += 1; 82 o += 1;
83 state = 0; 83 state = 0;
84 return; 84 return;
...@@ -91,14 +91,14 @@ window.videojs.hls.AacStream = function() { ...@@ -91,14 +91,14 @@ window.videojs.hls.AacStream = function() {
91 if (o >= e) { 91 if (o >= e) {
92 return; 92 return;
93 } 93 }
94 if (0xF0 !== (pData[o] & 0xF0)) { 94 if (0xF0 !== (data[o] & 0xF0)) {
95 console.log("Error no ATDS header found"); 95 console.assert(false, 'Error no ATDS header found');
96 o +=1; 96 o +=1;
97 state = 0; 97 state = 0;
98 return; 98 return;
99 } 99 }
100 100
101 adtsProtectionAbsent = !!(pData[o] & 0x01); 101 adtsProtectionAbsent = !!(data[o] & 0x01);
102 102
103 o += 1; 103 o += 1;
104 state = 2; 104 state = 2;
...@@ -107,9 +107,9 @@ window.videojs.hls.AacStream = function() { ...@@ -107,9 +107,9 @@ window.videojs.hls.AacStream = function() {
107 if (o >= e) { 107 if (o >= e) {
108 return; 108 return;
109 } 109 }
110 adtsObjectType = ((pData[o] & 0xC0) >>> 6) + 1; 110 adtsObjectType = ((data[o] & 0xC0) >>> 6) + 1;
111 adtsSampleingIndex = ((pData[o] & 0x3C) >>> 2); 111 adtsSampleingIndex = ((data[o] & 0x3C) >>> 2);
112 adtsChanelConfig = ((pData[o] & 0x01) << 2); 112 adtsChanelConfig = ((data[o] & 0x01) << 2);
113 113
114 o += 1; 114 o += 1;
115 state = 3; 115 state = 3;
...@@ -118,8 +118,8 @@ window.videojs.hls.AacStream = function() { ...@@ -118,8 +118,8 @@ window.videojs.hls.AacStream = function() {
118 if (o >= e) { 118 if (o >= e) {
119 return; 119 return;
120 } 120 }
121 adtsChanelConfig |= ((pData[o] & 0xC0) >>> 6); 121 adtsChanelConfig |= ((data[o] & 0xC0) >>> 6);
122 adtsFrameSize = ((pData[o] & 0x03) << 11); 122 adtsFrameSize = ((data[o] & 0x03) << 11);
123 123
124 o += 1; 124 o += 1;
125 state = 4; 125 state = 4;
...@@ -128,7 +128,7 @@ window.videojs.hls.AacStream = function() { ...@@ -128,7 +128,7 @@ window.videojs.hls.AacStream = function() {
128 if (o >= e) { 128 if (o >= e) {
129 return; 129 return;
130 } 130 }
131 adtsFrameSize |= (pData[o] << 3); 131 adtsFrameSize |= (data[o] << 3);
132 132
133 o += 1; 133 o += 1;
134 state = 5; 134 state = 5;
...@@ -137,7 +137,7 @@ window.videojs.hls.AacStream = function() { ...@@ -137,7 +137,7 @@ window.videojs.hls.AacStream = function() {
137 if(o >= e) { 137 if(o >= e) {
138 return; 138 return;
139 } 139 }
140 adtsFrameSize |= ((pData[o] & 0xE0) >>> 5); 140 adtsFrameSize |= ((data[o] & 0xE0) >>> 5);
141 adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9); 141 adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9);
142 142
143 o += 1; 143 o += 1;
...@@ -147,7 +147,7 @@ window.videojs.hls.AacStream = function() { ...@@ -147,7 +147,7 @@ window.videojs.hls.AacStream = function() {
147 if (o >= e) { 147 if (o >= e) {
148 return; 148 return;
149 } 149 }
150 adtsSampleCount = ((pData[o] & 0x03) + 1) * 1024; 150 adtsSampleCount = ((data[o] & 0x03) + 1) * 1024;
151 adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex]; 151 adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex];
152 152
153 newExtraData = (adtsObjectType << 11) | 153 newExtraData = (adtsObjectType << 11) |
...@@ -176,6 +176,7 @@ window.videojs.hls.AacStream = function() { ...@@ -176,6 +176,7 @@ window.videojs.hls.AacStream = function() {
176 aacFrame.pts = next_pts; 176 aacFrame.pts = next_pts;
177 aacFrame.view.setUint16(aacFrame.position, newExtraData); 177 aacFrame.view.setUint16(aacFrame.position, newExtraData);
178 aacFrame.position += 2; 178 aacFrame.position += 2;
179 aacFrame.length = Math.max(aacFrame.length, aacFrame.position);
179 180
180 this.tags.push(aacFrame); 181 this.tags.push(aacFrame);
181 } 182 }
...@@ -204,7 +205,7 @@ window.videojs.hls.AacStream = function() { ...@@ -204,7 +205,7 @@ window.videojs.hls.AacStream = function() {
204 return; 205 return;
205 } 206 }
206 bytesToCopy = (e - o) < adtsFrameSize ? (e - o) : adtsFrameSize; 207 bytesToCopy = (e - o) < adtsFrameSize ? (e - o) : adtsFrameSize;
207 aacFrame.writeBytes( pData, o, bytesToCopy ); 208 aacFrame.writeBytes(data, o, bytesToCopy);
208 o += bytesToCopy; 209 o += bytesToCopy;
209 adtsFrameSize -= bytesToCopy; 210 adtsFrameSize -= bytesToCopy;
210 } 211 }
......
1 (function(window) {
2 var module = {
3 hexDump: function(data) {
4 var
5 bytes = Array.prototype.slice.call(data),
6 step = 16,
7 hex,
8 ascii;
9 for (var j = 0; j < bytes.length / step; j++) {
10 hex = bytes.slice(j * step, j * step + step).map(function(e) {
11 var value = e.toString(16);
12 return "00".substring(0, 2 - value.length) + value;
13 }).join(' ');
14 ascii = bytes.slice(j * step, j * step + step).map(function(e) {
15 if (e > 32 && e < 125) {
16 return String.fromCharCode(e);
17 }
18 return '.';
19 }).join('');
20 return hex + ' ' + ascii;
21 }
22 },
23 tagDump: function(tag) {
24 return module.hexDump(tag.bytes);
25 }
26 };
27
28 window.videojs.hls.utils = module;
29 })(this);
...@@ -136,7 +136,8 @@ hls.FlvTag = function(type, extraData) { ...@@ -136,7 +136,8 @@ hls.FlvTag = function(type, extraData) {
136 this.view.setUint8(this.position, 0x00); 136 this.view.setUint8(this.position, 0x00);
137 this.position++; 137 this.position++;
138 this.view.setFloat64(this.position, val); 138 this.view.setFloat64(this.position, val);
139 this.position += 2; 139 this.position += 8;
140 this.length = Math.max(this.length, this.position);
140 ++adHoc; 141 ++adHoc;
141 }; 142 };
142 143
...@@ -154,6 +155,7 @@ hls.FlvTag = function(type, extraData) { ...@@ -154,6 +155,7 @@ hls.FlvTag = function(type, extraData) {
154 this.position++; 155 this.position++;
155 this.view.setUint8(this.position, val ? 0x01 : 0x00); 156 this.view.setUint8(this.position, val ? 0x01 : 0x00);
156 this.position++; 157 this.position++;
158 this.length = Math.max(this.length, this.position);
157 ++adHoc; 159 ++adHoc;
158 }; 160 };
159 161
...@@ -176,7 +178,7 @@ hls.FlvTag = function(type, extraData) { ...@@ -176,7 +178,7 @@ hls.FlvTag = function(type, extraData) {
176 break; 178 break;
177 179
178 case hls.FlvTag.AUDIO_TAG: 180 case hls.FlvTag.AUDIO_TAG:
179 this.bytes[11] = 0xAF; 181 this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo
180 this.bytes[12] = extraData ? 0x00 : 0x01; 182 this.bytes[12] = extraData ? 0x00 : 0x01;
181 break; 183 break;
182 184
...@@ -191,26 +193,27 @@ hls.FlvTag = function(type, extraData) { ...@@ -191,26 +193,27 @@ hls.FlvTag = function(type, extraData) {
191 0x74, 0x61, 0x44, 0x61, 193 0x74, 0x61, 0x44, 0x61,
192 0x74, 0x61], this.position); 194 0x74, 0x61], this.position);
193 this.position += 10; 195 this.position += 10;
194 this.view.setUint8(this.position, 196 this.bytes[this.position] = 0x08; // Array type
195 this.bytes[this.position] | 0x08); // Array type
196 this.position++; 197 this.position++;
197 this.view.setUint32(this.position, adHoc); 198 this.view.setUint32(this.position, adHoc);
198 this.position = this.length; 199 this.position = this.length;
199 this.view.setUint32(this.position, 0x09); // End Data Tag 200 this.bytes.set([0, 0, 9], this.position);
200 this.position += 4; 201 this.position += 3;
202 // this.view.setUint32(this.position, 0x09); // End Data Tag
203 // this.position += 4;
201 this.length = this.position; 204 this.length = this.position;
202 break; 205 break;
203 } 206 }
204 207
205 len = this.length - 11; 208 len = this.length - 11;
206 209
207 this.bytes[ 1] = ( len & 0x00FF0000 ) >>> 16; 210 this.bytes[ 1] = (len & 0x00FF0000) >>> 16;
208 this.bytes[ 2] = ( len & 0x0000FF00 ) >>> 8; 211 this.bytes[ 2] = (len & 0x0000FF00) >>> 8;
209 this.bytes[ 3] = ( len & 0x000000FF ) >>> 0; 212 this.bytes[ 3] = (len & 0x000000FF) >>> 0;
210 this.bytes[ 4] = ( this.pts & 0x00FF0000 ) >>> 16; 213 this.bytes[ 4] = (this.pts & 0x00FF0000) >>> 16;
211 this.bytes[ 5] = ( this.pts & 0x0000FF00 ) >>> 8; 214 this.bytes[ 5] = (this.pts & 0x0000FF00) >>> 8;
212 this.bytes[ 6] = ( this.pts & 0x000000FF ) >>> 0; 215 this.bytes[ 6] = (this.pts & 0x000000FF) >>> 0;
213 this.bytes[ 7] = ( this.pts & 0xFF000000 ) >>> 24; 216 this.bytes[ 7] = (this.pts & 0xFF000000) >>> 24;
214 this.bytes[ 8] = 0; 217 this.bytes[ 8] = 0;
215 this.bytes[ 9] = 0; 218 this.bytes[ 9] = 0;
216 this.bytes[10] = 0; 219 this.bytes[10] = 0;
...@@ -218,6 +221,11 @@ hls.FlvTag = function(type, extraData) { ...@@ -218,6 +221,11 @@ hls.FlvTag = function(type, extraData) {
218 this.view.setUint32(this.length, this.length); 221 this.view.setUint32(this.length, this.length);
219 this.length += 4; 222 this.length += 4;
220 this.position += 4; 223 this.position += 4;
224
225 // trim down the byte buffer to what is actually being used
226 this.bytes = this.bytes.subarray(0, this.length);
227 console.assert(this.bytes.byteLength === this.length);
228
221 return this; 229 return this;
222 }; 230 };
223 }; 231 };
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
17 this.pps = []; // :Array 17 this.pps = []; // :Array
18 18
19 this.addSPS = function(size) { // :ByteArray 19 this.addSPS = function(size) { // :ByteArray
20 console.log('come on, you fucker');
21 console.assert(size > 0); 20 console.assert(size > 0);
22 var tmp = new Uint8Array(size); // :ByteArray 21 var tmp = new Uint8Array(size); // :ByteArray
23 this.sps.push(tmp); 22 this.sps.push(tmp);
...@@ -261,8 +260,6 @@ ...@@ -261,8 +260,6 @@
261 260
262 window.videojs.hls.H264Stream = function() { 261 window.videojs.hls.H264Stream = function() {
263 var 262 var
264 tags = [],
265
266 next_pts, // :uint; 263 next_pts, // :uint;
267 next_dts, // :uint; 264 next_dts, // :uint;
268 pts_delta = -1, // :int 265 pts_delta = -1, // :int
...@@ -307,12 +304,12 @@ ...@@ -307,12 +304,12 @@
307 304
308 if (h264Frame.keyFrame) { 305 if (h264Frame.keyFrame) {
309 // Push extra data on every IDR frame in case we did a stream change + seek 306 // Push extra data on every IDR frame in case we did a stream change + seek
310 tags.push(oldExtraData.metaDataTag(h264Frame.pts)); 307 this.tags.push(oldExtraData.metaDataTag(h264Frame.pts));
311 tags.push(oldExtraData.extraDataTag(h264Frame.pts)); 308 this.tags.push(oldExtraData.extraDataTag(h264Frame.pts));
312 } 309 }
313 310
314 h264Frame.endNalUnit(); 311 h264Frame.endNalUnit();
315 tags.push(h264Frame); 312 this.tags.push(h264Frame);
316 } 313 }
317 314
318 h264Frame = null; 315 h264Frame = null;
......
...@@ -386,5 +386,14 @@ ...@@ -386,5 +386,14 @@
386 386
387 return true; 387 return true;
388 }; 388 };
389
390 self.stats = {
391 h264Tags: function() {
392 return h264Stream.tags.length;
393 },
394 aacTags: function() {
395 return aacStream.tags.length;
396 }
397 };
389 }; 398 };
390 })(this); 399 })(this);
......
No preview for this file type
No preview for this file type
No preview for this file type
This diff could not be displayed because it is too large.
...@@ -45,7 +45,10 @@ ...@@ -45,7 +45,10 @@
45 <script src="../src/aac-stream.js"></script> 45 <script src="../src/aac-stream.js"></script>
46 <script src="../src/segment-parser.js"></script> 46 <script src="../src/segment-parser.js"></script>
47 <!-- an example MPEG2-TS segment --> 47 <!-- an example MPEG2-TS segment -->
48 <script src="tsSegment.js"></script> 48 <!-- <script src="tsSegment.js"></script> -->
49 <script src="tsSegment-bc.js"></script>
50
51 <script src="../src/bin-utils.js"></script>
49 52
50 <script src="video-js-hls_test.js"></script> 53 <script src="video-js-hls_test.js"></script>
51 </head> 54 </head>
......
...@@ -25,7 +25,13 @@ ...@@ -25,7 +25,13 @@
25 expectedHeader = [ 25 expectedHeader = [
26 0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 26 0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00,
27 0x09, 0x00, 0x00, 0x00, 0x00 27 0x09, 0x00, 0x00, 0x00, 0x00
28 ]; 28 ],
29 testAudioTag,
30 testVideoTag,
31 testScriptTag,
32 asciiFromBytes,
33 testScriptString,
34 testScriptEcmaArray;
29 35
30 module('environment'); 36 module('environment');
31 37
...@@ -44,17 +50,175 @@ ...@@ -44,17 +50,175 @@
44 var header = Array.prototype.slice.call(parser.getFlvHeader()); 50 var header = Array.prototype.slice.call(parser.getFlvHeader());
45 ok(header, 'the header is truthy'); 51 ok(header, 'the header is truthy');
46 equal(9 + 4, header.length, 'the header length is correct'); 52 equal(9 + 4, header.length, 'the header length is correct');
47 equal(header[0], 'F'.charCodeAt(0), 'the signature is correct'); 53 equal(header[0], 'F'.charCodeAt(0), 'the first character is "F"');
48 equal(header[1], 'L'.charCodeAt(0), 'the signature is correct'); 54 equal(header[1], 'L'.charCodeAt(0), 'the second character is "L"');
49 equal(header[2], 'V'.charCodeAt(0), 'the signature is correct'); 55 equal(header[2], 'V'.charCodeAt(0), 'the third character is "V"');
50 56
51 deepEqual(expectedHeader, header, 'the rest of the header is correct'); 57 deepEqual(expectedHeader, header, 'the rest of the header is correct');
52 }); 58 });
53 59
54 test('parses the first bipbop segment', function() { 60 test('parses the first bipbop segment', function() {
55 var tag, bytes; 61 var tag, bytes, i;
56 parser.parseSegmentBinaryData(window.testSegment); 62 parser.parseSegmentBinaryData(window.bcSegment);
57 63
58 ok(parser.tagsAvailable(), 'tags are available'); 64 ok(parser.tagsAvailable(), 'tags are available');
65
66 console.log('h264 tags:', parser.stats.h264Tags(),
67 'aac tags:', parser.stats.aacTags());
68
69 console.log(videojs.hls.utils.hexDump(parser.getFlvHeader()));
70 for (i = 0; i < 4; ++i) {
71 parser.getNextTag();
72 }
73 console.log(videojs.hls.utils.tagDump(parser.getNextTag()));
74 console.log('bad tag:');
75 for (i = 0; i < 3; ++i) {
76 console.log(videojs.hls.utils.tagDump(parser.getNextTag()));
77 }
78 });
79
80 testAudioTag = function(tag) {
81 var
82 byte = tag.bytes[11],
83 format = (byte & 0xF0) >>> 4,
84 soundRate = byte & 0x03,
85 soundSize = (byte & 0x2) >>> 1,
86 soundType = byte & 0x1,
87 aacPacketType = tag.bytes[12];
88
89 equal(10, format, 'the audio format is aac');
90 equal(3, soundRate, 'the sound rate is 44kHhz');
91 equal(1, soundSize, 'the sound size is 16-bit samples');
92 equal(1, soundType, 'the sound type is stereo');
93
94 ok(aacPacketType === 0 || aacPacketType === 1, 'aac packets should have a valid type');
95 };
96
97 testVideoTag = function(tag) {
98 var
99 byte = tag.bytes[11],
100 frameType = (byte & 0xF0) >>> 4,
101 codecId = byte & 0x0F,
102 packetType = tag.bytes[12],
103 compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8;
104
105 console.log(frameType);
106
107 // XXX: I'm not sure that frame types 3-5 are invalid
108 ok(frameType === 1 || frameType === 2,
109 'the frame type should be valid');
110
111 equal(7, codecId, 'the codec ID is AVC for h264');
112 ok(packetType <=2 && packetType >= 0, 'the packet type is within [0, 2]');
113 if (packetType !== 1) {
114 equal(0,
115 compositionTime,
116 'the composition time is zero for non-NALU packets');
117 }
118
119 // TODO: the rest of the bytes are an NLU unit
120 };
121
122 asciiFromBytes = function(bytes) {
123 var
124 string = [],
125 i = bytes.byteLength;
126
127 while (i--) {
128 string[i] = String.fromCharCode(bytes[i]);
129 }
130 return string.join('');
131 };
132
133 testScriptString = function(tag, offset, expected) {
134 var type = tag.bytes[offset],
135 stringLength = tag.view.getUint16(offset + 1),
136 string,
137 i = expected.length;
138
139 equal(2, type, 'the script element is of string type');
140 equal(stringLength, expected.length, 'the script string length is correct');
141 string = asciiFromBytes(tag.bytes.subarray(offset + 3,
142 offset + 3 + stringLength));
143 equal(expected, string, 'the string value is "' + expected + '"');
144 };
145
146 testScriptEcmaArray = function(tag, start) {
147 var
148 numItems = tag.view.getUint32(start),
149 i = numItems,
150 offset = start + 4,
151 length,
152 type;
153
154 while (i--) {
155 length = tag.view.getUint16(offset);
156
157 // advance offset to the property value
158 offset += 2 + length;
159
160 type = tag.bytes[offset];
161 ok(type === 1 || type === 0,
162 'the ecma array property value type is number or boolean');
163 offset++;
164 if (type) {
165 // boolean
166 ok(tag.bytes[offset] === 0 || tag.bytes[offset] === 1,
167 'the script boolean value is 0 or 1');
168 offset++;
169 } else {
170 // number
171 offset += 8;
172 }
173 }
174 equal(tag.bytes[offset], 0, 'the property array terminator is valid');
175 equal(tag.bytes[offset + 1], 0, 'the property array terminator is valid');
176 equal(tag.bytes[offset + 2], 9, 'the property array terminator is valid');
177 };
178
179 testScriptTag = function(tag) {
180 testScriptString(tag, 11, 'onMetaData');
181
182 // the onMetaData object is stored as an 'ecma array', an array with non-
183 // integer indices (i.e. a dictionary or hash-map).
184 equal(8, tag.bytes[24], 'onMetaData is of ecma array type');
185 testScriptEcmaArray(tag, 25);
186 };
187
188 test('the flv tags are well-formed', function() {
189 var
190 tag,
191 byte,
192 type,
193 lastTime = 0;
194 parser.parseSegmentBinaryData(window.bcSegment);
195
196 while (parser.tagsAvailable()) {
197 tag = parser.getNextTag();
198 type = tag.bytes[0];
199
200 // generic flv headers
201 ok(type === 8 || type === 9 || type === 18,
202 'the type field specifies audio, video or script');
203
204 byte = (tag.view.getUint32(1) & 0xFFFFFF00) >>> 8;
205 equal(tag.bytes.byteLength - 11 - 4, byte, 'the size field is correct');
206
207 byte = tag.view.getUint32(5) & 0xFFFFFF00;
208 ok(byte >= lastTime, 'the timestamp for the tag is greater than zero');
209 lastTime = byte;
210
211 // tag type-specific headers
212 ({
213 8: testAudioTag,
214 9: testVideoTag,
215 18: testScriptTag
216 })[type](tag);
217
218 // previous tag size
219 equal(tag.bytes.byteLength - 4,
220 tag.view.getUint32(tag.bytes.byteLength - 4),
221 'the size of the previous tag is correct');
222 }
59 }); 223 });
60 })(this); 224 })(this);
......