h264-stream.js
8.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
(function(window) {
var
FlvTag = window.videojs.Hls.FlvTag,
H264ExtraData = window.videojs.Hls.H264ExtraData,
H264Stream,
NALUnitType;
/**
* Network Abstraction Layer (NAL) units are the packets of an H264
* stream. NAL units are divided into types based on their payload
* data. Each type has a unique numeric identifier.
*
* NAL unit
* |- NAL header -|------ RBSP ------|
*
* NAL unit: Network abstraction layer unit. The combination of a NAL
* header and an RBSP.
* NAL header: the encapsulation unit for transport-specific metadata in
* an h264 stream. Exactly one byte.
*/
// incomplete, see Table 7.1 of ITU-T H.264 for 12-32
window.videojs.Hls.NALUnitType = NALUnitType = {
unspecified: 0,
slice_layer_without_partitioning_rbsp_non_idr: 1,
slice_data_partition_a_layer_rbsp: 2,
slice_data_partition_b_layer_rbsp: 3,
slice_data_partition_c_layer_rbsp: 4,
slice_layer_without_partitioning_rbsp_idr: 5,
sei_rbsp: 6,
seq_parameter_set_rbsp: 7,
pic_parameter_set_rbsp: 8,
access_unit_delimiter_rbsp: 9,
end_of_seq_rbsp: 10,
end_of_stream_rbsp: 11
};
window.videojs.Hls.H264Stream = H264Stream = function() {
this._next_pts = 0; // :uint;
this._next_dts = 0; // :uint;
this._pts_offset = 0; // :int
this._h264Frame = null; // :FlvTag
this._oldExtraData = new H264ExtraData(); // :H264ExtraData
this._newExtraData = new H264ExtraData(); // :H264ExtraData
this._nalUnitType = -1; // :int
this._state = 0; // :uint;
this._nextFrameKeyFrame = false;
this.tags = [];
};
//(pts:uint):void
H264Stream.prototype.setTimeStampOffset = function(pts) {
this._pts_offset = pts;
};
//(pts:uint, dts:uint, dataAligned:Boolean):void
H264Stream.prototype.setNextTimeStamp = function(pts, dts, dataAligned) {
// We could end up with a DTS less than 0 here. We need to deal with that!
this._next_pts = pts - this._pts_offset;
this._next_dts = dts - this._pts_offset;
// If data is aligned, flush all internal buffers
if (dataAligned) {
this.finishFrame();
}
};
H264Stream.prototype.finishFrame = function() {
if (this._h264Frame) {
// Push SPS before EVERY IDR frame for seeking
if (this._newExtraData.extraDataExists()) {
this._oldExtraData = this._newExtraData;
this._newExtraData = new H264ExtraData();
}
// Check if keyframe and the length of tags.
// This makes sure we write metadata on the first frame of a segment.
if (this._h264Frame.keyFrame || this.tags.length === 0) {
// Push extra data on every IDR frame in case we did a stream change + seek
this.tags.push(this._oldExtraData.metaDataTag(this._h264Frame.pts));
this.tags.push(this._oldExtraData.extraDataTag(this._h264Frame.pts));
}
this._h264Frame.endNalUnit();
this.tags.push(this._h264Frame);
}
this._h264Frame = null;
this._nalUnitType = -1;
this._state = 0;
};
H264Stream.prototype.setNextFrameKeyFrame = function() {
this._nextFrameKeyFrame = true;
};
// (data:ByteArray, o:int, l:int):void
H264Stream.prototype.writeBytes = function(data, offset, length) {
var
nalUnitSize, // :uint
start, // :uint
end, // :uint
t; // :int
// default argument values
offset = offset || 0;
length = length || 0;
if (length <= 0) {
// data is empty so there's nothing to write
return;
}
// scan through the bytes until we find the start code (0x000001) for a
// NAL unit and then begin writing it out
// strip NAL start codes as we go
switch (this._state) {
default:
/* falls through */
case 0:
this._state = 1;
/* falls through */
case 1:
// A NAL unit may be split across two TS packets. Look back a bit to
// make sure the prefix of the start code wasn't already written out.
if (data[offset] <= 1) {
nalUnitSize = this._h264Frame ? this._h264Frame.nalUnitSize() : 0;
if (nalUnitSize >= 1 && this._h264Frame.negIndex(1) === 0) {
// ?? ?? 00 | O[01] ?? ??
if (data[offset] === 1 &&
nalUnitSize >= 2 &&
this._h264Frame.negIndex(2) === 0) {
// ?? 00 00 : 01
if (3 <= nalUnitSize && 0 === this._h264Frame.negIndex(3)) {
this._h264Frame.length -= 3; // 00 00 00 : 01
} else {
this._h264Frame.length -= 2; // 00 00 : 01
}
this._state = 3;
return this.writeBytes(data, offset + 1, length - 1);
}
if (length > 1 && data[offset] === 0 && data[offset + 1] === 1) {
// ?? 00 | 00 01
if (nalUnitSize >= 2 && this._h264Frame.negIndex(2) === 0) {
this._h264Frame.length -= 2; // 00 00 : 00 01
} else {
this._h264Frame.length -= 1; // 00 : 00 01
}
this._state = 3;
return this.writeBytes(data, offset + 2, length - 2);
}
if (length > 2 &&
data[offset] === 0 &&
data[offset + 1] === 0 &&
data[offset + 2] === 1) {
// 00 : 00 00 01
// this._h264Frame.length -= 1;
this._state = 3;
return this.writeBytes(data, offset + 3, length - 3);
}
}
}
// allow fall through if the above fails, we may end up checking a few
// bytes a second time. But that case will be VERY rare
this._state = 2;
/* falls through */
case 2:
// Look for start codes in the data from the current offset forward
start = offset;
end = start + length;
for (t = end - 3; offset < t;) {
if (data[offset + 2] > 1) {
// if data[offset + 2] is greater than 1, there is no way a start
// code can begin before offset + 3
offset += 3;
} else if (data[offset + 1] !== 0) {
offset += 2;
} else if (data[offset] !== 0) {
offset += 1;
} else {
// If we get here we have 00 00 00 or 00 00 01
if (data[offset + 2] === 1) {
if (offset > start) {
this._h264Frame.writeBytes(data, start, offset - start);
}
this._state = 3;
offset += 3;
return this.writeBytes(data, offset, end - offset);
}
if (end - offset >= 4 &&
data[offset + 2] === 0 &&
data[offset + 3] === 1) {
if (offset > start) {
this._h264Frame.writeBytes(data, start, offset - start);
}
this._state = 3;
offset += 4;
return this.writeBytes(data, offset, end - offset);
}
// We are at the end of the buffer, or we have 3 NULLS followed by
// something that is not a 1, either way we can step forward by at
// least 3
offset += 3;
}
}
// We did not find any start codes. Try again next packet
this._state = 1;
if (this._h264Frame) {
this._h264Frame.writeBytes(data, start, length);
}
return;
case 3:
// The next byte is the first byte of a NAL Unit
if (this._h264Frame) {
// we've come to a new NAL unit so finish up the one we've been
// working on
switch (this._nalUnitType) {
case NALUnitType.seq_parameter_set_rbsp:
this._h264Frame.endNalUnit(this._newExtraData.sps);
break;
case NALUnitType.pic_parameter_set_rbsp:
this._h264Frame.endNalUnit(this._newExtraData.pps);
break;
case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
this._h264Frame.endNalUnit();
break;
default:
this._h264Frame.endNalUnit();
break;
}
}
// setup to begin processing the new NAL unit
this._nalUnitType = data[offset] & 0x1F;
if (this._h264Frame) {
if (this._nalUnitType === NALUnitType.access_unit_delimiter_rbsp) {
// starting a new access unit, flush the previous one
this.finishFrame();
} else if (this._nalUnitType === NALUnitType.slice_layer_without_partitioning_rbsp_idr) {
this._h264Frame.keyFrame = true;
}
}
// finishFrame may render this._h264Frame null, so we must test again
if (!this._h264Frame) {
this._h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
this._h264Frame.pts = this._next_pts;
this._h264Frame.dts = this._next_dts;
if (this._nextFrameKeyFrame) {
this._h264Frame.keyFrame = true;
this._nextFrameKeyFrame = false;
}
}
this._h264Frame.startNalUnit();
// We know there will not be an overlapping start code, so we can skip
// that test
this._state = 2;
return this.writeBytes(data, offset, length);
} // switch
};
})(this);