63c045e6 by David LaPalomento

@gkatsev ensure segments without an initial IDR are not displayed in 4:3 initially.

Closes #272.

Squashed commit of the following:

commit de597c3ee10075cece7036aeaacedb74f33d9ef5
Author: David LaPalomento <dlapalomento@gmail.com>
Date:   Fri May 22 14:02:27 2015 -0400

    Remove vim swap file
    Ignore them in version control in the future.

commit fb189ba16330311371bb6b9cf1bb6248005e10b7
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 18:30:07 2015 -0400

    Default 'nextFrameKeyFrame' to false.

commit 3788ad0607fabb2cde1a1d5eea4163ceafb5fab5
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 18:24:16 2015 -0400

    setNextFrameKeyFrame

commit 6944234afd99285a448a9cf3adbcaf1eb26cfe33
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 18:06:26 2015 -0400

    Another camelcase usage

commit da6e32a4d9024d859ca58bc1d4bc3fcbe1de7a33
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 18:05:30 2015 -0400

    Dont parse unused fields. Use camel case names

commit fb7990b6606501a549d7448de6eec38784695896
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 16:28:28 2015 -0400

    ifs should have curly braces

commit 92e40c642270e4ce4652a874073a66876312072f
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 15:58:18 2015 -0400

    Adaptation Field vars. Use Random Access Indicator
    The Random Access Indicator tells us whether something is a keyframe.
    Set the stream's frame's keyFrame property to true if the Random Access
    Indicator is set.

commit 047a6d7771cd2d9c324b6da9bd76d4b6aeeadaf0
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:56:23 2015 -0400

    Set up the test with exact conditions

commit e2f8b18656d4cdb830cd23013fe7e5fcfa599ca4
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:56:01 2015 -0400

    Restore stubbed out methods

commit ff5f3b5fdb9dbe5e34447f8a4680c294163e0c46
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:50:22 2015 -0400

    prototypeify h264-stream

commit 7ee359d582550474a33730547742ce3d656dad52
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:28:08 2015 -0400

    Initial test for metadata

commit 0e3a961c7594d67280958cf21330fb347e1dda0a
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:21:55 2015 -0400

    Fix test properly

commit 7e0de308227a4373c6abd71769ed474e59c7edeb
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:14:57 2015 -0400

    Move h264-extradata into separate file

commit 66a676c234e6e087694611d710cad6414cbc984c
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 15:52:08 2015 -0400

    prototypeify H264ExtraData
1 parent 004e13d8
...@@ -6,4 +6,4 @@ dist/* ...@@ -6,4 +6,4 @@ dist/*
6 *.ipr 6 *.ipr
7 *.iws 7 *.iws
8 *.swp 8 *.swp
9 tmp/**
...\ No newline at end of file ...\ No newline at end of file
9 tmp/**.*.swo
......
...@@ -5,6 +5,7 @@ CHANGELOG ...@@ -5,6 +5,7 @@ CHANGELOG
5 * @dmlap use contribflow to manage contributions ([view](https://github.com/videojs/videojs-contrib-hls/pull/275)) 5 * @dmlap use contribflow to manage contributions ([view](https://github.com/videojs/videojs-contrib-hls/pull/275))
6 * @dmlap add a contribflow configuration ([view](https://github.com/videojs/videojs-contrib-hls/pull/276)) 6 * @dmlap add a contribflow configuration ([view](https://github.com/videojs/videojs-contrib-hls/pull/276))
7 * @ntadej Do not unnecessarily reset to the live point when refreshing playlists. Clean up playlist loader timeouts. ([view](https://github.com/videojs/videojs-contrib-hls/pull/274)) 7 * @ntadej Do not unnecessarily reset to the live point when refreshing playlists. Clean up playlist loader timeouts. ([view](https://github.com/videojs/videojs-contrib-hls/pull/274))
8 * @gkatsev ensure segments without an initial IDR are not displayed in 4
8 9
9 -------------------- 10 --------------------
10 11
......
...@@ -27,6 +27,7 @@ module.exports = function(grunt) { ...@@ -27,6 +27,7 @@ module.exports = function(grunt) {
27 'src/stream.js', 27 'src/stream.js',
28 'src/flv-tag.js', 28 'src/flv-tag.js',
29 'src/exp-golomb.js', 29 'src/exp-golomb.js',
30 'src/h264-extradata.js',
30 'src/h264-stream.js', 31 'src/h264-stream.js',
31 'src/aac-stream.js', 32 'src/aac-stream.js',
32 'src/metadata-stream.js', 33 'src/metadata-stream.js',
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
20 <script src="src/flv-tag.js"></script> 20 <script src="src/flv-tag.js"></script>
21 <script src="src/stream.js"></script> 21 <script src="src/stream.js"></script>
22 <script src="src/exp-golomb.js"></script> 22 <script src="src/exp-golomb.js"></script>
23 <script src="src/h264-extradata.js"></script>
23 <script src="src/h264-stream.js"></script> 24 <script src="src/h264-stream.js"></script>
24 <script src="src/aac-stream.js"></script> 25 <script src="src/aac-stream.js"></script>
25 <script src="src/metadata-stream.js"></script> 26 <script src="src/metadata-stream.js"></script>
......
1 (function() {
2 var
3 H264ExtraData,
4 ExpGolomb = window.videojs.Hls.ExpGolomb,
5 FlvTag = window.videojs.Hls.FlvTag;
6
7 window.videojs.Hls.H264ExtraData = H264ExtraData = function() {
8 this.sps = []; // :Array
9 this.pps = []; // :Array
10 };
11
12 H264ExtraData.prototype.extraDataExists = function() { // :Boolean
13 return this.sps.length > 0;
14 };
15
16 // (sizeOfScalingList:int, expGolomb:ExpGolomb):void
17 H264ExtraData.prototype.scaling_list = function(sizeOfScalingList, expGolomb) {
18 var
19 lastScale = 8, // :int
20 nextScale = 8, // :int
21 j,
22 delta_scale; // :int
23
24 for (j = 0; j < sizeOfScalingList; ++j) {
25 if (0 !== nextScale) {
26 delta_scale = expGolomb.readExpGolomb();
27 nextScale = (lastScale + delta_scale + 256) % 256;
28 //useDefaultScalingMatrixFlag = ( j = = 0 && nextScale = = 0 )
29 }
30
31 lastScale = (nextScale === 0) ? lastScale : nextScale;
32 // scalingList[ j ] = ( nextScale == 0 ) ? lastScale : nextScale;
33 // lastScale = scalingList[ j ]
34 }
35 };
36
37 /**
38 * RBSP: raw bit-stream payload. The actual encoded video data.
39 *
40 * SPS: sequence parameter set. Part of the RBSP. Metadata to be applied
41 * to a complete video sequence, like width and height.
42 */
43 H264ExtraData.prototype.getSps0Rbsp = function() { // :ByteArray
44 var
45 sps = this.sps[0],
46 offset = 1,
47 start = 1,
48 written = 0,
49 end = sps.byteLength - 2,
50 result = new Uint8Array(sps.byteLength);
51
52 // In order to prevent 0x0000 01 from being interpreted as a
53 // NAL start code, occurences of that byte sequence in the
54 // RBSP are escaped with an "emulation byte". That turns
55 // sequences of 0x0000 01 into 0x0000 0301. When interpreting
56 // a NAL payload, they must be filtered back out.
57 while (offset < end) {
58 if (sps[offset] === 0x00 &&
59 sps[offset + 1] === 0x00 &&
60 sps[offset + 2] === 0x03) {
61 result.set(sps.subarray(start, offset + 1), written);
62 written += offset + 1 - start;
63 start = offset + 3;
64 }
65 offset++;
66 }
67 result.set(sps.subarray(start), written);
68 return result.subarray(0, written + (sps.byteLength - start));
69 };
70
71 // (pts:uint):FlvTag
72 H264ExtraData.prototype.metaDataTag = function(pts) {
73 var
74 tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag
75 expGolomb, // :ExpGolomb
76 profile_idc, // :int
77 chroma_format_idc, // :int
78 imax, // :int
79 i, // :int
80
81 pic_order_cnt_type, // :int
82 num_ref_frames_in_pic_order_cnt_cycle, // :uint
83
84 pic_width_in_mbs_minus1, // :int
85 pic_height_in_map_units_minus1, // :int
86
87 frame_mbs_only_flag, // :int
88 frame_cropping_flag, // :Boolean
89
90 frame_crop_left_offset = 0, // :int
91 frame_crop_right_offset = 0, // :int
92 frame_crop_top_offset = 0, // :int
93 frame_crop_bottom_offset = 0, // :int
94
95 width,
96 height;
97
98 tag.dts = pts;
99 tag.pts = pts;
100 expGolomb = new ExpGolomb(this.getSps0Rbsp());
101
102 // :int = expGolomb.readUnsignedByte(); // profile_idc u(8)
103 profile_idc = expGolomb.readUnsignedByte();
104
105 // constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8)
106 expGolomb.skipBits(16);
107
108 // seq_parameter_set_id
109 expGolomb.skipUnsignedExpGolomb();
110
111 if (profile_idc === 100 ||
112 profile_idc === 110 ||
113 profile_idc === 122 ||
114 profile_idc === 244 ||
115 profile_idc === 44 ||
116 profile_idc === 83 ||
117 profile_idc === 86 ||
118 profile_idc === 118 ||
119 profile_idc === 128) {
120 chroma_format_idc = expGolomb.readUnsignedExpGolomb();
121 if (3 === chroma_format_idc) {
122 expGolomb.skipBits(1); // separate_colour_plane_flag
123 }
124 expGolomb.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
125 expGolomb.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
126 expGolomb.skipBits(1); // qpprime_y_zero_transform_bypass_flag
127 if (expGolomb.readBoolean()) { // seq_scaling_matrix_present_flag
128 imax = (chroma_format_idc !== 3) ? 8 : 12;
129 for (i = 0 ; i < imax ; ++i) {
130 if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ]
131 if (i < 6) {
132 this.scaling_list(16, expGolomb);
133 } else {
134 this.scaling_list(64, expGolomb);
135 }
136 }
137 }
138 }
139 }
140
141 expGolomb.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
142 pic_order_cnt_type = expGolomb.readUnsignedExpGolomb();
143
144 if ( 0 === pic_order_cnt_type ) {
145 expGolomb.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
146 } else if ( 1 === pic_order_cnt_type ) {
147 expGolomb.skipBits(1); // delta_pic_order_always_zero_flag
148 expGolomb.skipExpGolomb(); // offset_for_non_ref_pic
149 expGolomb.skipExpGolomb(); // offset_for_top_to_bottom_field
150 num_ref_frames_in_pic_order_cnt_cycle = expGolomb.readUnsignedExpGolomb();
151 for(i = 0 ; i < num_ref_frames_in_pic_order_cnt_cycle ; ++i) {
152 expGolomb.skipExpGolomb(); // offset_for_ref_frame[ i ]
153 }
154 }
155
156 expGolomb.skipUnsignedExpGolomb(); // max_num_ref_frames
157 expGolomb.skipBits(1); // gaps_in_frame_num_value_allowed_flag
158 pic_width_in_mbs_minus1 = expGolomb.readUnsignedExpGolomb();
159 pic_height_in_map_units_minus1 = expGolomb.readUnsignedExpGolomb();
160
161 frame_mbs_only_flag = expGolomb.readBits(1);
162 if (0 === frame_mbs_only_flag) {
163 expGolomb.skipBits(1); // mb_adaptive_frame_field_flag
164 }
165
166 expGolomb.skipBits(1); // direct_8x8_inference_flag
167 frame_cropping_flag = expGolomb.readBoolean();
168 if (frame_cropping_flag) {
169 frame_crop_left_offset = expGolomb.readUnsignedExpGolomb();
170 frame_crop_right_offset = expGolomb.readUnsignedExpGolomb();
171 frame_crop_top_offset = expGolomb.readUnsignedExpGolomb();
172 frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb();
173 }
174
175 width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_left_offset * 2 - frame_crop_right_offset * 2;
176 height = ((2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2);
177
178 tag.writeMetaDataDouble("videocodecid", 7);
179 tag.writeMetaDataDouble("width", width);
180 tag.writeMetaDataDouble("height", height);
181 // tag.writeMetaDataDouble("videodatarate", 0 );
182 // tag.writeMetaDataDouble("framerate", 0);
183
184 return tag;
185 };
186
187 // (pts:uint):FlvTag
188 H264ExtraData.prototype.extraDataTag = function(pts) {
189 var
190 i,
191 tag = new FlvTag(FlvTag.VIDEO_TAG, true);
192
193 tag.dts = pts;
194 tag.pts = pts;
195
196 tag.writeByte(0x01);// version
197 tag.writeByte(this.sps[0][1]);// profile
198 tag.writeByte(this.sps[0][2]);// compatibility
199 tag.writeByte(this.sps[0][3]);// level
200 tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
201 tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits)
202 tag.writeShort( this.sps[0].length ); // data of SPS
203 tag.writeBytes( this.sps[0] ); // SPS
204
205 tag.writeByte( this.pps.length ); // num of PPS (will there ever be more that 1 PPS?)
206 for (i = 0 ; i < this.pps.length ; ++i) {
207 tag.writeShort(this.pps[i].length); // 2 bytes for length of PPS
208 tag.writeBytes(this.pps[i]); // data of PPS
209 }
210
211 return tag;
212 };
213 })();
1 (function(window) { 1 (function(window) {
2 var 2 var
3 ExpGolomb = window.videojs.Hls.ExpGolomb,
4 FlvTag = window.videojs.Hls.FlvTag, 3 FlvTag = window.videojs.Hls.FlvTag,
5 4 H264ExtraData = window.videojs.Hls.H264ExtraData,
6 H264ExtraData = function() { 5 H264Stream,
7 this.sps = []; // :Array
8 this.pps = []; // :Array
9
10 this.extraDataExists = function() { // :Boolean
11 return this.sps.length > 0;
12 };
13
14 // (sizeOfScalingList:int, expGolomb:ExpGolomb):void
15 this.scaling_list = function(sizeOfScalingList, expGolomb) {
16 var
17 lastScale = 8, // :int
18 nextScale = 8, // :int
19 j,
20 delta_scale; // :int
21
22 for (j = 0; j < sizeOfScalingList; ++j) {
23 if (0 !== nextScale) {
24 delta_scale = expGolomb.readExpGolomb();
25 nextScale = (lastScale + delta_scale + 256) % 256;
26 //useDefaultScalingMatrixFlag = ( j = = 0 && nextScale = = 0 )
27 }
28
29 lastScale = (nextScale === 0) ? lastScale : nextScale;
30 // scalingList[ j ] = ( nextScale == 0 ) ? lastScale : nextScale;
31 // lastScale = scalingList[ j ]
32 }
33 };
34
35 /**
36 * RBSP: raw bit-stream payload. The actual encoded video data.
37 *
38 * SPS: sequence parameter set. Part of the RBSP. Metadata to be applied
39 * to a complete video sequence, like width and height.
40 */
41 this.getSps0Rbsp = function() { // :ByteArray
42 var
43 sps = this.sps[0],
44 offset = 1,
45 start = 1,
46 written = 0,
47 end = sps.byteLength - 2,
48 result = new Uint8Array(sps.byteLength);
49
50 // In order to prevent 0x0000 01 from being interpreted as a
51 // NAL start code, occurences of that byte sequence in the
52 // RBSP are escaped with an "emulation byte". That turns
53 // sequences of 0x0000 01 into 0x0000 0301. When interpreting
54 // a NAL payload, they must be filtered back out.
55 while (offset < end) {
56 if (sps[offset] === 0x00 &&
57 sps[offset + 1] === 0x00 &&
58 sps[offset + 2] === 0x03) {
59 result.set(sps.subarray(start, offset + 1), written);
60 written += offset + 1 - start;
61 start = offset + 3;
62 }
63 offset++;
64 }
65 result.set(sps.subarray(start), written);
66 return result.subarray(0, written + (sps.byteLength - start));
67 };
68
69 // (pts:uint):FlvTag
70 this.metaDataTag = function(pts) {
71 var
72 tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag
73 expGolomb, // :ExpGolomb
74 profile_idc, // :int
75 chroma_format_idc, // :int
76 imax, // :int
77 i, // :int
78
79 pic_order_cnt_type, // :int
80 num_ref_frames_in_pic_order_cnt_cycle, // :uint
81
82 pic_width_in_mbs_minus1, // :int
83 pic_height_in_map_units_minus1, // :int
84
85 frame_mbs_only_flag, // :int
86 frame_cropping_flag, // :Boolean
87
88 frame_crop_left_offset = 0, // :int
89 frame_crop_right_offset = 0, // :int
90 frame_crop_top_offset = 0, // :int
91 frame_crop_bottom_offset = 0, // :int
92
93 width,
94 height;
95
96 tag.dts = pts;
97 tag.pts = pts;
98 expGolomb = new ExpGolomb(this.getSps0Rbsp());
99
100 // :int = expGolomb.readUnsignedByte(); // profile_idc u(8)
101 profile_idc = expGolomb.readUnsignedByte();
102
103 // constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8)
104 expGolomb.skipBits(16);
105
106 // seq_parameter_set_id
107 expGolomb.skipUnsignedExpGolomb();
108
109 if (profile_idc === 100 ||
110 profile_idc === 110 ||
111 profile_idc === 122 ||
112 profile_idc === 244 ||
113 profile_idc === 44 ||
114 profile_idc === 83 ||
115 profile_idc === 86 ||
116 profile_idc === 118 ||
117 profile_idc === 128) {
118 chroma_format_idc = expGolomb.readUnsignedExpGolomb();
119 if (3 === chroma_format_idc) {
120 expGolomb.skipBits(1); // separate_colour_plane_flag
121 }
122 expGolomb.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
123 expGolomb.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
124 expGolomb.skipBits(1); // qpprime_y_zero_transform_bypass_flag
125 if (expGolomb.readBoolean()) { // seq_scaling_matrix_present_flag
126 imax = (chroma_format_idc !== 3) ? 8 : 12;
127 for (i = 0 ; i < imax ; ++i) {
128 if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ]
129 if (i < 6) {
130 this.scaling_list(16, expGolomb);
131 } else {
132 this.scaling_list(64, expGolomb);
133 }
134 }
135 }
136 }
137 }
138
139 expGolomb.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
140 pic_order_cnt_type = expGolomb.readUnsignedExpGolomb();
141
142 if ( 0 === pic_order_cnt_type ) {
143 expGolomb.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
144 } else if ( 1 === pic_order_cnt_type ) {
145 expGolomb.skipBits(1); // delta_pic_order_always_zero_flag
146 expGolomb.skipExpGolomb(); // offset_for_non_ref_pic
147 expGolomb.skipExpGolomb(); // offset_for_top_to_bottom_field
148 num_ref_frames_in_pic_order_cnt_cycle = expGolomb.readUnsignedExpGolomb();
149 for(i = 0 ; i < num_ref_frames_in_pic_order_cnt_cycle ; ++i) {
150 expGolomb.skipExpGolomb(); // offset_for_ref_frame[ i ]
151 }
152 }
153
154 expGolomb.skipUnsignedExpGolomb(); // max_num_ref_frames
155 expGolomb.skipBits(1); // gaps_in_frame_num_value_allowed_flag
156 pic_width_in_mbs_minus1 = expGolomb.readUnsignedExpGolomb();
157 pic_height_in_map_units_minus1 = expGolomb.readUnsignedExpGolomb();
158
159 frame_mbs_only_flag = expGolomb.readBits(1);
160 if (0 === frame_mbs_only_flag) {
161 expGolomb.skipBits(1); // mb_adaptive_frame_field_flag
162 }
163
164 expGolomb.skipBits(1); // direct_8x8_inference_flag
165 frame_cropping_flag = expGolomb.readBoolean();
166 if (frame_cropping_flag) {
167 frame_crop_left_offset = expGolomb.readUnsignedExpGolomb();
168 frame_crop_right_offset = expGolomb.readUnsignedExpGolomb();
169 frame_crop_top_offset = expGolomb.readUnsignedExpGolomb();
170 frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb();
171 }
172
173 width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_left_offset * 2 - frame_crop_right_offset * 2;
174 height = ((2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2);
175
176 tag.writeMetaDataDouble("videocodecid", 7);
177 tag.writeMetaDataDouble("width", width);
178 tag.writeMetaDataDouble("height", height);
179 // tag.writeMetaDataDouble("videodatarate", 0 );
180 // tag.writeMetaDataDouble("framerate", 0);
181
182 return tag;
183 };
184
185 // (pts:uint):FlvTag
186 this.extraDataTag = function(pts) {
187 var
188 i,
189 tag = new FlvTag(FlvTag.VIDEO_TAG, true);
190
191 tag.dts = pts;
192 tag.pts = pts;
193
194 tag.writeByte(0x01);// version
195 tag.writeByte(this.sps[0][1]);// profile
196 tag.writeByte(this.sps[0][2]);// compatibility
197 tag.writeByte(this.sps[0][3]);// level
198 tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
199 tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits)
200 tag.writeShort( this.sps[0].length ); // data of SPS
201 tag.writeBytes( this.sps[0] ); // SPS
202
203 tag.writeByte( this.pps.length ); // num of PPS (will there ever be more that 1 PPS?)
204 for (i = 0 ; i < this.pps.length ; ++i) {
205 tag.writeShort(this.pps[i].length); // 2 bytes for length of PPS
206 tag.writeBytes(this.pps[i]); // data of PPS
207 }
208
209 return tag;
210 };
211 },
212
213 NALUnitType; 6 NALUnitType;
214 7
215 /** 8 /**
...@@ -241,235 +34,246 @@ ...@@ -241,235 +34,246 @@
241 end_of_stream_rbsp: 11 34 end_of_stream_rbsp: 11
242 }; 35 };
243 36
244 window.videojs.Hls.H264Stream = function() { 37 window.videojs.Hls.H264Stream = H264Stream = function() {
245 var 38 this._next_pts = 0; // :uint;
246 next_pts, // :uint; 39 this._next_dts = 0; // :uint;
247 next_dts, // :uint; 40 this._pts_offset = 0; // :int
248 pts_offset, // :int 41
42 this._h264Frame = null; // :FlvTag
249 43
250 h264Frame, // :FlvTag 44 this._oldExtraData = new H264ExtraData(); // :H264ExtraData
45 this._newExtraData = new H264ExtraData(); // :H264ExtraData
251 46
252 oldExtraData = new H264ExtraData(), // :H264ExtraData 47 this._nalUnitType = -1; // :int
253 newExtraData = new H264ExtraData(), // :H264ExtraData
254 48
255 nalUnitType = -1, // :int 49 this._state = 0; // :uint;
256 50
257 state; // :uint; 51 this._nextFrameKeyFrame = false;
258 52
259 this.tags = []; 53 this.tags = [];
260 54
261 //(pts:uint):void 55 };
262 this.setTimeStampOffset = function(pts) { 56 //(pts:uint):void
263 pts_offset = pts; 57 H264Stream.prototype.setTimeStampOffset = function(pts) {
264 }; 58 this._pts_offset = pts;
59 };
265 60
266 //(pts:uint, dts:uint, dataAligned:Boolean):void 61 //(pts:uint, dts:uint, dataAligned:Boolean):void
267 this.setNextTimeStamp = function(pts, dts, dataAligned) { 62 H264Stream.prototype.setNextTimeStamp = function(pts, dts, dataAligned) {
268 // We could end up with a DTS less than 0 here. We need to deal with that! 63 // We could end up with a DTS less than 0 here. We need to deal with that!
269 next_pts = pts - pts_offset; 64 this._next_pts = pts - this._pts_offset;
270 next_dts = dts - pts_offset; 65 this._next_dts = dts - this._pts_offset;
271 66
272 // If data is aligned, flush all internal buffers 67 // If data is aligned, flush all internal buffers
273 if (dataAligned) { 68 if (dataAligned) {
274 this.finishFrame(); 69 this.finishFrame();
70 }
71 };
72
73 H264Stream.prototype.finishFrame = function() {
74 if (this._h264Frame) {
75 // Push SPS before EVERY IDR frame for seeking
76 if (this._newExtraData.extraDataExists()) {
77 this._oldExtraData = this._newExtraData;
78 this._newExtraData = new H264ExtraData();
275 } 79 }
276 };
277
278 this.finishFrame = function() {
279 if (h264Frame) {
280 // Push SPS before EVERY IDR frame for seeking
281 if (newExtraData.extraDataExists()) {
282 oldExtraData = newExtraData;
283 newExtraData = new H264ExtraData();
284 }
285 80
286 // Check if keyframe and the length of tags. 81 // Check if keyframe and the length of tags.
287 // This makes sure we write metadata on the first frame of a segment. 82 // This makes sure we write metadata on the first frame of a segment.
288 if (h264Frame.keyFrame || this.tags.length === 0) { 83 if (this._h264Frame.keyFrame || this.tags.length === 0) {
289 // Push extra data on every IDR frame in case we did a stream change + seek 84 // Push extra data on every IDR frame in case we did a stream change + seek
290 this.tags.push(oldExtraData.metaDataTag(h264Frame.pts)); 85 this.tags.push(this._oldExtraData.metaDataTag(this._h264Frame.pts));
291 this.tags.push(oldExtraData.extraDataTag(h264Frame.pts)); 86 this.tags.push(this._oldExtraData.extraDataTag(this._h264Frame.pts));
292 } 87 }
293 88
294 h264Frame.endNalUnit(); 89 this._h264Frame.endNalUnit();
295 this.tags.push(h264Frame); 90 this.tags.push(this._h264Frame);
296 91
297 } 92 }
298 93
299 h264Frame = null; 94 this._h264Frame = null;
300 nalUnitType = -1; 95 this._nalUnitType = -1;
301 state = 0; 96 this._state = 0;
302 }; 97 };
303
304 // (data:ByteArray, o:int, l:int):void
305 this.writeBytes = function(data, offset, length) {
306 var
307 nalUnitSize, // :uint
308 start, // :uint
309 end, // :uint
310 t; // :int
311
312 // default argument values
313 offset = offset || 0;
314 length = length || 0;
315
316 if (length <= 0) {
317 // data is empty so there's nothing to write
318 return;
319 }
320 98
321 // scan through the bytes until we find the start code (0x000001) for a
322 // NAL unit and then begin writing it out
323 // strip NAL start codes as we go
324 switch (state) {
325 default:
326 /* falls through */
327 case 0:
328 state = 1;
329 /* falls through */
330 case 1:
331 // A NAL unit may be split across two TS packets. Look back a bit to
332 // make sure the prefix of the start code wasn't already written out.
333 if (data[offset] <= 1) {
334 nalUnitSize = h264Frame ? h264Frame.nalUnitSize() : 0;
335 if (nalUnitSize >= 1 && h264Frame.negIndex(1) === 0) {
336 // ?? ?? 00 | O[01] ?? ??
337 if (data[offset] === 1 &&
338 nalUnitSize >= 2 &&
339 h264Frame.negIndex(2) === 0) {
340 // ?? 00 00 : 01
341 if (3 <= nalUnitSize && 0 === h264Frame.negIndex(3)) {
342 h264Frame.length -= 3; // 00 00 00 : 01
343 } else {
344 h264Frame.length -= 2; // 00 00 : 01
345 }
346
347 state = 3;
348 return this.writeBytes(data, offset + 1, length - 1);
349 }
350 99
351 if (length > 1 && data[offset] === 0 && data[offset + 1] === 1) { 100 H264Stream.prototype.setNextFrameKeyFrame = function() {
352 // ?? 00 | 00 01 101 this._nextFrameKeyFrame = true;
353 if (nalUnitSize >= 2 && h264Frame.negIndex(2) === 0) { 102 };
354 h264Frame.length -= 2; // 00 00 : 00 01
355 } else {
356 h264Frame.length -= 1; // 00 : 00 01
357 }
358 103
359 state = 3; 104 // (data:ByteArray, o:int, l:int):void
360 return this.writeBytes(data, offset + 2, length - 2); 105 H264Stream.prototype.writeBytes = function(data, offset, length) {
106 var
107 nalUnitSize, // :uint
108 start, // :uint
109 end, // :uint
110 t; // :int
111
112 // default argument values
113 offset = offset || 0;
114 length = length || 0;
115
116 if (length <= 0) {
117 // data is empty so there's nothing to write
118 return;
119 }
120
121 // scan through the bytes until we find the start code (0x000001) for a
122 // NAL unit and then begin writing it out
123 // strip NAL start codes as we go
124 switch (this._state) {
125 default:
126 /* falls through */
127 case 0:
128 this._state = 1;
129 /* falls through */
130 case 1:
131 // A NAL unit may be split across two TS packets. Look back a bit to
132 // make sure the prefix of the start code wasn't already written out.
133 if (data[offset] <= 1) {
134 nalUnitSize = this._h264Frame ? this._h264Frame.nalUnitSize() : 0;
135 if (nalUnitSize >= 1 && this._h264Frame.negIndex(1) === 0) {
136 // ?? ?? 00 | O[01] ?? ??
137 if (data[offset] === 1 &&
138 nalUnitSize >= 2 &&
139 this._h264Frame.negIndex(2) === 0) {
140 // ?? 00 00 : 01
141 if (3 <= nalUnitSize && 0 === this._h264Frame.negIndex(3)) {
142 this._h264Frame.length -= 3; // 00 00 00 : 01
143 } else {
144 this._h264Frame.length -= 2; // 00 00 : 01
361 } 145 }
362 146
363 if (length > 2 && 147 this._state = 3;
364 data[offset] === 0 && 148 return this.writeBytes(data, offset + 1, length - 1);
365 data[offset + 1] === 0 && 149 }
366 data[offset + 2] === 1) { 150
367 // 00 : 00 00 01 151 if (length > 1 && data[offset] === 0 && data[offset + 1] === 1) {
368 // h264Frame.length -= 1; 152 // ?? 00 | 00 01
369 state = 3; 153 if (nalUnitSize >= 2 && this._h264Frame.negIndex(2) === 0) {
370 return this.writeBytes(data, offset + 3, length - 3); 154 this._h264Frame.length -= 2; // 00 00 : 00 01
155 } else {
156 this._h264Frame.length -= 1; // 00 : 00 01
371 } 157 }
158
159 this._state = 3;
160 return this.writeBytes(data, offset + 2, length - 2);
161 }
162
163 if (length > 2 &&
164 data[offset] === 0 &&
165 data[offset + 1] === 0 &&
166 data[offset + 2] === 1) {
167 // 00 : 00 00 01
168 // this._h264Frame.length -= 1;
169 this._state = 3;
170 return this.writeBytes(data, offset + 3, length - 3);
372 } 171 }
373 } 172 }
374 // allow fall through if the above fails, we may end up checking a few 173 }
375 // bytes a second time. But that case will be VERY rare 174 // allow fall through if the above fails, we may end up checking a few
376 state = 2; 175 // bytes a second time. But that case will be VERY rare
377 /* falls through */ 176 this._state = 2;
378 case 2: 177 /* falls through */
379 // Look for start codes in the data from the current offset forward 178 case 2:
380 start = offset; 179 // Look for start codes in the data from the current offset forward
381 end = start + length; 180 start = offset;
382 for (t = end - 3; offset < t;) { 181 end = start + length;
383 if (data[offset + 2] > 1) { 182 for (t = end - 3; offset < t;) {
384 // if data[offset + 2] is greater than 1, there is no way a start 183 if (data[offset + 2] > 1) {
385 // code can begin before offset + 3 184 // if data[offset + 2] is greater than 1, there is no way a start
386 offset += 3; 185 // code can begin before offset + 3
387 } else if (data[offset + 1] !== 0) { 186 offset += 3;
388 offset += 2; 187 } else if (data[offset + 1] !== 0) {
389 } else if (data[offset] !== 0) { 188 offset += 2;
390 offset += 1; 189 } else if (data[offset] !== 0) {
391 } else { 190 offset += 1;
392 // If we get here we have 00 00 00 or 00 00 01 191 } else {
393 if (data[offset + 2] === 1) { 192 // If we get here we have 00 00 00 or 00 00 01
394 if (offset > start) { 193 if (data[offset + 2] === 1) {
395 h264Frame.writeBytes(data, start, offset - start); 194 if (offset > start) {
396 } 195 this._h264Frame.writeBytes(data, start, offset - start);
397 state = 3;
398 offset += 3;
399 return this.writeBytes(data, offset, end - offset);
400 } 196 }
197 this._state = 3;
198 offset += 3;
199 return this.writeBytes(data, offset, end - offset);
200 }
401 201
402 if (end - offset >= 4 && 202 if (end - offset >= 4 &&
403 data[offset + 2] === 0 && 203 data[offset + 2] === 0 &&
404 data[offset + 3] === 1) { 204 data[offset + 3] === 1) {
405 if (offset > start) { 205 if (offset > start) {
406 h264Frame.writeBytes(data, start, offset - start); 206 this._h264Frame.writeBytes(data, start, offset - start);
407 }
408 state = 3;
409 offset += 4;
410 return this.writeBytes(data, offset, end - offset);
411 } 207 }
412 208 this._state = 3;
413 // We are at the end of the buffer, or we have 3 NULLS followed by 209 offset += 4;
414 // something that is not a 1, either way we can step forward by at 210 return this.writeBytes(data, offset, end - offset);
415 // least 3
416 offset += 3;
417 } 211 }
212
213 // We are at the end of the buffer, or we have 3 NULLS followed by
214 // something that is not a 1, either way we can step forward by at
215 // least 3
216 offset += 3;
418 } 217 }
218 }
419 219
420 // We did not find any start codes. Try again next packet 220 // We did not find any start codes. Try again next packet
421 state = 1; 221 this._state = 1;
422 if (h264Frame) { 222 if (this._h264Frame) {
423 h264Frame.writeBytes(data, start, length); 223 this._h264Frame.writeBytes(data, start, length);
224 }
225 return;
226 case 3:
227 // The next byte is the first byte of a NAL Unit
228
229 if (this._h264Frame) {
230 // we've come to a new NAL unit so finish up the one we've been
231 // working on
232
233 switch (this._nalUnitType) {
234 case NALUnitType.seq_parameter_set_rbsp:
235 this._h264Frame.endNalUnit(this._newExtraData.sps);
236 break;
237 case NALUnitType.pic_parameter_set_rbsp:
238 this._h264Frame.endNalUnit(this._newExtraData.pps);
239 break;
240 case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
241 this._h264Frame.endNalUnit();
242 break;
243 default:
244 this._h264Frame.endNalUnit();
245 break;
424 } 246 }
425 return; 247 }
426 case 3: 248
427 // The next byte is the first byte of a NAL Unit 249 // setup to begin processing the new NAL unit
428 250 this._nalUnitType = data[offset] & 0x1F;
429 if (h264Frame) { 251 if (this._h264Frame) {
430 // we've come to a new NAL unit so finish up the one we've been 252 if (this._nalUnitType === NALUnitType.access_unit_delimiter_rbsp) {
431 // working on 253 // starting a new access unit, flush the previous one
432 254 this.finishFrame();
433 switch (nalUnitType) { 255 } else if (this._nalUnitType === NALUnitType.slice_layer_without_partitioning_rbsp_idr) {
434 case NALUnitType.seq_parameter_set_rbsp: 256 this._h264Frame.keyFrame = true;
435 h264Frame.endNalUnit(newExtraData.sps);
436 break;
437 case NALUnitType.pic_parameter_set_rbsp:
438 h264Frame.endNalUnit(newExtraData.pps);
439 break;
440 case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
441 h264Frame.endNalUnit();
442 break;
443 default:
444 h264Frame.endNalUnit();
445 break;
446 } 257 }
447 } 258 }
448 259
449 // setup to begin processing the new NAL unit 260 // finishFrame may render this._h264Frame null, so we must test again
450 nalUnitType = data[offset] & 0x1F; 261 if (!this._h264Frame) {
451 if (h264Frame) { 262 this._h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
452 if (nalUnitType === NALUnitType.access_unit_delimiter_rbsp) { 263 this._h264Frame.pts = this._next_pts;
453 // starting a new access unit, flush the previous one 264 this._h264Frame.dts = this._next_dts;
454 this.finishFrame();
455 } else if (nalUnitType === NALUnitType.slice_layer_without_partitioning_rbsp_idr) {
456 h264Frame.keyFrame = true;
457 }
458 }
459 265
460 // finishFrame may render h264Frame null, so we must test again 266 if (this._nextFrameKeyFrame) {
461 if (!h264Frame) { 267 this._h264Frame.keyFrame = true;
462 h264Frame = new FlvTag(FlvTag.VIDEO_TAG); 268 this._nextFrameKeyFrame = false;
463 h264Frame.pts = next_pts;
464 h264Frame.dts = next_dts;
465 } 269 }
270 }
466 271
467 h264Frame.startNalUnit(); 272 this._h264Frame.startNalUnit();
468 // We know there will not be an overlapping start code, so we can skip 273 // We know there will not be an overlapping start code, so we can skip
469 // that test 274 // that test
470 state = 2; 275 this._state = 2;
471 return this.writeBytes(data, offset, length); 276 return this.writeBytes(data, offset, length);
472 } // switch 277 } // switch
473 };
474 }; 278 };
475 })(this); 279 })(this);
......
...@@ -214,6 +214,9 @@ ...@@ -214,6 +214,9 @@
214 // adaptation_field_control, whether this header is followed by an 214 // adaptation_field_control, whether this header is followed by an
215 // adaptation field, a payload, or both 215 // adaptation field, a payload, or both
216 afflag = (data[offset + 3] & 0x30 ) >>> 4, 216 afflag = (data[offset + 3] & 0x30 ) >>> 4,
217 adaptationFieldLength,
218 afftemp,
219 randomAccessIndicator,
217 220
218 patTableId, // :int 221 patTableId, // :int
219 patCurrentNextIndicator, // Boolean 222 patCurrentNextIndicator, // Boolean
...@@ -247,7 +250,19 @@ ...@@ -247,7 +250,19 @@
247 // used to specify some forms of timing and control data that we 250 // used to specify some forms of timing and control data that we
248 // do not currently use. 251 // do not currently use.
249 if (afflag > 0x01) { 252 if (afflag > 0x01) {
250 offset += data[offset] + 1; 253 adaptationFieldLength = data[offset];
254
255 if (adaptationFieldLength > 0) {
256 afftemp = data[offset + 1];
257
258 randomAccessIndicator = (afftemp & 0x40) >>> 6;
259
260 if (randomAccessIndicator === 1) {
261 h264Stream.setNextFrameKeyFrame();
262 }
263 }
264
265 offset += adaptationFieldLength + 1;
251 } 266 }
252 267
253 // Handle a Program Association Table (PAT). PATs map PIDs to 268 // Handle a Program Association Table (PAT). PATs map PIDs to
......
...@@ -62,7 +62,10 @@ test('metadata is generated for IDRs after a full NAL unit is written', function ...@@ -62,7 +62,10 @@ test('metadata is generated for IDRs after a full NAL unit is written', function
62 62
63 test('starting PTS values can be negative', function() { 63 test('starting PTS values can be negative', function() {
64 var 64 var
65 h264Stream = new videojs.Hls.H264Stream(), 65 H264ExtraData = videojs.Hls.H264ExtraData,
66 oldExtraData = H264ExtraData.prototype.extraDataTag,
67 oldMetadata = H264ExtraData.prototype.metaDataTag,
68 h264Stream,
66 accessUnitDelimiter = new Uint8Array([ 69 accessUnitDelimiter = new Uint8Array([
67 0x00, 70 0x00,
68 0x00, 71 0x00,
...@@ -70,8 +73,14 @@ test('starting PTS values can be negative', function() { ...@@ -70,8 +73,14 @@ test('starting PTS values can be negative', function() {
70 nalUnitTypes.access_unit_delimiter_rbsp 73 nalUnitTypes.access_unit_delimiter_rbsp
71 ]); 74 ]);
72 75
73 // add a "tag" to the stream so that it doesn't try and parse metadata 76 H264ExtraData.prototype.extraDataTag = function() {
74 h264Stream.tags.push('spacer tag'); 77 return 'extraDataTag';
78 };
79 H264ExtraData.prototype.metaDataTag = function() {
80 return 'metaDataTag';
81 };
82
83 h264Stream = new videojs.Hls.H264Stream();
75 84
76 h264Stream.setTimeStampOffset(-100); 85 h264Stream.setTimeStampOffset(-100);
77 h264Stream.setNextTimeStamp(-100, -100, true); 86 h264Stream.setNextTimeStamp(-100, -100, true);
...@@ -83,6 +92,8 @@ test('starting PTS values can be negative', function() { ...@@ -83,6 +92,8 @@ test('starting PTS values can be negative', function() {
83 // flush out the last tag 92 // flush out the last tag
84 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength); 93 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
85 94
95 // shift the metadata and extradata tags out, since we don't care about them here
96 h264Stream.tags.shift();
86 h264Stream.tags.shift(); 97 h264Stream.tags.shift();
87 98
88 strictEqual(h264Stream.tags.length, 3, 'three tags are ready'); 99 strictEqual(h264Stream.tags.length, 3, 'three tags are ready');
...@@ -93,6 +104,57 @@ test('starting PTS values can be negative', function() { ...@@ -93,6 +104,57 @@ test('starting PTS values can be negative', function() {
93 104
94 strictEqual(h264Stream.tags[2].pts, 100, 'the third PTS is 100'); 105 strictEqual(h264Stream.tags[2].pts, 100, 'the third PTS is 100');
95 strictEqual(h264Stream.tags[2].dts, 100, 'the third DTS is 100'); 106 strictEqual(h264Stream.tags[2].dts, 100, 'the third DTS is 100');
107
108 H264ExtraData.prototype.extraDataTag = oldExtraData;
109 H264ExtraData.prototype.metaDataTag = oldMetadata;
110 });
111
112 test('make sure we add metadata and extra data at the beginning of a stream', function() {
113 var
114 H264ExtraData = videojs.Hls.H264ExtraData,
115 oldExtraData = H264ExtraData.prototype.extraDataTag,
116 oldMetadata = H264ExtraData.prototype.metaDataTag,
117 h264Stream,
118 accessUnitDelimiter = new Uint8Array([
119 0x00,
120 0x00,
121 0x01,
122 nalUnitTypes.access_unit_delimiter_rbsp
123 ]);
124
125 H264ExtraData.prototype.extraDataTag = function() {
126 return 'extraDataTag';
127 };
128 H264ExtraData.prototype.metaDataTag = function() {
129 return 'metaDataTag';
130 };
131
132 h264Stream = new videojs.Hls.H264Stream();
133
134 h264Stream.setTimeStampOffset(0);
135 h264Stream.setNextTimeStamp(0, 0, true);
136 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
137
138 // make sure that keyFrame is set to false but that we don't have any tags currently written out
139 h264Stream._h264Frame.keyFrame = false;
140 h264Stream.tags = [];
141
142 h264Stream.setNextTimeStamp(5, 5, true);
143 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
144 // flush out the last tag
145 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
146
147 strictEqual(h264Stream.tags.length, 4, 'three tags are ready');
148 strictEqual(h264Stream.tags[0], 'metaDataTag', 'the first tag is the metaDataTag');
149 strictEqual(h264Stream.tags[1], 'extraDataTag', 'the second tag is the extraDataTag');
150
151 strictEqual(h264Stream.tags[2].pts, 0, 'the first PTS is 0');
152 strictEqual(h264Stream.tags[2].dts, 0, 'the first DTS is 0');
153 strictEqual(h264Stream.tags[3].pts, 5, 'the second PTS is 5');
154 strictEqual(h264Stream.tags[3].dts, 5, 'the second DTS is 5');
155
156 H264ExtraData.prototype.extraDataTag = oldExtraData;
157 H264ExtraData.prototype.metaDataTag = oldMetadata;
96 }); 158 });
97 159
98 })(window.videojs); 160 })(window.videojs);
......
...@@ -83,6 +83,7 @@ module.exports = function(config) { ...@@ -83,6 +83,7 @@ module.exports = function(config) {
83 '../src/stream.js', 83 '../src/stream.js',
84 '../src/flv-tag.js', 84 '../src/flv-tag.js',
85 '../src/exp-golomb.js', 85 '../src/exp-golomb.js',
86 '../src/h264-extradata.js',
86 '../src/h264-stream.js', 87 '../src/h264-stream.js',
87 '../src/aac-stream.js', 88 '../src/aac-stream.js',
88 '../src/metadata-stream.js', 89 '../src/metadata-stream.js',
......
...@@ -47,6 +47,7 @@ module.exports = function(config) { ...@@ -47,6 +47,7 @@ module.exports = function(config) {
47 '../src/stream.js', 47 '../src/stream.js',
48 '../src/flv-tag.js', 48 '../src/flv-tag.js',
49 '../src/exp-golomb.js', 49 '../src/exp-golomb.js',
50 '../src/h264-extradata.js',
50 '../src/h264-stream.js', 51 '../src/h264-stream.js',
51 '../src/aac-stream.js', 52 '../src/aac-stream.js',
52 '../src/metadata-stream.js', 53 '../src/metadata-stream.js',
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
24 <script src="../src/stream.js"></script> 24 <script src="../src/stream.js"></script>
25 <script src="../src/flv-tag.js"></script> 25 <script src="../src/flv-tag.js"></script>
26 <script src="../src/exp-golomb.js"></script> 26 <script src="../src/exp-golomb.js"></script>
27 <script src="../src/h264-extradata.js"></script>
27 <script src="../src/h264-stream.js"></script> 28 <script src="../src/h264-stream.js"></script>
28 <script src="../src/aac-stream.js"></script> 29 <script src="../src/aac-stream.js"></script>
29 <script src="../src/metadata-stream.js"></script> 30 <script src="../src/metadata-stream.js"></script>
......