Skip to content

Commit

Permalink
Fix timestamps in transmuxed output
Browse files Browse the repository at this point in the history
Timestamps should not change during transmuxing.

Until videojs/mux.js#168 is solved, we work around the Transmuxer's
timestamp manipulation by setting an explicit base time.

Closes #1102

Change-Id: I92658d795797013a6e962b1e4bc4c712edfd50fc
  • Loading branch information
joeyparrish committed Nov 7, 2017
1 parent 7d23729 commit bde4c45
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 53 deletions.
12 changes: 12 additions & 0 deletions externs/mux.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ muxjs.mp4 = {};
muxjs.mp4.Transmuxer = function() {};


/**
* @param {number} time
*/
muxjs.mp4.Transmuxer.prototype.setBaseMediaDecodeTime = function(time) {};


/**
* @param {!Uint8Array} data
*/
Expand All @@ -62,6 +68,12 @@ muxjs.mp4.Transmuxer.prototype.on = function(type, listener) {};
muxjs.mp4.Transmuxer.prototype.off = function(type, listener) {};


/**
* Remove all handlers and clean up.
*/
muxjs.mp4.Transmuxer.prototype.dispose = function() {};


/**
* @typedef {{
* initSegment: !Uint8Array,
Expand Down
3 changes: 2 additions & 1 deletion lib/media/media_source_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,10 +435,11 @@ shaka.media.MediaSourceEngine.prototype.getBuffered_ = function(contentType) {
shaka.media.MediaSourceEngine.prototype.appendBuffer =
function(contentType, data, startTime, endTime) {
var ContentType = shaka.util.ManifestParserUtils.ContentType;

if (contentType == ContentType.TEXT) {
return this.textEngine_.appendBuffer(data, startTime, endTime);
} else if (this.transmuxers_[contentType]) {
return this.transmuxers_[contentType].transmux(data).then(
return this.transmuxers_[contentType].transmux(data, startTime).then(
function(transmuxedData) {
return this.enqueueOperation_(contentType,
this.append_.bind(this, contentType, transmuxedData.buffer));
Expand Down
44 changes: 25 additions & 19 deletions lib/media/transmuxer.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,36 +46,25 @@ shaka.media.Transmuxer = function() {
/** @private {boolean} */
this.isTransmuxing_ = false;

this.onTransmuxed_ = this.onTransmuxed_.bind(this);

this.onTransmuxDone_ = this.onTransmuxDone_.bind(this);
/** @private {boolean} */
this.baseDecodeTimeSet_ = false;

this.muxTransmuxer_.on('data', this.onTransmuxed_);
this.muxTransmuxer_.on('data', this.onTransmuxed_.bind(this));

this.muxTransmuxer_.on('done', this.onTransmuxDone_);
this.muxTransmuxer_.on('done', this.onTransmuxDone_.bind(this));
};


/**
* @override
*/
shaka.media.Transmuxer.prototype.destroy = function() {
this.cleanUpListeners_();
this.muxTransmuxer_.dispose();
this.muxTransmuxer_ = null;
return Promise.resolve();
};


/**
* Detaches all event listeners.
* @private
*/
shaka.media.Transmuxer.prototype.cleanUpListeners_ = function() {
this.muxTransmuxer_.off('data', this.onTransmuxed_);
this.muxTransmuxer_.off('done', this.onTransmuxDone_);
};


/**
* Check if the content type is Transport Stream, and if muxjs is loaded.
* @param {string} contentType
Expand Down Expand Up @@ -118,14 +107,23 @@ shaka.media.Transmuxer.convertTsCodecs = function(contentType, tsMimeType) {
/**
* Transmux from Transport stream to MP4, using mux.js library.
* @param {!ArrayBuffer} data
* @param {?number} startTime
* @return {!Promise.<!Uint8Array>}
*/
shaka.media.Transmuxer.prototype.transmux = function(data) {
shaka.media.Transmuxer.prototype.transmux = function(data, startTime) {
goog.asserts.assert(!this.isTransmuxing_,
'No transmuxing should be in progress.');
this.isTransmuxing_ = true;
this.transmuxPromise_ = new shaka.util.PublicPromise();
this.transmuxedData_ = [];

// TODO: remove this once videojs/mux.js#168 is solved
if (startTime != null && !this.baseDecodeTimeSet_) {
var timescale = shaka.media.Transmuxer.TS_TIMESCALE_;
this.muxTransmuxer_.setBaseMediaDecodeTime(startTime * timescale);
this.baseDecodeTimeSet_ = true;
}

var dataArray = new Uint8Array(data);
this.muxTransmuxer_.push(dataArray);
this.muxTransmuxer_.flush();
Expand Down Expand Up @@ -156,7 +154,15 @@ shaka.media.Transmuxer.prototype.onTransmuxed_ = function(segment) {
* @private
*/
shaka.media.Transmuxer.prototype.onTransmuxDone_ = function() {
this.transmuxPromise_.resolve(
shaka.util.Uint8ArrayUtils.concat.apply(null, this.transmuxedData_));
var output =
shaka.util.Uint8ArrayUtils.concat.apply(null, this.transmuxedData_);
this.transmuxPromise_.resolve(output);
this.isTransmuxing_ = false;
};


/**
* @const {number}
* @private
*/
shaka.media.Transmuxer.TS_TIMESCALE_ = 90000;
91 changes: 58 additions & 33 deletions test/media/transmuxer_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,48 +86,73 @@ describe('Transmuxer', function() {
describe('transmuxing', function() {
it('transmux video from TS to MP4', function(done) {
var sawMDAT = false;
transmuxer.transmux(videoSegment).then(
function(transmuxedData) {
expect(transmuxedData instanceof Uint8Array).toBe(true);
expect(transmuxedData.length).toBeGreaterThan(0);
new shaka.util.Mp4Parser()
.box('mdat', shaka.util.Mp4Parser.allData(function(data) {
sawMDAT = true;
expect(data.buffer.byteLength).toBeGreaterThan(0);
}.bind(this))).parse(transmuxedData.buffer);
expect(sawMDAT).toBeTruthy();
}).catch(fail).then(done);
transmuxer.transmux(videoSegment, 0).then(function(transmuxedData) {
expect(transmuxedData instanceof Uint8Array).toBe(true);
expect(transmuxedData.length).toBeGreaterThan(0);
new shaka.util.Mp4Parser()
.box('mdat', shaka.util.Mp4Parser.allData(function(data) {
sawMDAT = true;
expect(data.buffer.byteLength).toBeGreaterThan(0);
}))
.parse(transmuxedData.buffer);
expect(sawMDAT).toBeTruthy();
}).catch(fail).then(done);
});

it('transmux audio from TS to MP4', function(done) {
var sawMDAT = false;
transmuxer.transmux(audioSegment).then(
function(transmuxedData) {
expect(transmuxedData instanceof Uint8Array).toBe(true);
expect(transmuxedData.length).toBeGreaterThan(0);
new shaka.util.Mp4Parser()
.box('mdat', shaka.util.Mp4Parser.allData(function(data) {
sawMDAT = true;
expect(data.buffer.byteLength).toBeGreaterThan(0);
}.bind(this))).parse(transmuxedData.buffer);
expect(sawMDAT).toBeTruthy();

}).catch(fail).then(done);
transmuxer.transmux(audioSegment, 0).then(function(transmuxedData) {
expect(transmuxedData instanceof Uint8Array).toBe(true);
expect(transmuxedData.length).toBeGreaterThan(0);
new shaka.util.Mp4Parser()
.box('mdat', shaka.util.Mp4Parser.allData(function(data) {
sawMDAT = true;
expect(data.buffer.byteLength).toBeGreaterThan(0);
}))
.parse(transmuxedData.buffer);
expect(sawMDAT).toBeTruthy();
}).catch(fail).then(done);
});

it('transmux empty video from TS to MP4', function(done) {
var sawMDAT = false;
transmuxer.transmux(emptySegment).then(
function(transmuxedData) {
expect(transmuxedData instanceof Uint8Array).toBe(true);
expect(transmuxedData.length).toBeGreaterThan(0);
new shaka.util.Mp4Parser()
.box('mdat', shaka.util.Mp4Parser.allData(function(data) {
sawMDAT = true;
}.bind(this))).parse(transmuxedData.buffer);
expect(sawMDAT).toBeFalsy();
}).catch(fail).then(done);
transmuxer.transmux(emptySegment, 0).then(function(transmuxedData) {
expect(transmuxedData instanceof Uint8Array).toBe(true);
expect(transmuxedData.length).toBeGreaterThan(0);
new shaka.util.Mp4Parser()
.box('mdat', shaka.util.Mp4Parser.allData(function(data) {
sawMDAT = true;
}))
.parse(transmuxedData.buffer);
expect(sawMDAT).toBeFalsy();
}).catch(fail).then(done);
});

it('offsets output timestamps', function(done) {
var parsed = false;
var expectedMp4Timestamp = 123 * 90000; // timescale units
var mp4Timestamp;

transmuxer.transmux(videoSegment, 123).then(function(transmuxedData) {
var Mp4Parser = shaka.util.Mp4Parser;

new Mp4Parser()
.box('moof', Mp4Parser.children)
.box('traf', Mp4Parser.children)
.fullBox('tfdt', function(box) {
goog.asserts.assert(
box.version == 0 || box.version == 1,
'TFDT version can only be 0 or 1');
mp4Timestamp = (box.version == 0) ?
box.reader.readUint32() :
box.reader.readUint64();
parsed = true;
})
.parse(transmuxedData.buffer);

expect(parsed).toBe(true);
expect(mp4Timestamp).toEqual(expectedMp4Timestamp);
}).catch(fail).then(done);
});
});
});

0 comments on commit bde4c45

Please sign in to comment.