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.
Showing
4 changed files
with
91 additions
and
81 deletions
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); | ... | ... |
-
Please register or sign in to post a comment