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
/*
* aac-stream
*
*
*
* Copyright (c) 2013 Brightcove
* All rights reserved.
......@@ -18,9 +18,9 @@ var
];
window.videojs.hls.AacStream = function() {
var
var
next_pts, // :uint
pts_delta = -1, // :int
pts_offset, // :int
state, // :uint
pes_length, // :int
......@@ -39,18 +39,22 @@ 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;
pes_length = pes_size;
// on the first invocation, capture the starting PTS value
pts_offset = pts;
// If data is aligned, flush all internal buffers
if (dataAligned) {
state = 0;
}
// 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
if (dataAligned) {
state = 0;
}
};
this.setNextTimeStamp(pts, pes_size, dataAligned);
};
// (data:ByteArray, o:int = 0, l:int = 0):void
......@@ -114,7 +118,7 @@ window.videojs.hls.AacStream = function() {
offset += 1;
state = 3;
break;
case 3:
case 3:
if (offset >= end) {
return;
}
......@@ -124,7 +128,7 @@ window.videojs.hls.AacStream = function() {
offset += 1;
state = 4;
break;
case 4:
case 4:
if (offset >= end) {
return;
}
......@@ -143,7 +147,7 @@ window.videojs.hls.AacStream = function() {
offset += 1;
state = 6;
break;
case 6:
case 6:
if (offset >= end) {
return;
}
......@@ -159,11 +163,11 @@ window.videojs.hls.AacStream = function() {
aacFrame.dts = next_pts;
// AAC is always 10
aacFrame.writeMetaDataDouble("audiocodecid", 10);
aacFrame.writeMetaDataDouble("audiocodecid", 10);
aacFrame.writeMetaDataBoolean("stereo", 2 === adtsChanelConfig);
aacFrame.writeMetaDataDouble ("audiosamplerate", adtsSampleingRates[adtsSampleingIndex]);
// Is AAC always 16 bit?
aacFrame.writeMetaDataDouble ("audiosamplesize", 16);
aacFrame.writeMetaDataDouble ("audiosamplesize", 16);
this.tags.push(aacFrame);
......@@ -173,7 +177,7 @@ window.videojs.hls.AacStream = function() {
// For audio, DTS is always the same as PTS. We want to set the DTS
// however so we can compare with video DTS to determine approximate
// packet order
aacFrame.pts = next_pts;
aacFrame.pts = next_pts;
aacFrame.view.setUint16(aacFrame.position, newExtraData);
aacFrame.position += 2;
aacFrame.length = Math.max(aacFrame.length, aacFrame.position);
......
......@@ -253,7 +253,7 @@
var
next_pts, // :uint;
next_dts, // :uint;
pts_delta = -1, // :int
pts_offset, // :int
h264Frame, // :FlvTag
......@@ -268,20 +268,22 @@
//(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;
}
// 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;
// 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_offset;
next_dts = dts - pts_offset;
// If data is aligned, flush all internal buffers
if (dataAligned) {
this.finishFrame();
}
};
// If data is aligned, flush all internal buffers
if (dataAligned) {
this.finishFrame();
}
this.setNextTimeStamp(pts, dts, dataAligned);
};
this.finishFrame = function() {
......
......@@ -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) {
tag = aacStream.tags.shift();
} else {
tag = h264Stream.tags.shift();
}
} else if ( 0 < aacStream.tags.length ) {
if (!h264Stream.tags.length) {
// only audio tags remain
tag = aacStream.tags.shift();
} else if (!aacStream.tags.length) {
// only video tags remain
tag = h264Stream.tags.shift();
} 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);
......