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
1 /* 1 /*
2 * aac-stream 2 * aac-stream
3 * 3 *
4 * 4 *
5 * Copyright (c) 2013 Brightcove 5 * Copyright (c) 2013 Brightcove
6 * All rights reserved. 6 * All rights reserved.
...@@ -18,9 +18,9 @@ var ...@@ -18,9 +18,9 @@ var
18 ]; 18 ];
19 19
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,18 +39,22 @@ window.videojs.hls.AacStream = function() { ...@@ -39,18 +39,22 @@ 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
48 pes_length = pes_size; 44 pts_offset = pts;
49 45
50 // If data is aligned, flush all internal buffers 46 // on subsequent invocations, calculate the PTS based on the starting offset
51 if (dataAligned) { 47 this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
52 state = 0; 48 next_pts = pts - pts_offset;
53 } 49 pes_length = pes_size;
50
51 // If data is aligned, flush all internal buffers
52 if (dataAligned) {
53 state = 0;
54 }
55 };
56
57 this.setNextTimeStamp(pts, pes_size, dataAligned);
54 }; 58 };
55 59
56 // (data:ByteArray, o:int = 0, l:int = 0):void 60 // (data:ByteArray, o:int = 0, l:int = 0):void
...@@ -114,7 +118,7 @@ window.videojs.hls.AacStream = function() { ...@@ -114,7 +118,7 @@ window.videojs.hls.AacStream = function() {
114 offset += 1; 118 offset += 1;
115 state = 3; 119 state = 3;
116 break; 120 break;
117 case 3: 121 case 3:
118 if (offset >= end) { 122 if (offset >= end) {
119 return; 123 return;
120 } 124 }
...@@ -124,7 +128,7 @@ window.videojs.hls.AacStream = function() { ...@@ -124,7 +128,7 @@ window.videojs.hls.AacStream = function() {
124 offset += 1; 128 offset += 1;
125 state = 4; 129 state = 4;
126 break; 130 break;
127 case 4: 131 case 4:
128 if (offset >= end) { 132 if (offset >= end) {
129 return; 133 return;
130 } 134 }
...@@ -143,7 +147,7 @@ window.videojs.hls.AacStream = function() { ...@@ -143,7 +147,7 @@ window.videojs.hls.AacStream = function() {
143 offset += 1; 147 offset += 1;
144 state = 6; 148 state = 6;
145 break; 149 break;
146 case 6: 150 case 6:
147 if (offset >= end) { 151 if (offset >= end) {
148 return; 152 return;
149 } 153 }
...@@ -159,11 +163,11 @@ window.videojs.hls.AacStream = function() { ...@@ -159,11 +163,11 @@ window.videojs.hls.AacStream = function() {
159 aacFrame.dts = next_pts; 163 aacFrame.dts = next_pts;
160 164
161 // AAC is always 10 165 // AAC is always 10
162 aacFrame.writeMetaDataDouble("audiocodecid", 10); 166 aacFrame.writeMetaDataDouble("audiocodecid", 10);
163 aacFrame.writeMetaDataBoolean("stereo", 2 === adtsChanelConfig); 167 aacFrame.writeMetaDataBoolean("stereo", 2 === adtsChanelConfig);
164 aacFrame.writeMetaDataDouble ("audiosamplerate", adtsSampleingRates[adtsSampleingIndex]); 168 aacFrame.writeMetaDataDouble ("audiosamplerate", adtsSampleingRates[adtsSampleingIndex]);
165 // Is AAC always 16 bit? 169 // Is AAC always 16 bit?
166 aacFrame.writeMetaDataDouble ("audiosamplesize", 16); 170 aacFrame.writeMetaDataDouble ("audiosamplesize", 16);
167 171
168 this.tags.push(aacFrame); 172 this.tags.push(aacFrame);
169 173
...@@ -173,7 +177,7 @@ window.videojs.hls.AacStream = function() { ...@@ -173,7 +177,7 @@ window.videojs.hls.AacStream = function() {
173 // For audio, DTS is always the same as PTS. We want to set the DTS 177 // For audio, DTS is always the same as PTS. We want to set the DTS
174 // however so we can compare with video DTS to determine approximate 178 // however so we can compare with video DTS to determine approximate
175 // packet order 179 // packet order
176 aacFrame.pts = next_pts; 180 aacFrame.pts = next_pts;
177 aacFrame.view.setUint16(aacFrame.position, newExtraData); 181 aacFrame.view.setUint16(aacFrame.position, newExtraData);
178 aacFrame.position += 2; 182 aacFrame.position += 2;
179 aacFrame.length = Math.max(aacFrame.length, aacFrame.position); 183 aacFrame.length = Math.max(aacFrame.length, aacFrame.position);
......
...@@ -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,20 +268,22 @@ ...@@ -268,20 +268,22 @@
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) 273
274 pts_delta = pts; 274 // on subsequent invocations, calculate the PTS based on the starting offset
275 } 275 this.setNextTimeStamp = function(pts, dts, dataAligned) {
276 276 // We could end up with a DTS less than 0 here. We need to deal with that!
277 // We could end up with a DTS less than 0 here. We need to deal with that! 277 next_pts = pts - pts_offset;
278 next_pts = pts - pts_delta; 278 next_dts = dts - pts_offset;
279 next_dts = dts - pts_delta; 279
280 // If data is aligned, flush all internal buffers
281 if (dataAligned) {
282 this.finishFrame();
283 }
284 };
280 285
281 // If data is aligned, flush all internal buffers 286 this.setNextTimeStamp(pts, dts, dataAligned);
282 if (dataAligned) {
283 this.finishFrame();
284 }
285 }; 287 };
286 288
287 this.finishFrame = function() { 289 this.finishFrame = function() {
......
...@@ -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) {
134 tag = h264Stream.tags.shift(); 107 // only video tags remain
135 } 108 tag = h264Stream.tags.shift();
136 } else if ( 0 < aacStream.tags.length ) { 109 } else if (aacStream.tags[0].dts < h264Stream.tags[0].dts) {
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);
......