Skip to content

Commit

Permalink
feat: add firstSequenceNumber option to Transmuxer to start sequence …
Browse files Browse the repository at this point in the history
…somewhere other than zero (#395)

Adds an option `firstSequenceNumber` to Transmuxer.

Can be used like this:

new Transmuxer({ firstSequenceNumber: 10 });

This is passed in to VideoSegmentStream and AudioSegmentStream.

When mfhd boxes are constructed, the starting sequence number will be
taken from that option. The default value is zero.

This is useful for generating segmented streams out of order. Out of
oder processing allows for restart, parallelism, or random seeking into
source material.
  • Loading branch information
collin authored Aug 24, 2021
1 parent 86cfdca commit 6ff42f4
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 2 deletions.
8 changes: 6 additions & 2 deletions lib/mp4/transmuxer.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,15 @@ var generateSegmentTimingInfo = function(
AudioSegmentStream = function(track, options) {
var
adtsFrames = [],
sequenceNumber = 0,
sequenceNumber,
earliestAllowedDts = 0,
audioAppendStartTs = 0,
videoBaseMediaDecodeTime = Infinity;

options = options || {};

sequenceNumber = options.firstSequenceNumber || 0;

AudioSegmentStream.prototype.init.call(this);

this.push = function(data) {
Expand Down Expand Up @@ -227,14 +229,16 @@ AudioSegmentStream.prototype = new Stream();
*/
VideoSegmentStream = function(track, options) {
var
sequenceNumber = 0,
sequenceNumber,
nalUnits = [],
gopsToAlignWith = [],
config,
pps;

options = options || {};

sequenceNumber = options.firstSequenceNumber || 0;

VideoSegmentStream.prototype.init.call(this);

delete track.minPTS;
Expand Down
58 changes: 58 additions & 0 deletions test/transmuxer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3427,6 +3427,64 @@ QUnit.test('no options creates combined output', function(assert) {
assert.equal('mdat', boxes[3].type, 'generated a second mdat box');
});

QUnit.test('first sequence number option is used in mfhd box', function(assert) {
var
segments = [],
mfhds = [],
boxes,
transmuxer = new Transmuxer({ firstSequenceNumber: 10 });

transmuxer.on('data', function(segment) {
segments.push(segment);
});
transmuxer.push(packetize(PAT));
transmuxer.push(packetize(generatePMT({
hasVideo: true,
hasAudio: true
})));

transmuxer.push(packetize(audioPes([
0x19, 0x47
], true)));
transmuxer.push(packetize(videoPes([
0x09, 0x01 // access_unit_delimiter_rbsp
], true)));
transmuxer.push(packetize(videoPes([
0x08, 0x01 // pic_parameter_set_rbsp
], true)));
transmuxer.push(packetize(videoPes([
0x07, // seq_parameter_set_rbsp
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x53, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
], false)));
transmuxer.push(packetize(videoPes([
0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
], true)));
transmuxer.flush();

assert.equal(segments.length, 1, 'generated a combined video and audio segment');
assert.equal(segments[0].type, 'combined', 'combined is the segment type');

boxes = mp4.tools.inspect(segments[0].data);
boxes.forEach(function(box) {
if (box.type === 'moof') {
box.boxes.forEach(function(moofBox) {
if (moofBox.type === 'mfhd') {
mfhds.push(moofBox);
}
});
}
});

assert.equal(mfhds.length, 2, 'muxed output has two mfhds');

assert.equal(mfhds[0].sequenceNumber, 10, 'first mfhd sequence starts at 10');
assert.equal(mfhds[1].sequenceNumber, 10, 'second mfhd sequence starts at 10');
});

QUnit.test('can specify that we want to generate separate audio and video segments', function(assert) {
var
segments = [],
Expand Down

0 comments on commit 6ff42f4

Please sign in to comment.