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
window.videojs.hls.AacStream = function() {
var
next_pts, // :uint
pts_delta = -1, // :int
pts_offset, // :int
state, // :uint
pes_length, // :int
......@@ -39,12 +39,13 @@ window.videojs.hls.AacStream = function() {
// (pts:uint, pes_size:int, dataAligned:Boolean):void
this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
if (0 > pts_delta) {
// We assume the very first pts is less than 0x80000000
pts_delta = pts;
}
next_pts = pts - pts_delta;
// on the first invocation, capture the starting PTS value
pts_offset = pts;
// on subsequent invocations, calculate the PTS based on the starting offset
this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
next_pts = pts - pts_offset;
pes_length = pes_size;
// If data is aligned, flush all internal buffers
......@@ -53,6 +54,9 @@ window.videojs.hls.AacStream = function() {
}
};
this.setNextTimeStamp(pts, pes_size, dataAligned);
};
// (data:ByteArray, o:int = 0, l:int = 0):void
this.writeBytes = function(data, offset, length) {
var
......
......@@ -253,7 +253,7 @@
var
next_pts, // :uint;
next_dts, // :uint;
pts_delta = -1, // :int
pts_offset, // :int
h264Frame, // :FlvTag
......@@ -268,15 +268,14 @@
//(pts:uint, dts:uint, dataAligned:Boolean):void
this.setNextTimeStamp = function(pts, dts, dataAligned) {
if (pts_delta < 0) {
// We assume the very first pts is less than 0x8FFFFFFF (max signed
// int32)
pts_delta = pts;
}
// on the first invocation, capture the starting PTS value
pts_offset = pts;
// on subsequent invocations, calculate the PTS based on the starting offset
this.setNextTimeStamp = function(pts, dts, dataAligned) {
// We could end up with a DTS less than 0 here. We need to deal with that!
next_pts = pts - pts_delta;
next_dts = dts - pts_delta;
next_pts = pts - pts_offset;
next_dts = dts - pts_offset;
// If data is aligned, flush all internal buffers
if (dataAligned) {
......@@ -284,6 +283,9 @@
}
};
this.setNextTimeStamp(pts, dts, dataAligned);
};
this.finishFrame = function() {
if (h264Frame) {
// Push SPS before EVERY IDR frame for seeking
......
......@@ -18,8 +18,7 @@
streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH),
streamBufferByteCount = 0,
h264Stream = new H264Stream(),
aacStream = new AacStream(),
seekToKeyFrame = false;
aacStream = new AacStream();
// expose the stream metadata
self.stream = {
......@@ -84,60 +83,35 @@
self.flushTags = function() {
h264Stream.finishFrame();
};
self.doSeek = function() {
self.flushTags();
aacStream.tags.length = 0;
h264Stream.tags.length = 0;
seekToKeyFrame = true;
};
/**
* Returns whether a call to `getNextTag()` will be successful.
* @return {boolean} whether there is at least one transmuxed FLV
* tag ready
*/
self.tagsAvailable = function() { // :int {
var i, pts; // :uint
if (seekToKeyFrame) {
for (i = 0 ; i < h264Stream.tags.length && seekToKeyFrame; ++i) {
if (h264Stream.tags[i].keyFrame) {
seekToKeyFrame = false; // We found, a keyframe, stop seeking
}
}
if (seekToKeyFrame) {
// we didnt find a keyframe. yet
h264Stream.tags.length = 0;
return 0;
}
// TODO we MAY need to use dts, not pts
h264Stream.tags = h264Stream.tags.slice(i);
pts = h264Stream.tags[0].pts;
// Remove any audio before the found keyframe
while( 0 < aacStream.tags.length && pts > aacStream.tags[0].pts ) {
aacStream.tags.shift();
}
}
return h264Stream.tags.length + aacStream.tags.length;
};
self.getNextTag = function() { // :ByteArray {
var tag; // :FlvTag; // return tags in approximate dts order
if (0 === self.tagsAvailable()) {
throw new Error("getNextTag() called when 0 == tagsAvailable()");
}
/**
* Returns the next tag in decoder-timestamp (DTS) order.
* @returns {object} the next tag to decoded.
*/
self.getNextTag = function() {
var tag;
if (0 < h264Stream.tags.length) {
if (0 < aacStream.tags.length && aacStream.tags[0].dts < h264Stream.tags[0].dts) {
if (!h264Stream.tags.length) {
// only audio tags remain
tag = aacStream.tags.shift();
} else {
} else if (!aacStream.tags.length) {
// only video tags remain
tag = h264Stream.tags.shift();
}
} else if ( 0 < aacStream.tags.length ) {
} else if (aacStream.tags[0].dts < h264Stream.tags[0].dts) {
// audio should be decoded next
tag = aacStream.tags.shift();
} else {
// We dont have any tags available to return
return new Uint8Array();
// video should be decoded next
tag = h264Stream.tags.shift();
}
return tag.finalize();
......
......@@ -59,4 +59,34 @@ test('metadata is generated for IDRs after a full NAL unit is written', function
'picture parameter set is written');
ok(h264Stream.tags[2].keyFrame, 'key frame is written');
});
test('starting PTS values can be negative', function() {
var
h264Stream = new videojs.hls.H264Stream(),
accessUnitDelimiter = new Uint8Array([
0x00,
0x00,
0x01,
nalUnitTypes.access_unit_delimiter_rbsp
]);
h264Stream.setNextTimeStamp(-100, -100, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
h264Stream.setNextTimeStamp(-99, -99, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
h264Stream.setNextTimeStamp(0, 0, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
// flush out the last tag
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
strictEqual(h264Stream.tags.length, 3, 'three tags are ready');
strictEqual(h264Stream.tags[0].pts, 0, 'the first PTS is zero');
strictEqual(h264Stream.tags[0].dts, 0, 'the first DTS is zero');
strictEqual(h264Stream.tags[1].pts, 1, 'the second PTS is one');
strictEqual(h264Stream.tags[1].dts, 1, 'the second DTS is one');
strictEqual(h264Stream.tags[2].pts, 100, 'the third PTS is 100');
strictEqual(h264Stream.tags[2].dts, 100, 'the third DTS is 100');
});
})(window.videojs);
......