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 })();
...@@ -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>
......