Skip to content

Commit

Permalink
Choose lowest bandwidth codecs
Browse files Browse the repository at this point in the history
Before asking AbrManager to choose streams, we should choose codecs.
Choose the most efficient codecs, and do it after we have filtered
out the codecs that the platform cannot use.

Closes #841

Change-Id: Ifbdd22b33254d4584f77db6456ab238f7c04c755
  • Loading branch information
joeyparrish committed Jun 5, 2017
1 parent 3d317b4 commit a452c24
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 0 deletions.
69 changes: 69 additions & 0 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,10 @@ shaka.Player.prototype.load = function(manifestUri, opt_startTime,
this.streamingEngine_ = this.createStreamingEngine();
this.streamingEngine_.configure(this.config_.streaming);

// If the content is multi-codec and the browser can play more than one of
// them, choose codecs now before we initialize streaming.
this.chooseCodecsAndFilterManifest_();

return this.streamingEngine_.init();
}.bind(this)).then(function() {
if (this.config_.streaming.startAtSegmentBoundary) {
Expand Down Expand Up @@ -619,6 +623,71 @@ shaka.Player.prototype.load = function(manifestUri, opt_startTime,
};


/**
* In case of multiple usable codecs, choose one based on lowest average
* bandwidth and filter out the rest.
* @private
*/
shaka.Player.prototype.chooseCodecsAndFilterManifest_ = function() {
function variantCodecs(variant) {
// Only consider the base of the codec string. For example, these should
// both be considered the same codec: avc1.42c01e, avc1.4d401f
var baseVideoCodec =
variant.video ? variant.video.codecs.split('.')[0] : '';
var baseAudioCodec =
variant.audio ? variant.audio.codecs.split('.')[0] : '';
return baseVideoCodec + '-' + baseAudioCodec;
}

// Organize variants into buckets by codecs.
var variantsByCodecs = {};
this.manifest_.periods.forEach(function(period) {
period.variants.forEach(function(variant) {
var codecs = variantCodecs(variant);
if (!(codecs in variantsByCodecs)) {
variantsByCodecs[codecs] = [];
}
variantsByCodecs[codecs].push(variant);
});
});

// Compute the average bandwidth for each group of variants.
// Choose the lowest-bandwidth codecs.
var bestCodecs = null;
var lowestAverageBandwidth = Infinity;
shaka.util.MapUtils.forEach(variantsByCodecs, function(codecs, variants) {
var sum = 0;
var num = 0;
variants.forEach(function(variant) {
sum += variant.bandwidth;
++num;
});
var averageBandwidth = sum / num;
shaka.log.debug('codecs', codecs, 'avg bandwidth', averageBandwidth);

if (averageBandwidth < lowestAverageBandwidth) {
bestCodecs = codecs;
lowestAverageBandwidth = averageBandwidth;
}
});
goog.asserts.assert(bestCodecs != null, 'Should have chosen codecs!');
goog.asserts.assert(!isNaN(lowestAverageBandwidth),
'Bandwidth should be a number!');

// Filter out any variants that don't match, forcing AbrManager to choose from
// the most efficient variants possible.
this.manifest_.periods.forEach(function(period) {
period.variants = period.variants.filter(function(variant) {
var codecs = variantCodecs(variant);
if (codecs == bestCodecs) return true;

shaka.log.debug('Dropping Variant (better codec available)', variant);
return false;
});
});
};


/**
* Creates a new instance of DrmEngine. This can be replaced by tests to
* create fake instances instead.
Expand Down
14 changes: 14 additions & 0 deletions lib/util/map_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,17 @@ shaka.util.MapUtils.every = function(object, callback) {
return callback(key, object[key]);
});
};


/**
* Invokes the callback for each entry in the map.
*
* @param {!Object.<KEY, VALUE>} object
* @param {function(KEY, VALUE)} callback
* @template KEY,VALUE
*/
shaka.util.MapUtils.forEach = function(object, callback) {
Object.keys(object).forEach(function(key) {
callback(key, object[key]);
});
};
37 changes: 37 additions & 0 deletions test/player_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2024,6 +2024,43 @@ describe('Player', function() {
}).then(done);
});

it('chooses efficient codecs and removes the rest', function(done) {
manifest = new shaka.test.ManifestGenerator()
.addPeriod(0)
// More efficient codecs
.addVariant(0).bandwidth(100)
.addVideo(0).mime('video/mp4', 'good')
.addVariant(1).bandwidth(200)
.addVideo(1).mime('video/mp4', 'good')
.addVariant(2).bandwidth(300)
.addVideo(2).mime('video/mp4', 'good')
// Less efficient codecs
.addVariant(3).bandwidth(10000)
.addVideo(3).mime('video/mp4', 'bad')
.addVariant(4).bandwidth(20000)
.addVideo(4).mime('video/mp4', 'bad')
.addVariant(5).bandwidth(30000)
.addVideo(5).mime('video/mp4', 'bad')
.build();

parser = new shaka.test.FakeManifestParser(manifest);
parserFactory = function() { return parser; };
player.load('', 0, parserFactory).then(function() {
// "initialize" the current period.
chooseStreams();
canSwitch();
}).catch(fail).then(function() {
expect(abrManager.setVariants).toHaveBeenCalled();
var variants = abrManager.setVariants.calls.argsFor(0)[0];
// We've already chosen codecs, so only 3 tracks should remain.
expect(variants.length).toBe(3);
// They should be the low-bandwidth ones.
expect(variants[0].video.codecs).toEqual('good');
expect(variants[1].video.codecs).toEqual('good');
expect(variants[2].video.codecs).toEqual('good');
}).then(done);
});

/**
* Gets the currently active track.
* @param {string} type
Expand Down

0 comments on commit a452c24

Please sign in to comment.