1b2da15a by David LaPalomento

Handle negative PTS values on the TS segments

Overwrite setNextTimeStamp after the first invocation, capturing the first PTS value. Write all FLV PTS and DTS values as offsets from that.
1 parent adb7457d
...@@ -20,7 +20,7 @@ var ...@@ -20,7 +20,7 @@ var
20 window.videojs.hls.AacStream = function() { 20 window.videojs.hls.AacStream = function() {
21 var 21 var
22 next_pts, // :uint 22 next_pts, // :uint
23 pts_delta = -1, // :int 23 pts_offset, // :int
24 state, // :uint 24 state, // :uint
25 pes_length, // :int 25 pes_length, // :int
26 26
...@@ -39,12 +39,13 @@ window.videojs.hls.AacStream = function() { ...@@ -39,12 +39,13 @@ window.videojs.hls.AacStream = function() {
39 39
40 // (pts:uint, pes_size:int, dataAligned:Boolean):void 40 // (pts:uint, pes_size:int, dataAligned:Boolean):void
41 this.setNextTimeStamp = function(pts, pes_size, dataAligned) { 41 this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
42 if (0 > pts_delta) {
43 // We assume the very first pts is less than 0x80000000
44 pts_delta = pts;
45 }
46 42
47 next_pts = pts - pts_delta; 43 // on the first invocation, capture the starting PTS value
44 pts_offset = pts;
45
46 // on subsequent invocations, calculate the PTS based on the starting offset
47 this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
48 next_pts = pts - pts_offset;
48 pes_length = pes_size; 49 pes_length = pes_size;
49 50
50 // If data is aligned, flush all internal buffers 51 // If data is aligned, flush all internal buffers
...@@ -53,6 +54,9 @@ window.videojs.hls.AacStream = function() { ...@@ -53,6 +54,9 @@ window.videojs.hls.AacStream = function() {
53 } 54 }
54 }; 55 };
55 56
57 this.setNextTimeStamp(pts, pes_size, dataAligned);
58 };
59
56 // (data:ByteArray, o:int = 0, l:int = 0):void 60 // (data:ByteArray, o:int = 0, l:int = 0):void
57 this.writeBytes = function(data, offset, length) { 61 this.writeBytes = function(data, offset, length) {
58 var 62 var
......
...@@ -253,7 +253,7 @@ ...@@ -253,7 +253,7 @@
253 var 253 var
254 next_pts, // :uint; 254 next_pts, // :uint;
255 next_dts, // :uint; 255 next_dts, // :uint;
256 pts_delta = -1, // :int 256 pts_offset, // :int
257 257
258 h264Frame, // :FlvTag 258 h264Frame, // :FlvTag
259 259
...@@ -268,15 +268,14 @@ ...@@ -268,15 +268,14 @@
268 268
269 //(pts:uint, dts:uint, dataAligned:Boolean):void 269 //(pts:uint, dts:uint, dataAligned:Boolean):void
270 this.setNextTimeStamp = function(pts, dts, dataAligned) { 270 this.setNextTimeStamp = function(pts, dts, dataAligned) {
271 if (pts_delta < 0) { 271 // on the first invocation, capture the starting PTS value
272 // We assume the very first pts is less than 0x8FFFFFFF (max signed 272 pts_offset = pts;
273 // int32)
274 pts_delta = pts;
275 }
276 273
274 // on subsequent invocations, calculate the PTS based on the starting offset
275 this.setNextTimeStamp = function(pts, dts, dataAligned) {
277 // We could end up with a DTS less than 0 here. We need to deal with that! 276 // We could end up with a DTS less than 0 here. We need to deal with that!
278 next_pts = pts - pts_delta; 277 next_pts = pts - pts_offset;
279 next_dts = dts - pts_delta; 278 next_dts = dts - pts_offset;
280 279
281 // If data is aligned, flush all internal buffers 280 // If data is aligned, flush all internal buffers
282 if (dataAligned) { 281 if (dataAligned) {
...@@ -284,6 +283,9 @@ ...@@ -284,6 +283,9 @@
284 } 283 }
285 }; 284 };
286 285
286 this.setNextTimeStamp(pts, dts, dataAligned);
287 };
288
287 this.finishFrame = function() { 289 this.finishFrame = function() {
288 if (h264Frame) { 290 if (h264Frame) {
289 // Push SPS before EVERY IDR frame for seeking 291 // Push SPS before EVERY IDR frame for seeking
......
...@@ -18,8 +18,7 @@ ...@@ -18,8 +18,7 @@
18 streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH), 18 streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH),
19 streamBufferByteCount = 0, 19 streamBufferByteCount = 0,
20 h264Stream = new H264Stream(), 20 h264Stream = new H264Stream(),
21 aacStream = new AacStream(), 21 aacStream = new AacStream();
22 seekToKeyFrame = false;
23 22
24 // expose the stream metadata 23 // expose the stream metadata
25 self.stream = { 24 self.stream = {
...@@ -84,60 +83,35 @@ ...@@ -84,60 +83,35 @@
84 self.flushTags = function() { 83 self.flushTags = function() {
85 h264Stream.finishFrame(); 84 h264Stream.finishFrame();
86 }; 85 };
87 self.doSeek = function() {
88 self.flushTags();
89 aacStream.tags.length = 0;
90 h264Stream.tags.length = 0;
91 seekToKeyFrame = true;
92 };
93 86
87 /**
88 * Returns whether a call to `getNextTag()` will be successful.
89 * @return {boolean} whether there is at least one transmuxed FLV
90 * tag ready
91 */
94 self.tagsAvailable = function() { // :int { 92 self.tagsAvailable = function() { // :int {
95 var i, pts; // :uint
96
97 if (seekToKeyFrame) {
98 for (i = 0 ; i < h264Stream.tags.length && seekToKeyFrame; ++i) {
99 if (h264Stream.tags[i].keyFrame) {
100 seekToKeyFrame = false; // We found, a keyframe, stop seeking
101 }
102 }
103
104 if (seekToKeyFrame) {
105 // we didnt find a keyframe. yet
106 h264Stream.tags.length = 0;
107 return 0;
108 }
109
110 // TODO we MAY need to use dts, not pts
111 h264Stream.tags = h264Stream.tags.slice(i);
112 pts = h264Stream.tags[0].pts;
113
114 // Remove any audio before the found keyframe
115 while( 0 < aacStream.tags.length && pts > aacStream.tags[0].pts ) {
116 aacStream.tags.shift();
117 }
118 }
119
120 return h264Stream.tags.length + aacStream.tags.length; 93 return h264Stream.tags.length + aacStream.tags.length;
121 }; 94 };
122 95
123 self.getNextTag = function() { // :ByteArray { 96 /**
124 var tag; // :FlvTag; // return tags in approximate dts order 97 * Returns the next tag in decoder-timestamp (DTS) order.
125 98 * @returns {object} the next tag to decoded.
126 if (0 === self.tagsAvailable()) { 99 */
127 throw new Error("getNextTag() called when 0 == tagsAvailable()"); 100 self.getNextTag = function() {
128 } 101 var tag;
129 102
130 if (0 < h264Stream.tags.length) { 103 if (!h264Stream.tags.length) {
131 if (0 < aacStream.tags.length && aacStream.tags[0].dts < h264Stream.tags[0].dts) { 104 // only audio tags remain
132 tag = aacStream.tags.shift(); 105 tag = aacStream.tags.shift();
133 } else { 106 } else if (!aacStream.tags.length) {
107 // only video tags remain
134 tag = h264Stream.tags.shift(); 108 tag = h264Stream.tags.shift();
135 } 109 } else if (aacStream.tags[0].dts < h264Stream.tags[0].dts) {
136 } else if ( 0 < aacStream.tags.length ) { 110 // audio should be decoded next
137 tag = aacStream.tags.shift(); 111 tag = aacStream.tags.shift();
138 } else { 112 } else {
139 // We dont have any tags available to return 113 // video should be decoded next
140 return new Uint8Array(); 114 tag = h264Stream.tags.shift();
141 } 115 }
142 116
143 return tag.finalize(); 117 return tag.finalize();
......
...@@ -59,4 +59,34 @@ test('metadata is generated for IDRs after a full NAL unit is written', function ...@@ -59,4 +59,34 @@ test('metadata is generated for IDRs after a full NAL unit is written', function
59 'picture parameter set is written'); 59 'picture parameter set is written');
60 ok(h264Stream.tags[2].keyFrame, 'key frame is written'); 60 ok(h264Stream.tags[2].keyFrame, 'key frame is written');
61 }); 61 });
62
63 test('starting PTS values can be negative', function() {
64 var
65 h264Stream = new videojs.hls.H264Stream(),
66 accessUnitDelimiter = new Uint8Array([
67 0x00,
68 0x00,
69 0x01,
70 nalUnitTypes.access_unit_delimiter_rbsp
71 ]);
72
73 h264Stream.setNextTimeStamp(-100, -100, true);
74 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
75 h264Stream.setNextTimeStamp(-99, -99, true);
76 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
77 h264Stream.setNextTimeStamp(0, 0, true);
78 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
79 // flush out the last tag
80 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
81
82 strictEqual(h264Stream.tags.length, 3, 'three tags are ready');
83 strictEqual(h264Stream.tags[0].pts, 0, 'the first PTS is zero');
84 strictEqual(h264Stream.tags[0].dts, 0, 'the first DTS is zero');
85 strictEqual(h264Stream.tags[1].pts, 1, 'the second PTS is one');
86 strictEqual(h264Stream.tags[1].dts, 1, 'the second DTS is one');
87
88 strictEqual(h264Stream.tags[2].pts, 100, 'the third PTS is 100');
89 strictEqual(h264Stream.tags[2].dts, 100, 'the third DTS is 100');
90 });
91
62 })(window.videojs); 92 })(window.videojs);
......