c09dfad4 by David LaPalomento

A quick translation of h264-stream

Map over H264Stream from action script and get it passing JSLint. There are still plenty of pieces which are definitely busted. Stub ExpGolomb out for now.
1 parent 17dd1e68
1 (function() {
2
3 window.videojs.hls.ExpGolomb = function() {};
4
5 /*
6 public class ExpGolomb
7 {
8 private var workingData:ByteArray;
9 private var workingWord:uint;
10 private var workingbBitsAvailable:uint;
11
12 public function ExpGolomb(pData:ByteArray)
13 {
14 workingData = pData;
15 workingData.position = 0;
16 loadWord();
17 }
18
19 public function length():uint
20 {
21 return ( 8 * workingData.length );
22 }
23
24 public function bitsAvailable():uint
25 {
26 return ( 8 * workingData.bytesAvailable ) + workingbBitsAvailable;
27 }
28
29 private function loadWord():void
30 {
31 workingWord = 0; workingbBitsAvailable = 0;
32 switch( workingData.bytesAvailable )
33 {
34 case 0: workingbBitsAvailable = 0; break;
35 default: // not 0, but greater than 4
36 case 4: workingWord = workingData.readUnsignedByte(); workingbBitsAvailable = 8;
37 case 3: workingWord = ( workingWord << 8 ) | workingData.readUnsignedByte(); workingbBitsAvailable += 8;
38 case 2: workingWord = ( workingWord << 8 ) | workingData.readUnsignedByte(); workingbBitsAvailable += 8;
39 case 1: workingWord = ( workingWord << 8 ) | workingData.readUnsignedByte(); workingbBitsAvailable += 8;
40 }
41
42 workingWord <<= (32 - workingbBitsAvailable);
43 }
44
45 public function skipBits(size:int):void
46 {
47 if ( workingbBitsAvailable > size )
48 {
49 workingWord <<= size;
50 workingbBitsAvailable -= size;
51 }
52 else
53 {
54 size -= workingbBitsAvailable;
55 var skipBytes:int = size / 8;
56
57 size -= ( skipBytes * 8 );
58 workingData.position += skipBytes;
59
60 loadWord();
61
62 workingWord <<= size;
63 workingbBitsAvailable -= size;
64 }
65 }
66
67 public function readBits(size:int):uint
68 {
69 // if ( 32 < size )
70 // throw new Error("Can not read more than 32 bits at a time");
71
72 var bits:uint = ( workingbBitsAvailable < size ? workingbBitsAvailable : size);
73 var valu:uint = workingWord >>> (32 - bits);
74
75 workingbBitsAvailable -= bits;
76 if ( 0 < workingbBitsAvailable )
77 workingWord <<= bits;
78 else
79 loadWord();
80
81 bits = size - bits;
82 if ( 0 < bits )
83 return valu << bits | readBits( bits );
84 else
85 return valu;
86 }
87
88 private function skipLeadingZeros():uint
89 {
90 for( var clz:uint = 0 ; clz < workingbBitsAvailable ; ++clz )
91 {
92 if( 0 != ( workingWord & ( 0x80000000 >>> clz ) ) )
93 {
94 workingWord <<= clz;
95 workingbBitsAvailable -= clz;
96 return clz;
97 }
98 }
99
100 loadWord(); // we exhausted workingWord and still have not found a 1
101 return clz + skipLeadingZeros();
102 }
103
104 public function skipUnsignedExpGolomb():void
105 {
106 skipBits(1 + skipLeadingZeros() );
107 }
108
109 public function skipExpGolomb():void
110 {
111 skipBits(1 + skipLeadingZeros() );
112 }
113
114 public function readUnsignedExpGolomb():uint
115 {
116 var clz:uint = skipLeadingZeros();
117 return readBits(clz+1) - 1;
118 }
119
120 public function readExpGolomb():int
121 {
122 var valu:int = readUnsignedExpGolomb();
123 if ( 0x01 & valu ) // the number is odd if the low order bit is set
124 return (1 + valu) >>> 1; // add 1 to make it even, and devide by 2
125 else
126 return -1 * (valu >>> 1); // devide by two then make it negative
127 }
128
129 // Some convenience functions
130 public function readBoolean():Boolean
131 {
132 return 1 == readBits(1);
133 }
134
135 public function readUnsignedByte():int
136 {
137 return readBits(8);
138 }
139 }
140 */
141 })();
...@@ -7,9 +7,428 @@ ...@@ -7,9 +7,428 @@
7 */ 7 */
8 8
9 (function(window) { 9 (function(window) {
10 var
11 ExpGolomb = window.videojs.hls.ExpGolomb,
12 FlvTag = window.videojs.hls.FlvTag,
13 H264ExtraData = function() {
14 var
15 sps = [], // :Array
16 pps = []; // :Array
10 17
11 window.videojs.hls.H264Stream = function(){ 18 this.addSPS = function() { // :ByteArray
12 this.tags = []; 19 var tmp = new Uint8Array(); // :ByteArray
13 }; 20 sps.push(tmp);
21 return tmp;
22 };
14 23
24 this.addPPS = function() { // :ByteArray
25 var tmp = new Uint8Array(); // :ByteArray
26 pps.push(tmp);
27 return tmp;
28 };
29
30 this.extraDataExists = function() { // :Boolean
31 return 0 < sps.length;
32 };
33
34 // (sizeOfScalingList:int, expGolomb:ExpGolomb):void
35 this.scaling_list = function(sizeOfScalingList, expGolomb) {
36 var
37 lastScale = 8, // :int
38 nextScale = 8, // :int
39 j,
40 delta_scale; // :int
41
42 for (j = 0; j < sizeOfScalingList; ++j) {
43 if (0 !== nextScale) {
44 delta_scale = expGolomb.readExpGolomb();
45 nextScale = (lastScale + delta_scale + 256) % 256;
46 //useDefaultScalingMatrixFlag = ( j = = 0 && nextScale = = 0 )
47 }
48
49 lastScale = (nextScale === 0) ? lastScale : nextScale;
50 // scalingList[ j ] = ( nextScale == 0 ) ? lastScale : nextScale;
51 // lastScale = scalingList[ j ]
52 }
53 };
54
55 this.getSps0Rbsp = function() { // :ByteArray
56 // remove emulation bytes. Is this nesessary? is there ever emulation bytes in the SPS?
57 var
58 sps0 = sps[0],// :ByteArray = sps[0];
59 s, // :uint
60 e, // :uint
61 rbsp, // :ByteArray
62 o; // :uint
63
64 sps0.position = 1;
65 s = sps0.position;
66 e = sps0.bytesAvailable - 2;
67 rbsp = new Uint8Array(); // new ByteArray();
68
69 for (o = s ; o < e ;) {
70 if (3 !== sps0[o + 2]) {
71 o += 3;
72 } else if (0 !== sps0[o + 1]) {
73 o += 2;
74 } else if (0 !== sps0[o + 0]) {
75 o += 1;
76 } else { // found emulation bytess
77 rbsp.writeShort(0x0000);
78
79 if ( o > s ) {
80 // If there are bytes to write, write them
81 sps0.readBytes( rbsp, rbsp.length, o-s );
82 }
83
84 // skip the emulation bytes
85 sps0.position += 3;
86 o = s = sps0.position;
87 }
88 }
89
90 // copy any remaining bytes
91 sps0.readBytes(rbsp, rbsp.length);
92 sps0.position = 0;
93 return rbsp;
94 };
95
96 //(pts:uint):FlvTag
97 this.metaDataTag = function(pts) {
98 var
99 tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag
100 expGolomb, // :ExpGolomb
101 profile_idc, // :int
102 chroma_format_idc, // :int
103 imax, // :int
104 i, // :int
105
106 pic_order_cnt_type, // :int
107 num_ref_frames_in_pic_order_cnt_cycle, // :uint
108
109 pic_width_in_mbs_minus1, // :int
110 pic_height_in_map_units_minus1, // :int
111
112 frame_mbs_only_flag, // :int
113 frame_cropping_flag, // :Boolean
114
115 frame_crop_left_offset, // :int
116 frame_crop_right_offset, // :int
117 frame_crop_top_offset, // :int
118 frame_crop_bottom_offset, // :int
119
120 width,
121 height;
122
123 tag.dts = pts;
124 tag.pts = pts;
125 expGolomb = new ExpGolomb(this.getSps0Rbsp());
126
127 profile_idc = expGolomb.readUnsignedByte(); // :int = expGolomb.readUnsignedByte(); // profile_idc u(8)
128 expGolomb.skipBits(16);// constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8)
129 expGolomb.skipUnsignedExpGolomb(); // seq_parameter_set_id
130
131 if (profile_idc === 100 ||
132 profile_idc === 110 ||
133 profile_idc === 122 ||
134 profile_idc === 244 ||
135 profile_idc === 44 ||
136 profile_idc === 83 ||
137 profile_idc === 86 ||
138 profile_idc === 118 ||
139 profile_idc === 128) {
140 chroma_format_idc = expGolomb.readUnsignedExpGolomb();
141 if (3 === chroma_format_idc) {
142 expGolomb.skipBits(1); // separate_colour_plane_flag
143 }
144 expGolomb.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
145 expGolomb.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
146 expGolomb.skipBits(1); // qpprime_y_zero_transform_bypass_flag
147 if ( expGolomb.readBoolean() ) { // seq_scaling_matrix_present_flag
148 imax = (chroma_format_idc !== 3) ? 8 : 12;
149 for (i = 0 ; i < imax ; ++i) {
150 if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ]
151 if (i < 6) {
152 this.scaling_list(16, expGolomb);
153 } else {
154 this.scaling_list(64, expGolomb);
155 }
156 }
157 }
158 }
159 }
160
161 expGolomb.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
162 pic_order_cnt_type = expGolomb.readUnsignedExpGolomb();
163
164 if ( 0 === pic_order_cnt_type ) {
165 expGolomb.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
166 } else if ( 1 === pic_order_cnt_type ) {
167 expGolomb.skipBits(1); // delta_pic_order_always_zero_flag
168 expGolomb.skipExpGolomb(); // offset_for_non_ref_pic
169 expGolomb.skipExpGolomb(); // offset_for_top_to_bottom_field
170 num_ref_frames_in_pic_order_cnt_cycle = expGolomb.readUnsignedExpGolomb();
171 for(i = 0 ; i < num_ref_frames_in_pic_order_cnt_cycle ; ++i) {
172 expGolomb.skipExpGolomb(); // offset_for_ref_frame[ i ]
173 }
174 }
175
176 expGolomb.skipUnsignedExpGolomb(); // max_num_ref_frames
177 expGolomb.skipBits(1); // gaps_in_frame_num_value_allowed_flag
178 pic_width_in_mbs_minus1 = expGolomb.readUnsignedExpGolomb();
179 pic_height_in_map_units_minus1 = expGolomb.readUnsignedExpGolomb();
180
181 frame_mbs_only_flag = expGolomb.readBits(1);
182 if (0 === frame_mbs_only_flag) {
183 expGolomb.skipBits(1); // mb_adaptive_frame_field_flag
184 }
185
186 expGolomb.skipBits(1); // direct_8x8_inference_flag
187 frame_cropping_flag = expGolomb.readBoolean();
188 if (frame_cropping_flag) {
189 frame_crop_left_offset = expGolomb.readUnsignedExpGolomb();
190 frame_crop_right_offset = expGolomb.readUnsignedExpGolomb();
191 frame_crop_top_offset = expGolomb.readUnsignedExpGolomb();
192 frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb();
193 }
194
195 width = ((pic_width_in_mbs_minus1 +1)*16) - frame_crop_left_offset*2 - frame_crop_right_offset*2;
196 height = ((2 - frame_mbs_only_flag)* (pic_height_in_map_units_minus1 +1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2);
197
198 tag.writeMetaDataDouble("videocodecid", 7);
199 tag.writeMetaDataDouble("width", width);
200 tag.writeMetaDataDouble("height", height);
201 // tag.writeMetaDataDouble("videodatarate", 0 );
202 // tag.writeMetaDataDouble("framerate", 0);
203
204 return tag;
205 };
206
207 // (pts:uint):FlvTag
208 this.extraDataTag = function(pts) {
209 var
210 i,
211 tag = new FlvTag(FlvTag.VIDEO_TAG,true);
212
213 tag.dts = pts;
214 tag.pts = pts;
215
216 tag.writeByte(0x01);// version
217 tag.writeByte(sps[0][1]);// profile
218 tag.writeByte(sps[0][2]);// compatibility
219 tag.writeByte(sps[0][3]);// level
220 tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
221 tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits)
222 tag.writeShort( sps[0].length ); // data of SPS
223 tag.writeBytes( sps[0] ); // SPS
224
225 tag.writeByte( pps.length ); // num of PPS (will there ever be more that 1 PPS?)
226 for (i = 0 ; i < pps.length ; ++i) {
227 tag.writeShort(pps[i].length); // 2 bytes for length of PPS
228 tag.writeBytes(pps[i]); // data of PPS
229 }
230
231 return tag;
232 };
233 };
234
235 window.videojs.hls.H264Stream = function() {
236 var
237 tags = [],
238
239 next_pts, // :uint;
240 next_dts, // :uint;
241 pts_delta = -1, // :int
242
243 h264Frame, // :FlvTag
244
245 oldExtraData = new H264ExtraData(), // :H264ExtraData
246 newExtraData = new H264ExtraData(), // :H264ExtraData
247
248 nalUnitType = -1, // :int
249
250 state; // :uint;
251
252 this.tags = [];
253
254 //(pts:uint, dts:uint, dataAligned:Boolean):void
255 this.setNextTimeStamp = function(pts, dts, dataAligned) {
256 if (0>pts_delta) {
257 // We assume the very first pts is less than 0x8FFFFFFF (max signed
258 // int32)
259 pts_delta = pts;
260 }
261
262 // We could end up with a DTS less than 0 here. We need to deal with that!
263 next_pts = pts - pts_delta;
264 next_dts = dts - pts_delta;
265
266 // If data is aligned, flush all internal buffers
267 if (dataAligned) {
268 this.finishFrame();
269 }
270 };
271
272 this.finishFrame = function() {
273 if (null !== h264Frame) {
274 // Push SPS before EVERY IDR frame fo seeking
275 if (newExtraData.extraDataExists()) {
276 oldExtraData = newExtraData;
277 newExtraData = new H264ExtraData();
278 }
279
280 if(true === h264Frame.keyFrame) {
281 // Push extra data on every IDR frame in case we did a stream change + seek
282 tags.push( oldExtraData.metaDataTag (h264Frame.pts) );
283 tags.push( oldExtraData.extraDataTag(h264Frame.pts) );
284 }
285
286 h264Frame.endNalUnit();
287 tags.push(h264Frame);
288 }
289
290 h264Frame = null;
291 nalUnitType = -1;
292 state = 0;
293 };
294
295 // (pData:ByteArray, o:int, l:int):void
296 this.writeBytes = function(pData, o, l) {
297 var
298 nalUnitSize, // :uint
299 s, // :uint
300 e, // :uint
301 t; // :int
302
303 if (0 >= l) {
304 return;
305 }
306 switch (state) {
307 default:
308 state = 1;
309 break;
310 case 0:
311 state = 1;
312 break;
313 /*--------------------------------------------------------------------------------------------------------------------*/
314 case 1: // We are looking for overlaping start codes
315 if (1 >= pData[o]) {
316 nalUnitSize = (null === h264Frame) ? 0 : h264Frame.nalUnitSize();
317 if (1 <= nalUnitSize && 0 === h264Frame.negIndex(1)) {
318 // ?? ?? 00 | O[01] ?? ??
319 if (1 === pData[o] && 2 <= nalUnitSize && 0 === h264Frame.negIndex(2) ) {
320 // ?? 00 00 : 01
321 if (3 <= nalUnitSize && 0 === h264Frame.negIndex(3)) {
322 h264Frame.length -= 3; // 00 00 00 : 01
323 } else {
324 h264Frame.length -= 2; // 00 00 : 01
325 }
326
327 state = 3;
328 return this.writeBytes(pData, o + 1, l - 1);
329 }
330
331 if (1 < l && 0 === pData[o] && 1 === pData[o + 1]) {
332 // ?? 00 | 00 01
333 if (2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) {
334 h264Frame.length -= 2; // 00 00 : 00 01
335 } else {
336 h264Frame.length -= 1; // 00 : 00 01
337 }
338
339 state = 3;
340 return this.writeBytes(pData, o + 2, l - 2);
341 }
342
343 if (2 < l && 0 === pData[o] && 0 === pData[o + 1] && 1 === pData[o + 2]) {
344 // 00 | 00 00 01
345 h264Frame.length -= 1;
346 state = 3;
347 return this.writeBytes(pData, o + 3, l - 3);
348 }
349 }
350 }
351 // allow fall through if the above fails, we may end up checking a few
352 // bytes a second time. But that case will be VERY rare
353 state = 2;
354 break;
355 case 2: // Look for start codes in pData
356 s = o; // s = Start
357 e = s + l; // e = End
358 for (t = e - 3 ; o < t ;) {
359 if (1 < pData[o + 2]) {
360 o += 3; // if pData[o+2] is greater than 1, there is no way a start code can begin before o+3
361 } else if (0 !== pData[o + 1]) {
362 o += 2;
363 } else if (0 !== pData[o]) {
364 o += 1;
365 } else {
366 // If we get here we have 00 00 00 or 00 00 01
367 if (1 === pData[o + 2]) {
368 if (o > s) {
369 h264Frame.writeBytes(pData, s, o - s);
370 }
371 state = 3; o += 3;
372 return this.writeBytes(pData, o, e - o);
373 }
374
375 if (4 <= e-o && 0 === pData[o + 2] && 1 === pData[o + 3]) {
376 if (o > s) {
377 h264Frame.writeBytes(pData, s, o - s);
378 }
379 state = 3;
380 o += 4;
381 return this.writeBytes(pData, o, e - o);
382 }
383
384 // We are at the end of the buffer, or we have 3 NULLS followed by something that is not a 1, eaither way we can step forward by at least 3
385 o += 3;
386 }
387 }
388
389 // We did not find any start codes. Try again next packet
390 state = 1;
391 h264Frame.writeBytes( pData, s, l );
392 return;
393 /*--------------------------------------------------------------------------------------------------------------------*/
394 case 3: // The next byte is the first byte of a NAL Unit
395 if (null !== h264Frame) {
396 switch (nalUnitType) {
397 // We are still operating on the previous NAL Unit
398 case 7:
399 h264Frame.endNalUnit(newExtraData.addSPS());
400 break;
401 case 8:
402 h264Frame.endNalUnit(newExtraData.addPPS());
403 break;
404 case 5:
405 h264Frame.keyFrame = true;
406 h264Frame.endNalUnit();
407 break;
408 default:
409 h264Frame.endNalUnit();
410 break;
411 }
412 }
413
414 nalUnitType = pData[o] & 0x1F;
415 if ( null != h264Frame && 9 === nalUnitType ) {
416 this.finishFrame(); // We are starting a new access unit. Flush the previous one
417 }
418
419 // finishFrame may render h264Frame null, so we must test again
420 if ( null === h264Frame )
421 {
422 h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
423 h264Frame.pts = next_pts;
424 h264Frame.dts = next_dts;
425 }
426
427 h264Frame.startNalUnit();
428 state = 2; // We know there will not be an overlapping start code, so we can skip that test
429 return this.writeBytes(pData, o, l);
430 /*--------------------------------------------------------------------------------------------------------------------*/
431 } // switch
432 };
433 };
15 })(this); 434 })(this);
......