Skip to content

Commit

Permalink
Do not assume same timescale in manifest and media
Browse files Browse the repository at this point in the history
According to the DASH spec, the timescale in the manifest need not match the
timescale in the media.  Therefore, we should be applying scaled
presentationTimeOffsets in segment index parsers, since the two scales might
differ.

Issue #1098

Change-Id: Ic191d1bba399b30a656ab5060d7bb226e659b79a
  • Loading branch information
joeyparrish committed Nov 13, 2017
1 parent e03acc9 commit 6cf57fd
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 32 deletions.
16 changes: 8 additions & 8 deletions lib/dash/segment_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ shaka.dash.SegmentBase.createStream = function(context, requestInitSegment) {
var init =
SegmentBase.createInitSegment(context, SegmentBase.fromInheritance_);
var index = SegmentBase.createSegmentIndex_(
context, requestInitSegment, init, unscaledPresentationTimeOffset);
context, requestInitSegment, init, scaledPresentationTimeOffset);

return {
createSegmentIndex: index.createSegmentIndex,
Expand All @@ -127,12 +127,12 @@ shaka.dash.SegmentBase.createStream = function(context, requestInitSegment) {
* @param {number} startByte
* @param {?number} endByte
* @param {string} containerType
* @param {number} unscaledPresentationTimeOffset
* @param {number} scaledPresentationTimeOffset
* @return {shaka.dash.DashParser.SegmentIndexFunctions}
*/
shaka.dash.SegmentBase.createSegmentIndexFromUris = function(
context, requestInitSegment, init, uris,
startByte, endByte, containerType, unscaledPresentationTimeOffset) {
startByte, endByte, containerType, scaledPresentationTimeOffset) {
var presentationTimeline = context.presentationTimeline;
var fitLast = !context.dynamic || !context.periodInfo.isLastPeriod;
var periodStartTime = context.periodInfo.start;
Expand All @@ -157,12 +157,12 @@ shaka.dash.SegmentBase.createSegmentIndexFromUris = function(

if (containerType == 'mp4') {
references = shaka.media.Mp4SegmentIndexParser(
indexData, startByte, uris, unscaledPresentationTimeOffset);
indexData, startByte, uris, scaledPresentationTimeOffset);
} else {
goog.asserts.assert(initData, 'WebM requires init data');
var parser = new shaka.media.WebmSegmentIndexParser();
references = parser.parse(indexData, initData, uris,
unscaledPresentationTimeOffset);
scaledPresentationTimeOffset);
}

presentationTimeline.notifySegments(periodStartTime, references);
Expand Down Expand Up @@ -210,13 +210,13 @@ shaka.dash.SegmentBase.fromInheritance_ = function(frame) {
* @param {shaka.dash.DashParser.Context} context
* @param {shaka.dash.DashParser.RequestInitSegmentCallback} requestInitSegment
* @param {shaka.media.InitSegmentReference} init
* @param {number} unscaledPresentationTimeOffset
* @param {number} scaledPresentationTimeOffset
* @return {shaka.dash.DashParser.SegmentIndexFunctions}
* @throws shaka.util.Error When there is a parsing error.
* @private
*/
shaka.dash.SegmentBase.createSegmentIndex_ = function(
context, requestInitSegment, init, unscaledPresentationTimeOffset) {
context, requestInitSegment, init, scaledPresentationTimeOffset) {
var MpdUtils = shaka.dash.MpdUtils;
var SegmentBase = shaka.dash.SegmentBase;
var XmlUtils = shaka.util.XmlUtils;
Expand Down Expand Up @@ -280,5 +280,5 @@ shaka.dash.SegmentBase.createSegmentIndex_ = function(

return shaka.dash.SegmentBase.createSegmentIndexFromUris(
context, requestInitSegment, init, indexUris, indexRange.start,
indexRange.end, containerType, unscaledPresentationTimeOffset);
indexRange.end, containerType, scaledPresentationTimeOffset);
};
19 changes: 10 additions & 9 deletions lib/media/mp4_segment_index_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ goog.require('shaka.util.Mp4Parser');
* the MP4 container.
* @param {!Array.<string>} uris The possible locations of the MP4 file that
* contains the segments.
* @param {number} unscaledPresentationTimeOffset
* @param {number} scaledPresentationTimeOffset
* @return {!Array.<!shaka.media.SegmentReference>}
* @throws {shaka.util.Error}
*/
shaka.media.Mp4SegmentIndexParser = function(
sidxData, sidxOffset, uris, unscaledPresentationTimeOffset) {
sidxData, sidxOffset, uris, scaledPresentationTimeOffset) {

var Mp4SegmentIndexParser = shaka.media.Mp4SegmentIndexParser;

Expand All @@ -46,7 +46,7 @@ shaka.media.Mp4SegmentIndexParser = function(
.fullBox('sidx', function(box) {
references = Mp4SegmentIndexParser.parseSIDX_(
sidxOffset,
unscaledPresentationTimeOffset,
scaledPresentationTimeOffset,
uris,
box);
});
Expand All @@ -71,7 +71,7 @@ shaka.media.Mp4SegmentIndexParser = function(
* Parse a SIDX box from the given reader.
*
* @param {number} sidxOffset
* @param {number} unscaledPresentationTimeOffset
* @param {number} scaledPresentationTimeOffset
* @param {!Array.<string>} uris The possible locations of the MP4 file that
* contains the segments.
* @param {!shaka.util.Mp4Parser.ParsedBox} box
Expand All @@ -80,7 +80,7 @@ shaka.media.Mp4SegmentIndexParser = function(
*/
shaka.media.Mp4SegmentIndexParser.parseSIDX_ = function(
sidxOffset,
unscaledPresentationTimeOffset,
scaledPresentationTimeOffset,
uris,
box) {

Expand Down Expand Up @@ -122,8 +122,7 @@ shaka.media.Mp4SegmentIndexParser.parseSIDX_ = function(
var referenceCount = box.reader.readUint16();

// Substract the presentation time offset
var unscaledStartTime =
earliestPresentationTime - unscaledPresentationTimeOffset;
var unscaledStartTime = earliestPresentationTime;
var startByte = sidxOffset + box.size + firstOffset;

for (var i = 0; i < referenceCount; i++) {
Expand Down Expand Up @@ -151,8 +150,10 @@ shaka.media.Mp4SegmentIndexParser.parseSIDX_ = function(
references.push(
new shaka.media.SegmentReference(
references.length,
unscaledStartTime / timescale,
(unscaledStartTime + subsegmentDuration) / timescale,
(unscaledStartTime / timescale) -
scaledPresentationTimeOffset,
((unscaledStartTime + subsegmentDuration) / timescale) -
scaledPresentationTimeOffset,
function() { return uris; },
startByte,
startByte + referenceSize - 1));
Expand Down
34 changes: 19 additions & 15 deletions lib/media/webm_segment_index_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ shaka.media.WebmSegmentIndexParser.CUE_CLUSTER_POSITION = 0xf1;
* @param {!ArrayBuffer} initData The WebM container's headers.
* @param {!Array.<string>} uris The possible locations of the WebM file that
* contains the segments.
* @param {number} unscaledPresentationTimeOffset
* @param {number} scaledPresentationTimeOffset
* @return {!Array.<!shaka.media.SegmentReference>}
* @throws {shaka.util.Error}
* @see http://www.matroska.org/technical/specs/index.html
* @see http://www.webmproject.org/docs/container/
*/
shaka.media.WebmSegmentIndexParser.prototype.parse = function(
cuesData, initData, uris, unscaledPresentationTimeOffset) {
cuesData, initData, uris, scaledPresentationTimeOffset) {
var tuple = this.parseWebmContainer_(initData);
var parser = new shaka.util.EbmlParser(new DataView(cuesData));
var cuesElement = parser.parseElement();
Expand All @@ -103,7 +103,7 @@ shaka.media.WebmSegmentIndexParser.prototype.parse = function(

return this.parseCues_(
cuesElement, tuple.segmentOffset, tuple.timecodeScale, tuple.duration,
uris, unscaledPresentationTimeOffset);
uris, scaledPresentationTimeOffset);
};


Expand Down Expand Up @@ -242,21 +242,21 @@ shaka.media.WebmSegmentIndexParser.prototype.parseInfo_ = function(
* @param {number} timecodeScale
* @param {number} duration
* @param {!Array.<string>} uris
* @param {number} unscaledPresentationTimeOffset
* @param {number} scaledPresentationTimeOffset
* @return {!Array.<!shaka.media.SegmentReference>}
* @throws {shaka.util.Error}
* @private
*/
shaka.media.WebmSegmentIndexParser.prototype.parseCues_ = function(
cuesElement, segmentOffset, timecodeScale, duration, uris,
unscaledPresentationTimeOffset) {
scaledPresentationTimeOffset) {
var references = [];
var getUris = function() { return uris; };

var parser = cuesElement.createParser();

var lastTime = -1;
var lastOffset = -1;
var lastTime = null;
var lastOffset = null;

while (parser.hasMoreData()) {
var elem = parser.parseElement();
Expand All @@ -270,17 +270,17 @@ shaka.media.WebmSegmentIndexParser.prototype.parseCues_ = function(
}

// Substract presentation time offset from unscaled time
var currentTime = timecodeScale *
(tuple.unscaledTime - unscaledPresentationTimeOffset);
var currentTime = timecodeScale * tuple.unscaledTime;
var currentOffset = segmentOffset + tuple.relativeOffset;

if (lastTime >= 0) {
goog.asserts.assert(lastOffset >= 0, 'last offset cannot be 0');
if (lastTime != null) {
goog.asserts.assert(lastOffset != null, 'last offset cannot be null');

references.push(
new shaka.media.SegmentReference(
references.length,
lastTime, currentTime,
lastTime - scaledPresentationTimeOffset,
currentTime - scaledPresentationTimeOffset,
getUris,
lastOffset, currentOffset - 1));
}
Expand All @@ -289,12 +289,16 @@ shaka.media.WebmSegmentIndexParser.prototype.parseCues_ = function(
lastOffset = currentOffset;
}

if (lastTime >= 0) {
goog.asserts.assert(lastOffset >= 0, 'last offset cannot be 0');
if (lastTime != null) {
goog.asserts.assert(lastOffset != null, 'last offset cannot be null');

references.push(
new shaka.media.SegmentReference(
references.length, lastTime, duration, getUris, lastOffset, null));
references.length,
lastTime - scaledPresentationTimeOffset,
duration - scaledPresentationTimeOffset,
getUris,
lastOffset, null));
}

return references;
Expand Down
45 changes: 45 additions & 0 deletions test/dash/dash_parser_segment_base_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ describe('DashParser SegmentBase', function() {
var parser;
/** @type {shakaExtern.ManifestParser.PlayerInterface} */
var playerInterface;
/** @const {string} */
var indexSegmentUri = '/base/test/test/assets/index-segment.mp4';
/** @type {ArrayBuffer} */
var indexSegment;

beforeAll(function(done) {
shaka.test.Util.fetch(indexSegmentUri).then(function(data) {
indexSegment = data;
}).catch(fail).then(done);
});

beforeEach(function() {
fakeNetEngine = new shaka.test.FakeNetworkingEngine();
Expand Down Expand Up @@ -252,6 +262,41 @@ describe('DashParser SegmentBase', function() {
.then(done);
});

it('does not assume the same timescale as media', function(done) {
var source = [
'<MPD mediaPresentationDuration="PT75S">',
' <Period>',
' <AdaptationSet mimeType="video/mp4">',
' <Representation bandwidth="1">',
' <BaseURL>http://example.com/index.mp4</BaseURL>',
' <SegmentBase indexRange="30-900" ',
' timescale="1000"',
' presentationTimeOffset="2000" />',
' </Representation>',
' </AdaptationSet>',
' </Period>',
'</MPD>'].join('\n');

fakeNetEngine.setResponseMap({
'dummy://foo': shaka.util.StringUtils.toUTF8(source),
'http://example.com/index.mp4': indexSegment
});

var video;
parser.start('dummy://foo', playerInterface)
.then(function(manifest) {
video = manifest.periods[0].variants[0].video;
return video.createSegmentIndex(); // real data, should succeed
})
.then(function() {
var reference = video.getSegmentReference(0);
expect(reference.startTime).toEqual(-2);
expect(reference.endTime).toEqual(10);
})
.catch(fail)
.then(done);
});

describe('fails for', function() {
it('unsupported container', function(done) {
var source = [
Expand Down
20 changes: 20 additions & 0 deletions test/media/mp4_segment_index_parser_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,24 @@ describe('Mp4SegmentIndexParser', function() {
expect(result[i].endByte).toBe(references[i].endByte);
}
});

it('takes a scaled presentationTimeOffset in seconds', function() {
var result = shaka.media.Mp4SegmentIndexParser(indexSegment, 0, [], 2);
var references =
[
{startTime: -2, endTime: 10},
{startTime: 10, endTime: 22},
{startTime: 22, endTime: 34},
{startTime: 34, endTime: 46},
{startTime: 46, endTime: 58}
];

expect(result).toBeTruthy();
expect(result.length).toBe(references.length);
for (var i = 0; i < result.length; i++) {
expect(result[i].position).toBe(i);
expect(result[i].startTime).toBe(references[i].startTime);
expect(result[i].endTime).toBe(references[i].endTime);
}
});
});
20 changes: 20 additions & 0 deletions test/media/webm_segment_index_parser_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,24 @@ describe('WebmSegmentIndexParser', function() {
expect(result[i].endByte).toBe(references[i].endByte);
}
});

it('takes a scaled presentationTimeOffset in seconds', function() {
var result = parser.parse(indexSegment, initSegment, [], 2);
var references =
[
{startTime: -2, endTime: 10},
{startTime: 10, endTime: 22},
{startTime: 22, endTime: 34},
{startTime: 34, endTime: 46},
{startTime: 46, endTime: 58}
];

expect(result).toBeTruthy();
expect(result.length).toBe(references.length);
for (var i = 0; i < result.length; i++) {
expect(result[i].position).toBe(i);
expect(result[i].startTime).toBe(references[i].startTime);
expect(result[i].endTime).toBe(references[i].endTime);
}
});
});

0 comments on commit 6cf57fd

Please sign in to comment.