Fix loadWord in ExpGolomb and code cleanup
loadWord wasn't incrementing the position in the byte stream so fix it so that the accounting is taken care of. Replace a bunch of single-letter variable names with more descriptive terms. Add some comments. Add tests for exponential golomb parsing.
Showing
7 changed files
with
232 additions
and
105 deletions
1 | (function(window) { | 1 | (function(window) { |
2 | 2 | ||
3 | /** | ||
4 | * Parser for exponential Golomb codes, a variable-bitwidth number encoding | ||
5 | * scheme used by h264. | ||
6 | */ | ||
3 | window.videojs.hls.ExpGolomb = function(workingData) { | 7 | window.videojs.hls.ExpGolomb = function(workingData) { |
4 | var | 8 | var |
5 | // the number of bytes left to examine in workingData | 9 | // the number of bytes left to examine in workingData |
6 | workingBytesAvailable = workingData.byteLength, | 10 | workingBytesAvailable = workingData.byteLength, |
7 | 11 | ||
8 | // the current word being examined | 12 | // the current word being examined |
9 | workingWord, // :uint | 13 | workingWord = 0, // :uint |
10 | 14 | ||
11 | // the number of bits left to examine in the current word | 15 | // the number of bits left to examine in the current word |
12 | workingBitsAvailable; // :uint; | 16 | workingBitsAvailable = 0; // :uint; |
13 | 17 | ||
14 | // ():uint | 18 | // ():uint |
15 | this.length = function() { | 19 | this.length = function() { |
... | @@ -21,15 +25,24 @@ window.videojs.hls.ExpGolomb = function(workingData) { | ... | @@ -21,15 +25,24 @@ window.videojs.hls.ExpGolomb = function(workingData) { |
21 | return (8 * workingBytesAvailable) + workingBitsAvailable; | 25 | return (8 * workingBytesAvailable) + workingBitsAvailable; |
22 | }; | 26 | }; |
23 | 27 | ||
28 | this.logStuff = function() { | ||
29 | console.log('bits', workingBitsAvailable, 'word', (workingWord >>> 0)); | ||
30 | }; | ||
31 | |||
24 | // ():void | 32 | // ():void |
25 | this.loadWord = function() { | 33 | this.loadWord = function() { |
26 | var | 34 | var |
35 | position = workingData.byteLength - workingBytesAvailable, | ||
27 | workingBytes = new Uint8Array(4), | 36 | workingBytes = new Uint8Array(4), |
28 | availableBytes = Math.min(4, workingBytesAvailable); | 37 | availableBytes = Math.min(4, workingBytesAvailable); |
29 | 38 | ||
30 | // console.assert(availableBytes > 0); | 39 | // console.assert(availableBytes > 0); |
40 | if (availableBytes === 0) { | ||
41 | throw new Error('no bytes available'); | ||
42 | } | ||
31 | 43 | ||
32 | workingBytes.set(workingData.subarray(0, availableBytes)); | 44 | workingBytes.set(workingData.subarray(position, |
45 | position + availableBytes)); | ||
33 | workingWord = new DataView(workingBytes.buffer).getUint32(0); | 46 | workingWord = new DataView(workingBytes.buffer).getUint32(0); |
34 | 47 | ||
35 | // track the amount of workingData that has been processed | 48 | // track the amount of workingData that has been processed |
... | @@ -37,23 +50,23 @@ window.videojs.hls.ExpGolomb = function(workingData) { | ... | @@ -37,23 +50,23 @@ window.videojs.hls.ExpGolomb = function(workingData) { |
37 | workingBytesAvailable -= availableBytes; | 50 | workingBytesAvailable -= availableBytes; |
38 | }; | 51 | }; |
39 | 52 | ||
40 | // (size:int):void | 53 | // (count:int):void |
41 | this.skipBits = function(size) { | 54 | this.skipBits = function(count) { |
42 | var skipBytes; // :int | 55 | var skipBytes; // :int |
43 | if (workingBitsAvailable > size) { | 56 | if (workingBitsAvailable > count) { |
44 | workingWord <<= size; | 57 | workingWord <<= count; |
45 | workingBitsAvailable -= size; | 58 | workingBitsAvailable -= count; |
46 | } else { | 59 | } else { |
47 | size -= workingBitsAvailable; | 60 | count -= workingBitsAvailable; |
48 | skipBytes = size / 8; | 61 | skipBytes = count / 8; |
49 | 62 | ||
50 | size -= (skipBytes * 8); | 63 | count -= (skipBytes * 8); |
51 | workingData.position += skipBytes; | 64 | workingBytesAvailable -= skipBytes; |
52 | 65 | ||
53 | this.loadWord(); | 66 | this.loadWord(); |
54 | 67 | ||
55 | workingWord <<= size; | 68 | workingWord <<= count; |
56 | workingBitsAvailable -= size; | 69 | workingBitsAvailable -= count; |
57 | } | 70 | } |
58 | }; | 71 | }; |
59 | 72 | ||
... | @@ -63,17 +76,17 @@ window.videojs.hls.ExpGolomb = function(workingData) { | ... | @@ -63,17 +76,17 @@ window.videojs.hls.ExpGolomb = function(workingData) { |
63 | bits = Math.min(workingBitsAvailable, size), // :uint | 76 | bits = Math.min(workingBitsAvailable, size), // :uint |
64 | valu = workingWord >>> (32 - bits); // :uint | 77 | valu = workingWord >>> (32 - bits); // :uint |
65 | 78 | ||
66 | console.assert(32 > size, 'Cannot read more than 32 bits at a time'); | 79 | console.assert(size < 32, 'Cannot read more than 32 bits at a time'); |
67 | 80 | ||
68 | workingBitsAvailable -= bits; | 81 | workingBitsAvailable -= bits; |
69 | if (0 < workingBitsAvailable) { | 82 | if (workingBitsAvailable > 0) { |
70 | workingWord <<= bits; | 83 | workingWord <<= bits; |
71 | } else { | 84 | } else if (workingBytesAvailable > 0) { |
72 | this.loadWord(); | 85 | this.loadWord(); |
73 | } | 86 | } |
74 | 87 | ||
75 | bits = size - bits; | 88 | bits = size - bits; |
76 | if (0 < bits) { | 89 | if (bits > 0) { |
77 | return valu << bits | this.readBits(bits); | 90 | return valu << bits | this.readBits(bits); |
78 | } else { | 91 | } else { |
79 | return valu; | 92 | return valu; |
... | @@ -82,18 +95,19 @@ window.videojs.hls.ExpGolomb = function(workingData) { | ... | @@ -82,18 +95,19 @@ window.videojs.hls.ExpGolomb = function(workingData) { |
82 | 95 | ||
83 | // ():uint | 96 | // ():uint |
84 | this.skipLeadingZeros = function() { | 97 | this.skipLeadingZeros = function() { |
85 | var clz; // :uint | 98 | var leadingZeroCount; // :uint |
86 | for (clz = 0 ; clz < workingBitsAvailable ; ++clz) { | 99 | for (leadingZeroCount = 0 ; leadingZeroCount < workingBitsAvailable ; ++leadingZeroCount) { |
87 | if (0 !== (workingWord & (0x80000000 >>> clz))) { | 100 | if (0 !== (workingWord & (0x80000000 >>> leadingZeroCount))) { |
88 | workingWord <<= clz; | 101 | // the first bit of working word is 1 |
89 | workingBitsAvailable -= clz; | 102 | workingWord <<= leadingZeroCount; |
90 | return clz; | 103 | workingBitsAvailable -= leadingZeroCount; |
104 | return leadingZeroCount; | ||
91 | } | 105 | } |
92 | } | 106 | } |
93 | 107 | ||
94 | // we exhausted workingWord and still have not found a 1 | 108 | // we exhausted workingWord and still have not found a 1 |
95 | this.loadWord(); | 109 | this.loadWord(); |
96 | return clz + this.skipLeadingZeros(); | 110 | return leadingZeroCount + this.skipLeadingZeros(); |
97 | }; | 111 | }; |
98 | 112 | ||
99 | // ():void | 113 | // ():void | ... | ... |
... | @@ -105,7 +105,8 @@ hls.FlvTag = function(type, extraData) { | ... | @@ -105,7 +105,8 @@ hls.FlvTag = function(type, extraData) { |
105 | 105 | ||
106 | // Rewind to the marker and write the size | 106 | // Rewind to the marker and write the size |
107 | if (this.length === adHoc + 4) { | 107 | if (this.length === adHoc + 4) { |
108 | this.length -= 4; // we started a nal unit, but didnt write one, so roll back the 4 byte size value | 108 | // we started a nal unit, but didnt write one, so roll back the 4 byte size value |
109 | this.length -= 4; | ||
109 | } else if (adHoc > 0) { | 110 | } else if (adHoc > 0) { |
110 | nalStart = adHoc + 4; | 111 | nalStart = adHoc + 4; |
111 | nalLength = this.length - nalStart; | 112 | nalLength = this.length - nalStart; |
... | @@ -207,13 +208,16 @@ hls.FlvTag = function(type, extraData) { | ... | @@ -207,13 +208,16 @@ hls.FlvTag = function(type, extraData) { |
207 | 208 | ||
208 | len = this.length - 11; | 209 | len = this.length - 11; |
209 | 210 | ||
211 | // write the DataSize field | ||
210 | this.bytes[ 1] = (len & 0x00FF0000) >>> 16; | 212 | this.bytes[ 1] = (len & 0x00FF0000) >>> 16; |
211 | this.bytes[ 2] = (len & 0x0000FF00) >>> 8; | 213 | this.bytes[ 2] = (len & 0x0000FF00) >>> 8; |
212 | this.bytes[ 3] = (len & 0x000000FF) >>> 0; | 214 | this.bytes[ 3] = (len & 0x000000FF) >>> 0; |
215 | // write the Timestamp | ||
213 | this.bytes[ 4] = (this.pts & 0x00FF0000) >>> 16; | 216 | this.bytes[ 4] = (this.pts & 0x00FF0000) >>> 16; |
214 | this.bytes[ 5] = (this.pts & 0x0000FF00) >>> 8; | 217 | this.bytes[ 5] = (this.pts & 0x0000FF00) >>> 8; |
215 | this.bytes[ 6] = (this.pts & 0x000000FF) >>> 0; | 218 | this.bytes[ 6] = (this.pts & 0x000000FF) >>> 0; |
216 | this.bytes[ 7] = (this.pts & 0xFF000000) >>> 24; | 219 | this.bytes[ 7] = (this.pts & 0xFF000000) >>> 24; |
220 | // write the StreamID | ||
217 | this.bytes[ 8] = 0; | 221 | this.bytes[ 8] = 0; |
218 | this.bytes[ 9] = 0; | 222 | this.bytes[ 9] = 0; |
219 | this.bytes[10] = 0; | 223 | this.bytes[10] = 0; |
... | @@ -230,9 +234,9 @@ hls.FlvTag = function(type, extraData) { | ... | @@ -230,9 +234,9 @@ hls.FlvTag = function(type, extraData) { |
230 | }; | 234 | }; |
231 | }; | 235 | }; |
232 | 236 | ||
233 | hls.FlvTag.AUDIO_TAG = 0x08; // :uint | 237 | hls.FlvTag.AUDIO_TAG = 0x08; // == 8, :uint |
234 | hls.FlvTag.VIDEO_TAG = 0x09; // :uint | 238 | hls.FlvTag.VIDEO_TAG = 0x09; // == 9, :uint |
235 | hls.FlvTag.METADATA_TAG = 0x12; // :uint | 239 | hls.FlvTag.METADATA_TAG = 0x12; // == 18, :uint |
236 | 240 | ||
237 | // (tag:ByteArray):Boolean { | 241 | // (tag:ByteArray):Boolean { |
238 | hls.FlvTag.isAudioFrame = function(tag) { | 242 | hls.FlvTag.isAudioFrame = function(tag) { | ... | ... |
... | @@ -55,6 +55,19 @@ | ... | @@ -55,6 +55,19 @@ |
55 | } | 55 | } |
56 | }; | 56 | }; |
57 | 57 | ||
58 | /** | ||
59 | * NAL unit | ||
60 | * |- NAL header -|------ RBSP ------| | ||
61 | * | ||
62 | * NAL unit: Network abstraction layer unit. The combination of a NAL | ||
63 | * header and an RBSP. | ||
64 | * NAL header: the encapsulation unit for transport-specific metadata in | ||
65 | * an h264 stream. | ||
66 | * RBSP: raw bit-stream payload. The actual encoded video data. | ||
67 | * | ||
68 | * SPS: sequence parameter set. Part of the RBSP. Metadata to be applied | ||
69 | * to a complete video sequence, like width and height. | ||
70 | */ | ||
58 | this.getSps0Rbsp = function() { // :ByteArray | 71 | this.getSps0Rbsp = function() { // :ByteArray |
59 | // remove emulation bytes. Is this nesessary? is there ever emulation | 72 | // remove emulation bytes. Is this nesessary? is there ever emulation |
60 | // bytes in the SPS? | 73 | // bytes in the SPS? |
... | @@ -62,22 +75,20 @@ | ... | @@ -62,22 +75,20 @@ |
62 | spsCount = 0, | 75 | spsCount = 0, |
63 | sps0 = this.sps[0], // :ByteArray | 76 | sps0 = this.sps[0], // :ByteArray |
64 | rbspCount = 0, | 77 | rbspCount = 0, |
65 | s, // :uint | 78 | start = 1, // :uint |
66 | e, // :uint | 79 | end = sps0.byteLength - 2, // :uint |
67 | rbsp, // :ByteArray | 80 | rbsp = new Uint8Array(sps0.byteLength), // :ByteArray |
68 | o; // :uint | 81 | offset = 0; // :uint |
69 | 82 | ||
70 | s = 1; | 83 | // H264 requires emulation bytes (0x03) be dropped to interpret NAL |
71 | e = sps0.byteLength - 2; | 84 | // units. For instance, 0x8a03b4 should be read as 0x8ab4. |
72 | rbsp = new Uint8Array(sps0.byteLength); // new ByteArray(); | 85 | for (offset = start ; offset < end ;) { |
73 | 86 | if (3 !== sps0[offset + 2]) { | |
74 | for (o = s ; o < e ;) { | 87 | offset += 3; |
75 | if (3 !== sps0[o + 2]) { | 88 | } else if (0 !== sps0[offset + 1]) { |
76 | o += 3; | 89 | offset += 2; |
77 | } else if (0 !== sps0[o + 1]) { | 90 | } else if (0 !== sps0[offset + 0]) { |
78 | o += 2; | 91 | offset += 1; |
79 | } else if (0 !== sps0[o + 0]) { | ||
80 | o += 1; | ||
81 | } else { | 92 | } else { |
82 | console.log('found emulation bytes'); | 93 | console.log('found emulation bytes'); |
83 | 94 | ||
... | @@ -85,21 +96,22 @@ | ... | @@ -85,21 +96,22 @@ |
85 | spsCount += 2; | 96 | spsCount += 2; |
86 | rbspCount += 2; | 97 | rbspCount += 2; |
87 | 98 | ||
88 | if (o > s) { | 99 | if (offset > start) { |
89 | // If there are bytes to write, write them | 100 | // If there are bytes to write, write them |
90 | rbsp.set(sps0.subarray(0, o - s), rbspCount); | 101 | rbsp.set(sps0.subarray(start, offset - start), rbspCount); |
91 | spsCount += o - s; | 102 | spsCount += offset - start; |
92 | rbspCount += o - s; | 103 | rbspCount += offset - start; |
93 | } | 104 | } |
94 | 105 | ||
95 | // skip the emulation bytes | 106 | // skip the emulation bytes |
96 | o += 3; | 107 | offset += 3; |
97 | s = o; | 108 | start = offset; |
98 | } | 109 | } |
99 | } | 110 | } |
100 | 111 | ||
101 | // copy any remaining bytes | 112 | // copy any remaining bytes |
102 | rbsp.set(sps0.subarray(spsCount), rbspCount); // sps0.readBytes(rbsp, rbsp.length); | 113 | rbsp.set(sps0.subarray(spsCount), rbspCount); // sps0.readBytes(rbsp, rbsp.length); |
114 | |||
103 | return rbsp; | 115 | return rbsp; |
104 | }; | 116 | }; |
105 | 117 | ||
... | @@ -122,10 +134,10 @@ | ... | @@ -122,10 +134,10 @@ |
122 | frame_mbs_only_flag, // :int | 134 | frame_mbs_only_flag, // :int |
123 | frame_cropping_flag, // :Boolean | 135 | frame_cropping_flag, // :Boolean |
124 | 136 | ||
125 | frame_crop_left_offset, // :int | 137 | frame_crop_left_offset = 0, // :int |
126 | frame_crop_right_offset, // :int | 138 | frame_crop_right_offset = 0, // :int |
127 | frame_crop_top_offset, // :int | 139 | frame_crop_top_offset = 0, // :int |
128 | frame_crop_bottom_offset, // :int | 140 | frame_crop_bottom_offset = 0, // :int |
129 | 141 | ||
130 | width, | 142 | width, |
131 | height; | 143 | height; |
... | @@ -202,8 +214,8 @@ | ... | @@ -202,8 +214,8 @@ |
202 | frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb(); | 214 | frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb(); |
203 | } | 215 | } |
204 | 216 | ||
205 | width = ((pic_width_in_mbs_minus1 +1)*16) - frame_crop_left_offset*2 - frame_crop_right_offset*2; | 217 | width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_left_offset * 2 - frame_crop_right_offset * 2; |
206 | height = ((2 - frame_mbs_only_flag)* (pic_height_in_map_units_minus1 +1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2); | 218 | height = ((2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2); |
207 | 219 | ||
208 | tag.writeMetaDataDouble("videocodecid", 7); | 220 | tag.writeMetaDataDouble("videocodecid", 7); |
209 | tag.writeMetaDataDouble("width", width); | 221 | tag.writeMetaDataDouble("width", width); |
... | @@ -318,20 +330,25 @@ | ... | @@ -318,20 +330,25 @@ |
318 | }; | 330 | }; |
319 | 331 | ||
320 | // (data:ByteArray, o:int, l:int):void | 332 | // (data:ByteArray, o:int, l:int):void |
321 | this.writeBytes = function(data, o, l) { | 333 | this.writeBytes = function(data, offset, length) { |
322 | var | 334 | var |
323 | nalUnitSize, // :uint | 335 | nalUnitSize, // :uint |
324 | s, // :uint | 336 | start, // :uint |
325 | e, // :uint | 337 | end, // :uint |
326 | t; // :int | 338 | t; // :int |
327 | 339 | ||
328 | if (l <= 0) { | 340 | // default argument values |
341 | offset = offset || 0; | ||
342 | length = length || 0; | ||
343 | |||
344 | if (length <= 0) { | ||
329 | // data is empty so there's nothing to write | 345 | // data is empty so there's nothing to write |
330 | return; | 346 | return; |
331 | } | 347 | } |
332 | 348 | ||
333 | // scan through the bytes until we find the start code (0x000001) for a | 349 | // scan through the bytes until we find the start code (0x000001) for a |
334 | // NAL unit and then begin writing it out | 350 | // NAL unit and then begin writing it out |
351 | // strip NAL start codes as we go | ||
335 | switch (state) { | 352 | switch (state) { |
336 | default: | 353 | default: |
337 | /* falls through */ | 354 | /* falls through */ |
... | @@ -341,11 +358,11 @@ | ... | @@ -341,11 +358,11 @@ |
341 | case 1: | 358 | case 1: |
342 | // A NAL unit may be split across two TS packets. Look back a bit to | 359 | // A NAL unit may be split across two TS packets. Look back a bit to |
343 | // make sure the prefix of the start code wasn't already written out. | 360 | // make sure the prefix of the start code wasn't already written out. |
344 | if (data[o] <= 1) { | 361 | if (data[offset] <= 1) { |
345 | nalUnitSize = h264Frame ? h264Frame.nalUnitSize() : 0; | 362 | nalUnitSize = h264Frame ? h264Frame.nalUnitSize() : 0; |
346 | if (nalUnitSize >= 1 && h264Frame.negIndex(1) === 0) { | 363 | if (nalUnitSize >= 1 && h264Frame.negIndex(1) === 0) { |
347 | // ?? ?? 00 | O[01] ?? ?? | 364 | // ?? ?? 00 | O[01] ?? ?? |
348 | if (1 === data[o] && 2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) { | 365 | if (1 === data[offset] && 2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) { |
349 | // ?? 00 00 : 01 | 366 | // ?? 00 00 : 01 |
350 | if (3 <= nalUnitSize && 0 === h264Frame.negIndex(3)) { | 367 | if (3 <= nalUnitSize && 0 === h264Frame.negIndex(3)) { |
351 | h264Frame.length -= 3; // 00 00 00 : 01 | 368 | h264Frame.length -= 3; // 00 00 00 : 01 |
... | @@ -354,10 +371,10 @@ | ... | @@ -354,10 +371,10 @@ |
354 | } | 371 | } |
355 | 372 | ||
356 | state = 3; | 373 | state = 3; |
357 | return this.writeBytes(data, o + 1, l - 1); | 374 | return this.writeBytes(data, offset + 1, length - 1); |
358 | } | 375 | } |
359 | 376 | ||
360 | if (1 < l && 0 === data[o] && 1 === data[o + 1]) { | 377 | if (1 < length && 0 === data[offset] && 1 === data[offset + 1]) { |
361 | // ?? 00 | 00 01 | 378 | // ?? 00 | 00 01 |
362 | if (2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) { | 379 | if (2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) { |
363 | h264Frame.length -= 2; // 00 00 : 00 01 | 380 | h264Frame.length -= 2; // 00 00 : 00 01 |
... | @@ -366,14 +383,17 @@ | ... | @@ -366,14 +383,17 @@ |
366 | } | 383 | } |
367 | 384 | ||
368 | state = 3; | 385 | state = 3; |
369 | return this.writeBytes(data, o + 2, l - 2); | 386 | return this.writeBytes(data, offset + 2, length - 2); |
370 | } | 387 | } |
371 | 388 | ||
372 | if (2 < l && 0 === data[o] && 0 === data[o + 1] && 1 === data[o + 2]) { | 389 | if (2 < length |
390 | && 0 === data[offset] | ||
391 | && 0 === data[offset + 1] | ||
392 | && 1 === data[offset + 2]) { | ||
373 | // 00 | 00 00 01 | 393 | // 00 | 00 00 01 |
374 | h264Frame.length -= 1; | 394 | h264Frame.length -= 1; |
375 | state = 3; | 395 | state = 3; |
376 | return this.writeBytes(data, o + 3, l - 3); | 396 | return this.writeBytes(data, offset + 3, length - 3); |
377 | } | 397 | } |
378 | } | 398 | } |
379 | } | 399 | } |
... | @@ -382,45 +402,45 @@ | ... | @@ -382,45 +402,45 @@ |
382 | state = 2; | 402 | state = 2; |
383 | /* falls through */ | 403 | /* falls through */ |
384 | case 2: // Look for start codes in data | 404 | case 2: // Look for start codes in data |
385 | s = o; // s = Start | 405 | start = offset; |
386 | e = s + l; // e = End | 406 | end = start + length; |
387 | for (t = e - 3 ; o < t ;) { | 407 | for (t = end - 3 ; offset < t ;) { |
388 | if (1 < data[o + 2]) { | 408 | if (1 < data[offset + 2]) { |
389 | o += 3; // if data[o + 2] is greater than 1, there is no way a start code can begin before o+3 | 409 | offset += 3; // if data[offset + 2] is greater than 1, there is no way a start code can begin before offset+3 |
390 | } else if (0 !== data[o + 1]) { | 410 | } else if (0 !== data[offset + 1]) { |
391 | o += 2; | 411 | offset += 2; |
392 | } else if (0 !== data[o]) { | 412 | } else if (0 !== data[offset]) { |
393 | o += 1; | 413 | offset += 1; |
394 | } else { | 414 | } else { |
395 | // If we get here we have 00 00 00 or 00 00 01 | 415 | // If we get here we have 00 00 00 or 00 00 01 |
396 | if (1 === data[o + 2]) { | 416 | if (1 === data[offset + 2]) { |
397 | if (o > s) { | 417 | if (offset > start) { |
398 | h264Frame.writeBytes(data, s, o - s); | 418 | h264Frame.writeBytes(data, start, offset - start); |
399 | } | 419 | } |
400 | state = 3; | 420 | state = 3; |
401 | o += 3; | 421 | offset += 3; |
402 | return this.writeBytes(data, o, e - o); | 422 | return this.writeBytes(data, offset, end - offset); |
403 | } | 423 | } |
404 | 424 | ||
405 | if (e - o >= 4 && 0 === data[o + 2] && 1 === data[o + 3]) { | 425 | if (end - offset >= 4 && 0 === data[offset + 2] && 1 === data[offset + 3]) { |
406 | if (o > s) { | 426 | if (offset > start) { |
407 | h264Frame.writeBytes(data, s, o - s); | 427 | h264Frame.writeBytes(data, start, offset - start); |
408 | } | 428 | } |
409 | state = 3; | 429 | state = 3; |
410 | o += 4; | 430 | offset += 4; |
411 | return this.writeBytes(data, o, e - o); | 431 | return this.writeBytes(data, offset, end - offset); |
412 | } | 432 | } |
413 | 433 | ||
414 | // We are at the end of the buffer, or we have 3 NULLS followed by | 434 | // We are at the end of the buffer, or we have 3 NULLS followed by |
415 | // something that is not a 1, either way we can step forward by at | 435 | // something that is not a 1, either way we can step forward by at |
416 | // least 3 | 436 | // least 3 |
417 | o += 3; | 437 | offset += 3; |
418 | } | 438 | } |
419 | } | 439 | } |
420 | 440 | ||
421 | // We did not find any start codes. Try again next packet | 441 | // We did not find any start codes. Try again next packet |
422 | state = 1; | 442 | state = 1; |
423 | h264Frame.writeBytes(data, s, l); | 443 | h264Frame.writeBytes(data, start, length); |
424 | return; | 444 | return; |
425 | case 3: | 445 | case 3: |
426 | // The next byte is the first byte of a NAL Unit | 446 | // The next byte is the first byte of a NAL Unit |
... | @@ -447,7 +467,7 @@ | ... | @@ -447,7 +467,7 @@ |
447 | } | 467 | } |
448 | 468 | ||
449 | // setup to begin processing the new NAL unit | 469 | // setup to begin processing the new NAL unit |
450 | nalUnitType = data[o] & 0x1F; | 470 | nalUnitType = data[offset] & 0x1F; |
451 | if (h264Frame && 9 === nalUnitType) { | 471 | if (h264Frame && 9 === nalUnitType) { |
452 | this.finishFrame(); // We are starting a new access unit. Flush the previous one | 472 | this.finishFrame(); // We are starting a new access unit. Flush the previous one |
453 | } | 473 | } |
... | @@ -461,7 +481,7 @@ | ... | @@ -461,7 +481,7 @@ |
461 | 481 | ||
462 | h264Frame.startNalUnit(); | 482 | h264Frame.startNalUnit(); |
463 | state = 2; // We know there will not be an overlapping start code, so we can skip that test | 483 | state = 2; // We know there will not be an overlapping start code, so we can skip that test |
464 | return this.writeBytes(data, o, l); | 484 | return this.writeBytes(data, offset, length); |
465 | /*--------------------------------------------------------------------------------------------------------------------*/ | 485 | /*--------------------------------------------------------------------------------------------------------------------*/ |
466 | } // switch | 486 | } // switch |
467 | }; | 487 | }; | ... | ... |
test/exp-golomb_test.js
0 → 100644
1 | (function(window) { | ||
2 | /* | ||
3 | ======== A Handy Little QUnit Reference ======== | ||
4 | http://api.qunitjs.com/ | ||
5 | |||
6 | Test methods: | ||
7 | module(name, {[setup][ ,teardown]}) | ||
8 | test(name, callback) | ||
9 | expect(numberOfAssertions) | ||
10 | stop(increment) | ||
11 | start(decrement) | ||
12 | Test assertions: | ||
13 | ok(value, [message]) | ||
14 | equal(actual, expected, [message]) | ||
15 | notEqual(actual, expected, [message]) | ||
16 | deepEqual(actual, expected, [message]) | ||
17 | notDeepEqual(actual, expected, [message]) | ||
18 | strictEqual(actual, expected, [message]) | ||
19 | notStrictEqual(actual, expected, [message]) | ||
20 | throws(block, [expected], [message]) | ||
21 | */ | ||
22 | var | ||
23 | buffer, | ||
24 | expGolomb, | ||
25 | view; | ||
26 | |||
27 | module('Exponential Golomb coding'); | ||
28 | |||
29 | test('small numbers are coded correctly', function() { | ||
30 | var | ||
31 | expected = [ | ||
32 | [0xF8, 0], | ||
33 | [0x5F, 1], | ||
34 | [0x7F, 2], | ||
35 | [0x27, 3], | ||
36 | [0x2F, 4], | ||
37 | [0x37, 5], | ||
38 | [0x3F, 6], | ||
39 | [0x11, 7], | ||
40 | [0x13, 8], | ||
41 | [0x15, 9] | ||
42 | ], | ||
43 | i = expected.length, | ||
44 | result; | ||
45 | |||
46 | while (i--) { | ||
47 | buffer = new Uint8Array([expected[i][0]]); | ||
48 | expGolomb = new window.videojs.hls.ExpGolomb(buffer); | ||
49 | result = expGolomb.readUnsignedExpGolomb(); | ||
50 | equal(expected[i][1], result, expected[i][0] + ' is decoded to ' + expected[i][1]); | ||
51 | } | ||
52 | }); | ||
53 | |||
54 | test('drops working data as it is parsed', function() { | ||
55 | var expGolomb = new window.videojs.hls.ExpGolomb(new Uint8Array([0x00, 0xFF])); | ||
56 | expGolomb.skipBits(8); | ||
57 | equal(8, expGolomb.bitsAvailable(), '8 bits remain'); | ||
58 | equal(0xFF, expGolomb.readBits(8), 'the second byte is read'); | ||
59 | }); | ||
60 | |||
61 | test('drops working data when skipping leading zeros', function() { | ||
62 | var expGolomb = new window.videojs.hls.ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF])); | ||
63 | equal(32, expGolomb.skipLeadingZeros(), '32 leading zeros are dropped'); | ||
64 | equal(8, expGolomb.bitsAvailable(), '8 bits remain'); | ||
65 | equal(0xFF, expGolomb.readBits(8), 'the second byte is read'); | ||
66 | }); | ||
67 | |||
68 | test('drops working data when skipping leading zeros', function() { | ||
69 | var expGolomb = new window.videojs.hls.ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF])); | ||
70 | equal(3, expGolomb.skipLeadingZeros(), '3 leading zeros are dropped'); | ||
71 | equal((8 * 4) + 5, expGolomb.bitsAvailable(), '37 bits remain'); | ||
72 | expGolomb.skipBits(1); | ||
73 | equal(0x5a, expGolomb.readBits(8), 'the next bits are read'); | ||
74 | }); | ||
75 | |||
76 | })(this); |
... | @@ -51,6 +51,7 @@ | ... | @@ -51,6 +51,7 @@ |
51 | <script src="../src/bin-utils.js"></script> | 51 | <script src="../src/bin-utils.js"></script> |
52 | 52 | ||
53 | <script src="video-js-hls_test.js"></script> | 53 | <script src="video-js-hls_test.js"></script> |
54 | <script src="exp-golomb_test.js"></script> | ||
54 | </head> | 55 | </head> |
55 | <body> | 56 | <body> |
56 | <div id="qunit"></div> | 57 | <div id="qunit"></div> | ... | ... |
... | @@ -65,16 +65,6 @@ | ... | @@ -65,16 +65,6 @@ |
65 | 65 | ||
66 | console.log('h264 tags:', parser.stats.h264Tags(), | 66 | console.log('h264 tags:', parser.stats.h264Tags(), |
67 | 'aac tags:', parser.stats.aacTags()); | 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 | }); | 68 | }); |
79 | 69 | ||
80 | testAudioTag = function(tag) { | 70 | testAudioTag = function(tag) { |
... | @@ -100,7 +90,10 @@ | ... | @@ -100,7 +90,10 @@ |
100 | frameType = (byte & 0xF0) >>> 4, | 90 | frameType = (byte & 0xF0) >>> 4, |
101 | codecId = byte & 0x0F, | 91 | codecId = byte & 0x0F, |
102 | packetType = tag.bytes[12], | 92 | packetType = tag.bytes[12], |
103 | compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8; | 93 | compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8, |
94 | nalHeader; | ||
95 | |||
96 | // payload starts at tag.bytes[16] | ||
104 | 97 | ||
105 | console.log(frameType); | 98 | console.log(frameType); |
106 | 99 | ||
... | @@ -109,7 +102,7 @@ | ... | @@ -109,7 +102,7 @@ |
109 | 'the frame type should be valid'); | 102 | 'the frame type should be valid'); |
110 | 103 | ||
111 | equal(7, codecId, 'the codec ID is AVC for h264'); | 104 | equal(7, codecId, 'the codec ID is AVC for h264'); |
112 | ok(packetType <=2 && packetType >= 0, 'the packet type is within [0, 2]'); | 105 | ok(packetType <= 2 && packetType >= 0, 'the packet type is within [0, 2]'); |
113 | if (packetType !== 1) { | 106 | if (packetType !== 1) { |
114 | equal(0, | 107 | equal(0, |
115 | compositionTime, | 108 | compositionTime, |
... | @@ -117,7 +110,25 @@ | ... | @@ -117,7 +110,25 @@ |
117 | } | 110 | } |
118 | 111 | ||
119 | // TODO: the rest of the bytes are an NLU unit | 112 | // TODO: the rest of the bytes are an NLU unit |
113 | if (packetType === 0) { | ||
114 | // AVC decoder configuration record | ||
115 | } else { | ||
116 | // NAL units | ||
117 | testNalUnit(tag.bytes.subarray(16)); | ||
118 | } | ||
119 | }; | ||
120 | |||
121 | testNalUnit = function(bytes) { | ||
122 | var | ||
123 | nalHeader = bytes[0], | ||
124 | unitType = nalHeader & 0x1F; | ||
125 | |||
126 | equal(0, (nalHeader & 0x80) >>> 7, 'the first bit is always 0'); | ||
127 | // equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something'); | ||
128 | // ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0'); | ||
129 | // ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22'); | ||
120 | }; | 130 | }; |
131 | |||
121 | 132 | ||
122 | asciiFromBytes = function(bytes) { | 133 | asciiFromBytes = function(bytes) { |
123 | var | 134 | var |
... | @@ -168,6 +179,7 @@ | ... | @@ -168,6 +179,7 @@ |
168 | offset++; | 179 | offset++; |
169 | } else { | 180 | } else { |
170 | // number | 181 | // number |
182 | ok(!isNaN(tag.view.getFloat64(offset)), 'the value is not NaN'); | ||
171 | offset += 8; | 183 | offset += 8; |
172 | } | 184 | } |
173 | } | 185 | } | ... | ... |
-
Please register or sign in to post a comment