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.
Showing
13 changed files
with
318 additions
and
52 deletions
... | @@ -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); | ... | ... |
hls-example.html
0 → 100644
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 | } | ... | ... |
src/bin-utils.js
0 → 100644
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); | ... | ... |
test/bc0.ts
0 → 100644
No preview for this file type
test/bipbop1.flv
0 → 100644
No preview for this file type
test/bipbop1.ts
0 → 100644
No preview for this file type
test/tsSegment-bc.js
0 → 100644
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); | ... | ... |
-
Please register or sign in to post a comment