Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix PTS calculation on rollover. #2930

Merged
merged 6 commits into from Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 2 additions & 10 deletions src/demux/tsdemuxer.js
Expand Up @@ -487,22 +487,14 @@ class TSDemuxer {
(frag[11] & 0xFE) * 16384 +// 1 << 14
(frag[12] & 0xFF) * 128 +// 1 << 7
(frag[13] & 0xFE) / 2;
// check if greater than 2^32 -1
if (pesPts > 4294967295) {
// decrement 2^33
pesPts -= 8589934592;
}

if (pesFlags & 0x40) {
pesDts = (frag[14] & 0x0E) * 536870912 +// 1 << 29
(frag[15] & 0xFF) * 4194304 +// 1 << 22
(frag[16] & 0xFE) * 16384 +// 1 << 14
(frag[17] & 0xFF) * 128 +// 1 << 7
(frag[18] & 0xFE) / 2;
// check if greater than 2^32 -1
if (pesDts > 4294967295) {
// decrement 2^33
pesDts -= 8589934592;
}
itsjamie marked this conversation as resolved.
Show resolved Hide resolved

if (pesPts - pesDts > 60 * 90000) {
logger.warn(`${Math.round((pesPts - pesDts) / 90000)}s delta between PTS and DTS, align them`);
pesPts = pesDts;
Expand Down
46 changes: 32 additions & 14 deletions src/remux/mp4-remuxer.js
Expand Up @@ -34,6 +34,25 @@ class MP4Remuxer {
this.ISGenerated = false;
}

getVideoStartPts (videoSamples) {
let rolloverDetected = false;
const startPTS = videoSamples.reduce((minPTS, sample) => {
const delta = sample.pts - minPTS;
if (delta < -4294967296) { // 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation
rolloverDetected = true;
return minPTS;
} else if (delta > 0) {
return minPTS;
} else {
return sample.pts;
}
}, videoSamples[0].pts);
if (rolloverDetected) {
logger.debug('PTS rollover detected');
}
return startPTS;
}

remux (audioTrack, videoTrack, id3Track, textTrack, timeOffset, contiguous, accurateTimeOffset) {
// generate Init Segment if needed
if (!this.ISGenerated) {
Expand All @@ -50,7 +69,7 @@ class MP4Remuxer {
// if first audio DTS is not aligned with first video DTS then we need to take that into account
// when providing timeOffset to remuxAudio / remuxVideo. if we don't do that, there might be a permanent / small
// drift between audio and video streams
const startPTS = videoTrack.samples.reduce((minPTS, sample) => Math.min(minPTS, sample.pts), videoTrack.samples[0].pts);
const startPTS = this.getVideoStartPts(videoTrack.samples);
const tsDelta = audioTrack.samples[0].pts - startPTS;
const audiovideoTimestampDelta = tsDelta / videoTrack.inputTimeScale;
audioTimeOffset += Math.max(0, audiovideoTimestampDelta);
Expand Down Expand Up @@ -163,7 +182,7 @@ class MP4Remuxer {
}
};
if (computePTSDTS) {
const startPTS = videoSamples.reduce((minPTS, sample) => Math.min(minPTS, sample.pts), videoSamples[0].pts);
const startPTS = this.getVideoStartPts(videoSamples);
const startOffset = Math.round(inputTimeScale * timeOffset);
initDTS = Math.min(initDTS, videoSamples[0].dts - startOffset);
initPTS = Math.min(initPTS, startPTS - startOffset);
Expand Down Expand Up @@ -213,7 +232,7 @@ class MP4Remuxer {

if (!contiguous) {
const pts = timeOffset * timeScale;
const cts = inputSamples[0].pts - inputSamples[0].dts;
const cts = inputSamples[0].pts - PTSNormalize(inputSamples[0].dts, inputSamples[0].pts);
// if not contiguous, let's use target timeOffset
nextAvcDts = pts - cts;
}
Expand Down Expand Up @@ -736,7 +755,7 @@ class MP4Remuxer {
this.remuxAudio(track, timeOffset, contiguous);
}

remuxID3 (track) {
remuxID3 (track, timeOffset) {
const length = track.samples.length;
if (!length) {
return;
Expand All @@ -749,8 +768,8 @@ class MP4Remuxer {
const sample = track.samples[index];
// setting id3 pts, dts to relative time
// using this._initPTS and this._initDTS to calculate relative time
sample.pts = ((sample.pts - initPTS) / inputTimeScale);
sample.dts = ((sample.dts - initDTS) / inputTimeScale);
sample.pts = PTSNormalize(sample.pts - initPTS, timeOffset * inputTimeScale) / inputTimeScale;
sample.dts = PTSNormalize(sample.dts - initDTS, timeOffset * inputTimeScale) / inputTimeScale;
}
this.observer.trigger(Event.FRAG_PARSING_METADATA, {
samples: track.samples
Expand All @@ -759,22 +778,21 @@ class MP4Remuxer {
track.samples = [];
}

remuxText (track) {
track.samples.sort(function (a, b) {
return (a.pts - b.pts);
});

let length = track.samples.length, sample;
remuxText (track, timeOffset) {
const length = track.samples.length;
const inputTimeScale = track.inputTimeScale;
const initPTS = this._initPTS;
// consume samples
if (length) {
for (let index = 0; index < length; index++) {
sample = track.samples[index];
const sample = track.samples[index];
// setting text pts, dts to relative time
// using this._initPTS and this._initDTS to calculate relative time
sample.pts = ((sample.pts - initPTS) / inputTimeScale);
sample.pts = PTSNormalize(sample.pts - initPTS, timeOffset * inputTimeScale) / inputTimeScale;
}
track.samples.sort(function (a, b) {
return (a.pts - b.pts);
});
this.observer.trigger(Event.FRAG_PARSING_USERDATA, {
samples: track.samples
});
Expand Down
34 changes: 21 additions & 13 deletions tests/functional/auto/setup.js
Expand Up @@ -22,6 +22,8 @@ const browserConfig = {
*/
let browser;
let stream;
let printDebugLogs = false;

// Setup browser config data from env vars
if (onTravis) {
let UA = process.env.UA;
Expand Down Expand Up @@ -123,18 +125,20 @@ async function testSmoothSwitch (url, config) {
const callback = arguments[arguments.length - 1];
window.startStream(url, config, callback);
const video = window.video;
window.hls.once(window.Hls.Events.FRAG_CHANGED, function (event, data) {
window.hls.once(window.Hls.Events.FRAG_CHANGED, function (eventName, data) {
console.log('[test] > ' + eventName + ' frag.level: ' + data.frag.level);
window.switchToHighestLevel('next');
});
window.hls.on(window.Hls.Events.LEVEL_SWITCHED, function (event, data) {
console.log('[test] > level switched: ' + data.level);
window.hls.on(window.Hls.Events.LEVEL_SWITCHED, function (eventName, data) {
console.log('[test] > ' + eventName + ' data.level: ' + data.level);
let currentTime = video.currentTime;
if (data.level === window.hls.levels.length - 1) {
console.log('[test] > switched on level: ' + data.level);
const highestLevel = (window.hls.levels.length - 1);
if (data.level === highestLevel) {
window.setTimeout(function () {
let newCurrentTime = video.currentTime;
console.log('[test] > currentTime delta : ' + (newCurrentTime - currentTime));
console.log('[test] > currentTime delta: ' + (newCurrentTime - currentTime));
callback({
highestLevel: highestLevel,
currentTimeDelta: newCurrentTime - currentTime,
logs: window.logString
});
Expand Down Expand Up @@ -335,15 +339,17 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu
beforeEach(async function () {
try {
await retry(async () => {
console.log('Loading test page...');
if (printDebugLogs) {
console.log('Loading test page...');
}
try {
await browser.get(`http://${hostname}:8000/tests/functional/auto/index.html`);
} catch (e) {
throw new Error('failed to open test page');
}
console.log('Test page loaded.');

console.log('Locating ID \'hlsjs-functional-tests\'');
if (printDebugLogs) {
console.log('Test page loaded.');
}
try {
await browser.wait(
until.elementLocated(By.css('body#hlsjs-functional-tests')),
Expand All @@ -355,18 +361,20 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu
console.log(source);
throw e;
}
console.log('Located the ID, page confirmed loaded');
if (printDebugLogs) {
console.log('Test harness found, page confirmed loaded');
}
});
} catch (e) {
throw new Error(`error getting test page loaded: ${e}`);
}
});

afterEach(async function () {
// if (onTravis || (!onTravis && this.currentTest.isFailed())) {
if (printDebugLogs || this.currentTest.isFailed()) {
const logString = await browser.executeScript('return logString');
console.log(`${onTravis ? 'travis_fold:start:debug_logs' : ''}\n${logString}\n${onTravis ? 'travis_fold:end:debug_logs' : ''}`);
// }
}
});

after(async function () {
Expand Down