Skip to content

Commit

Permalink
feat: use bigint for 64 bit ints if needed and available. (#383)
Browse files Browse the repository at this point in the history
Read values as BigInt when appropriate. Potentially, cases where this happens already results in broken playback, but out of abundance of caution we're marking this as a breaking change.

BREAKING CHANGE: In some cases, mux.js will now be returning a BigInt rather than a regular Number value. This means that consumers of this library will need to add checks for BigInt for optimal operation.
  • Loading branch information
brandonocasey committed Nov 29, 2021
1 parent 87f777f commit 83779b9
Show file tree
Hide file tree
Showing 12 changed files with 1,144 additions and 752 deletions.
13 changes: 10 additions & 3 deletions lib/mp4/caption-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var findBox = require('../mp4/find-box.js');
var parseTfdt = require('../tools/parse-tfdt.js');
var parseTrun = require('../tools/parse-trun.js');
var parseTfhd = require('../tools/parse-tfhd.js');
var window = require('global/window');

/**
* Maps an offset in the mdat to a sample based on the the size of the samples.
Expand Down Expand Up @@ -122,7 +123,7 @@ var findSeiNals = function(avcStream, samples, trackId) {
* the absolute presentation and decode timestamps of each sample.
*
* @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
* @param {Number} baseMediaDecodeTime - base media decode time from tfdt
* @param {Number|BigInt} baseMediaDecodeTime - base media decode time from tfdt
@see ISO-BMFF-12/2015, Section 8.8.12
* @param {Object} tfhd - The parsed Track Fragment Header
* @see inspect.parseTfhd
Expand Down Expand Up @@ -156,9 +157,15 @@ var parseSamples = function(truns, baseMediaDecodeTime, tfhd) {
if (sample.compositionTimeOffset === undefined) {
sample.compositionTimeOffset = 0;
}
sample.pts = currentDts + sample.compositionTimeOffset;

currentDts += sample.duration;
if (typeof currentDts === 'bigint') {
sample.pts = currentDts + window.BigInt(sample.compositionTimeOffset);
currentDts += window.BigInt(sample.duration);

} else {
sample.pts = currentDts + sample.compositionTimeOffset;
currentDts += sample.duration;
}
});

allSamples = allSamples.concat(samples);
Expand Down
7 changes: 4 additions & 3 deletions lib/mp4/mp4-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
*/
'use strict';

var UINT32_MAX = Math.pow(2, 32) - 1;
var MAX_UINT32 = require('../utils/numbers.js').MAX_UINT32;


var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd,
trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex,
Expand Down Expand Up @@ -581,8 +582,8 @@ traf = function(track) {
0x00, 0x00, 0x00, 0x00 // default_sample_flags
]));

upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (MAX_UINT32));
lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (MAX_UINT32));

trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([
0x01, // version 1
Expand Down
97 changes: 57 additions & 40 deletions lib/mp4/probe.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ var parseType = require('../mp4/parse-type.js');
var parseTfhd = require('../tools/parse-tfhd.js');
var parseTrun = require('../tools/parse-trun.js');
var parseTfdt = require('../tools/parse-tfdt.js');
var getUint64 = require('../utils/numbers.js').getUint64;
var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks,
getTimescaleFromMediaHeader;
var window = require('global/window');


/**
* Parses an MP4 initialization segment and extracts the timescale
Expand Down Expand Up @@ -87,52 +90,55 @@ timescale = function(init) {
* fragment, in seconds
*/
startTime = function(timescale, fragment) {
var trafs, baseTimes, result;
var trafs, result;

// we need info from two childrend of each track fragment box
trafs = findBox(fragment, ['moof', 'traf']);

// determine the start times for each track
baseTimes = [].concat.apply([], trafs.map(function(traf) {
return findBox(traf, ['tfhd']).map(function(tfhd) {
var id, scale, baseTime;

// get the track id from the tfhd
id = toUnsigned(tfhd[4] << 24 |
tfhd[5] << 16 |
tfhd[6] << 8 |
tfhd[7]);
// assume a 90kHz clock if no timescale was specified
scale = timescale[id] || 90e3;

// get the base media decode time from the tfdt
baseTime = findBox(traf, ['tfdt']).map(function(tfdt) {
var version, result;

version = tfdt[0];
result = toUnsigned(tfdt[4] << 24 |
tfdt[5] << 16 |
tfdt[6] << 8 |
tfdt[7]);
if (version === 1) {
result *= Math.pow(2, 32);
result += toUnsigned(tfdt[8] << 24 |
tfdt[9] << 16 |
tfdt[10] << 8 |
tfdt[11]);
}
return result;
})[0];
baseTime = typeof baseTime === 'number' && !isNaN(baseTime) ? baseTime : Infinity;
var lowestTime = trafs.reduce(function(acc, traf) {
var tfhd = findBox(traf, ['tfhd'])[0];

// get the track id from the tfhd
var id = toUnsigned(tfhd[4] << 24 |
tfhd[5] << 16 |
tfhd[6] << 8 |
tfhd[7]);
// assume a 90kHz clock if no timescale was specified
var scale = timescale[id] || 90e3;

// get the base media decode time from the tfdt
var tfdt = findBox(traf, ['tfdt'])[0];
var dv = new DataView(tfdt.buffer, tfdt.byteOffset, tfdt.byteLength);
var baseTime;

// version 1 is 64 bit
if (tfdt[0] === 1) {
baseTime = getUint64(tfdt.subarray(4, 12));
} else {
baseTime = dv.getUint32(4);
}

// convert base time to seconds
return baseTime / scale;
});
}));
// convert base time to seconds if it is a valid number.
let seconds;
if (typeof baseTime === 'bigint') {
seconds = baseTime / window.BigInt(scale);
} else if (typeof baseTime === 'number' && !isNaN(baseTime)) {
seconds = baseTime / scale;
}

if (seconds < Number.MAX_SAFE_INTEGER) {
seconds = Number(seconds);
}

// return the minimum
result = Math.min.apply(null, baseTimes);
return isFinite(result) ? result : 0;
if (seconds < acc) {
acc = seconds;
}

return acc;
}, Infinity);

return typeof lowestTime === 'bigint' || isFinite(lowestTime) ? lowestTime : 0;
};

/**
Expand Down Expand Up @@ -194,7 +200,18 @@ compositionStartTime = function(timescales, fragment) {
var timescale = timescales[trackId] || 90e3;

// return the composition start time, in seconds
return (baseMediaDecodeTime + compositionTimeOffset) / timescale;
if (typeof baseMediaDecodeTime === 'bigint') {
compositionTimeOffset = window.BigInt(compositionTimeOffset);
timescale = window.BigInt(timescale);
}

var result = (baseMediaDecodeTime + compositionTimeOffset) / timescale;

if (typeof result === 'bigint' && result < Number.MAX_SAFE_INTEGER) {
result = Number(result);
}

return result;
};

/**
Expand Down
8 changes: 5 additions & 3 deletions lib/tools/mp4-inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
*/
'use strict';

var MAX_UINT32 = Math.pow(2, 32);
var numberHelpers = require('../utils/numbers.js');
var MAX_UINT32 = numberHelpers.MAX_UINT32;
var getUint64 = numberHelpers.getUint64;

var
inspectMp4,
Expand Down Expand Up @@ -150,8 +152,8 @@ var
i += 12;
} else {
result.edits.push({
segmentDuration: (view.getUint32(i) * MAX_UINT32) + view.getUint32(i + 4),
mediaTime: (view.getUint32(i + 8) * MAX_UINT32) + view.getUint32(i + 12),
segmentDuration: getUint64(data.subarray(i)),
mediaTime: getUint64(data.subarray(i + 8)),
mediaRate: view.getUint16(i + 16) + view.getUint16(i + 18) / (256 * 256)
});
i += 20;
Expand Down
6 changes: 3 additions & 3 deletions lib/tools/parse-sidx.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var MAX_UINT32 = Math.pow(2, 32);
var getUint64 = require('../utils/numbers.js').getUint64;

var parseSidx = function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
Expand All @@ -17,8 +17,8 @@ var parseSidx = function(data) {
i += 8;
} else {
// read 64 bits
result.earliestPresentationTime = (view.getUint32(i) * MAX_UINT32) + view.getUint32(i + 4);
result.firstOffset = (view.getUint32(i + 8) * MAX_UINT32) + view.getUint32(i + 12);
result.earliestPresentationTime = getUint64(data.subarray(i));
result.firstOffset = getUint64(data.subarray(i + 8));
i += 16;
}

Expand Down
9 changes: 6 additions & 3 deletions lib/tools/parse-tfdt.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
var toUnsigned = require('../utils/bin').toUnsigned;
var getUint64 = require('../utils/numbers.js').getUint64;

var tfdt = function(data) {
var result = {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
baseMediaDecodeTime: toUnsigned(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7])
};


if (result.version === 1) {
result.baseMediaDecodeTime *= Math.pow(2, 32);
result.baseMediaDecodeTime += toUnsigned(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]);
result.baseMediaDecodeTime = getUint64(data.subarray(4));
} else {
result.baseMediaDecodeTime = toUnsigned(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7])
}
return result;
};
Expand Down
23 changes: 23 additions & 0 deletions lib/utils/numbers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
var MAX_UINT32 = Math.pow(2, 32);

var getUint64 = (uint8) => {
var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
var value;

if (dv.getBigUint64) {
value = dv.getBigUint64(0);

if (value < Number.MAX_SAFE_INTEGER) {
return Number(value);
}

return value;
}

return (dv.getUint32(0) * MAX_UINT32) + dv.getUint32(4);
};

module.exports = {
getUint64: getUint64,
MAX_UINT32: MAX_UINT32
};
Loading

0 comments on commit 83779b9

Please sign in to comment.