Skip to content

Commit

Permalink
feat(CodecPreference): Add preferred codecs config
Browse files Browse the repository at this point in the history
Adding the "preferredVideoCodecs" and "preferredAudioCodecs"
configuration, so that the application can set their own preferences
when choosing a codec.

Issue: shaka-project#2179
Change-Id: Ib56aec10613dda9b7dce8e465c5f1d81540c34fd
  • Loading branch information
michellezhuogg committed May 21, 2021
1 parent b426fe8 commit 9a706ef
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 17 deletions.
6 changes: 6 additions & 0 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,8 @@ shaka.extern.OfflineConfiguration;
* preferredTextLanguage: string,
* preferredVariantRole: string,
* preferredTextRole: string,
* preferredVideoCodecs: !Array.<string>,
* preferredAudioCodecs: !Array.<string>,
* preferredAudioChannelCount: number,
* preferredDecodingAttributes: !Array.<string>,
* preferForcedSubs: boolean,
Expand Down Expand Up @@ -1015,6 +1017,10 @@ shaka.extern.OfflineConfiguration;
* The preferred role to use for variants.
* @property {string} preferredTextRole
* The preferred role to use for text tracks.
* @property {!Array.<string>} preferredVideoCodecs
* The list of preferred video codecs, in order of highest to lowest priority.
* @property {!Array.<string>} preferredAudioCodecs
* The list of preferred audio codecs, in order of highest to lowest priority.
* @property {number} preferredAudioChannelCount
* The preferred number of audio channels.
* @property {!Array.<string>} preferredDecodingAttributes
Expand Down
2 changes: 1 addition & 1 deletion lib/cast/cast_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
'getBufferedInfo': 2,
// NOTE: The 'getSharedConfiguration' property is not proxied as it would
// not be possible to share a reference.
'getConfiguration': 2,
'getConfiguration': 4,
'getExpiration': 2,
'getKeyStatuses': 2,
// NOTE: The 'getManifest' property is not proxied, as it is very large.
Expand Down
5 changes: 4 additions & 1 deletion lib/offline/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,9 +502,12 @@ shaka.offline.Storage = class {
// Choose the codec that has the lowest average bandwidth.
const preferredAudioChannelCount = config.preferredAudioChannelCount;
const preferredDecodingAttributes = config.preferredDecodingAttributes;
const preferredVideoCodecs = config.preferredVideoCodecs;
const preferredAudioCodecs = config.preferredAudioCodecs;

StreamUtils.chooseCodecsAndFilterManifest(
manifest, preferredAudioChannelCount, preferredDecodingAttributes);
manifest, preferredVideoCodecs, preferredAudioCodecs,
preferredAudioChannelCount, preferredDecodingAttributes);

for (const variant of manifest.variants) {
goog.asserts.assert(
Expand Down
5 changes: 4 additions & 1 deletion lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -1849,7 +1849,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
// If the content is multi-codec and the browser can play more than one of
// them, choose codecs now before we initialize streaming.
shaka.util.StreamUtils.chooseCodecsAndFilterManifest(
this.manifest_, this.config_.preferredAudioChannelCount,
this.manifest_,
this.config_.preferredVideoCodecs,
this.config_.preferredAudioCodecs,
this.config_.preferredAudioChannelCount,
this.config_.preferredDecodingAttributes);

this.streamingEngine_ = this.createStreamingEngine();
Expand Down
2 changes: 2 additions & 0 deletions lib/util/player_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ shaka.util.PlayerConfiguration = class {
preferredVariantRole: '',
preferredTextRole: '',
preferredAudioChannelCount: 2,
preferredVideoCodecs: [],
preferredAudioCodecs: [],
preferForcedSubs: false,
preferredDecodingAttributes: [],
restrictions: {
Expand Down
61 changes: 52 additions & 9 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,38 @@ shaka.util.StreamUtils = class {
* bandwidth and filter out the rest.
* Also filters out variants that have too many audio channels.
* @param {!shaka.extern.Manifest} manifest
* @param {!Array.<string>} preferredVideoCodecs
* @param {!Array.<string>} preferredAudioCodecs
* @param {number} preferredAudioChannelCount
* @param {!Array.<string>} preferredDecodingAttributes
*/
static chooseCodecsAndFilterManifest(manifest, preferredAudioChannelCount,
static chooseCodecsAndFilterManifest(manifest, preferredVideoCodecs,
preferredAudioCodecs, preferredAudioChannelCount,
preferredDecodingAttributes) {
const StreamUtils = shaka.util.StreamUtils;

// To start, consider a subset of variants based on audio channel
let variants = manifest.variants;
// To start, choose the codecs based on configured preferences if available.
if (preferredVideoCodecs.length || preferredAudioCodecs.length) {
variants = StreamUtils.choosePreferredCodecs(variants,
preferredVideoCodecs, preferredAudioCodecs);
}

// Consider a subset of variants based on audio channel
// preferences.
// For some content (#1013), surround-sound variants will use a different
// codec than stereo variants, so it is important to choose codecs **after**
// considering the audio channel config.
const variants = StreamUtils.filterVariantsByAudioChannelCount(
manifest.variants, preferredAudioChannelCount);
variants = StreamUtils.filterVariantsByAudioChannelCount(
variants, preferredAudioChannelCount);

// Now organize variants into buckets by codecs.
/** @type {!shaka.util.MultiMap.<shaka.extern.Variant>} */
let variantsByCodecs = StreamUtils.getVariantsByCodecs_(variants);
variantsByCodecs = StreamUtils.filterVariantsByDensity_(variantsByCodecs);

const bestCodecs = StreamUtils.chooseCodecs_(variantsByCodecs,
preferredDecodingAttributes);
const bestCodecs = StreamUtils.chooseCodecsByDecodingAttributes_(
variantsByCodecs, preferredDecodingAttributes);

// Filter out any variants that don't match, forcing AbrManager to choose
// from a single video codec and a single audio codec possible.
Expand Down Expand Up @@ -125,6 +135,39 @@ shaka.util.StreamUtils = class {
return maxDensity ? codecGroupsByDensity.get(maxDensity) : variantsByCodecs;
}

/**
* Choose the codecs by configured preferred audio and video codecs.
*
* @param {!Array<shaka.extern.Variant>} variants
* @param {!Array.<string>} preferredVideoCodecs
* @param {!Array.<string>} preferredAudioCodecs
* @return {!Array<shaka.extern.Variant>}
*/
static choosePreferredCodecs(variants, preferredVideoCodecs,
preferredAudioCodecs) {
let subset = variants;
for (const videoCodec of preferredVideoCodecs) {
const filtered = subset.filter((variant) => {
return variant.video && variant.video.codecs == videoCodec;
});
if (filtered.length) {
subset = filtered;
break;
}
}

for (const audioCodec of preferredAudioCodecs) {
const filtered = subset.filter((variant) => {
return variant.audio && variant.audio.codecs == audioCodec;
});
if (filtered.length) {
subset = filtered;
break;
}
}
return subset;
}

/**
* Choose the codecs by configured preferred decoding attributes.
*
Expand All @@ -133,7 +176,7 @@ shaka.util.StreamUtils = class {
* @return {string}
* @private
*/
static chooseCodecs_(variantsByCodecs, attributes) {
static chooseCodecsByDecodingAttributes_(variantsByCodecs, attributes) {
const StreamUtils = shaka.util.StreamUtils;

for (const attribute of attributes) {
Expand Down Expand Up @@ -180,7 +223,7 @@ shaka.util.StreamUtils = class {
}

const averageScore = sum / num;
shaka.log.info('codecs', codecs, 'avg', attribute, averageScore);
shaka.log.debug('codecs', codecs, 'avg', attribute, averageScore);

if (averageScore > highestScore) {
bestVariantsByCodecs.clear();
Expand Down Expand Up @@ -256,7 +299,7 @@ shaka.util.StreamUtils = class {
* Filter the variants in |manifest| to only include the variants that meet
* the given restrictions.
*
* @param {shaka.extern.Manifest} manifest
* @param {!shaka.extern.Manifest} manifest
* @param {shaka.extern.Restrictions} restrictions
* @param {{width: number, height:number}} maxHwResolution
*/
Expand Down
80 changes: 75 additions & 5 deletions test/util/stream_utils_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,7 @@ describe('StreamUtils', () => {
variant.bandwidth = 5058558;
variant.addAudio(1, (stream) => {
stream.bandwidth = 129998;
stream.codecs = 'opus';
});
variant.addVideo(2, (stream) => {
stream.bandwidth = 4928560;
Expand All @@ -677,6 +678,7 @@ describe('StreamUtils', () => {
variant.bandwidth = 4911000;
variant.addAudio(4, (stream) => {
stream.bandwidth = 129998;
stream.codecs = 'vorbis';
});
variant.addVideo(5, (stream) => {
stream.bandwidth = 4781002;
Expand All @@ -691,6 +693,7 @@ describe('StreamUtils', () => {
variant.bandwidth = 10850316;
variant.addAudio(7, (stream) => {
stream.bandwidth = 129998;
stream.codecs = 'opus';
});
variant.addVideo(8, (stream) => {
stream.bandwidth = 10784324;
Expand All @@ -700,18 +703,77 @@ describe('StreamUtils', () => {
});
};

it('chooses preferred audio and video codecs', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
addVariant1080Avc1(manifest);
addVariant1080Vp9(manifest);
addVariant2160Vp9(manifest);
});
const variants =
shaka.util.StreamUtils.choosePreferredCodecs(manifest.variants,
/* preferredVideoCodecs= */[vp09Codecs],
/* preferredAudioCodecs= */['opus']);

expect(variants.length).toBe(1);
expect(variants[0].video.codecs).toBe(vp09Codecs);
expect(variants[0].audio.codecs).toBe('opus');
});

it('chooses preferred video codecs', () => {
// If no preferred audio codecs is specified or can be found, choose the
// variants with preferred video codecs.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
addVariant1080Avc1(manifest);
addVariant1080Vp9(manifest);
addVariant2160Vp9(manifest);
});
const variants =
shaka.util.StreamUtils.choosePreferredCodecs(manifest.variants,
/* preferredVideoCodecs= */[vp09Codecs],
/* preferredAudioCodecs= */[]);

expect(variants.length).toBe(2);
expect(variants[0].video.codecs).toBe(vp09Codecs);
expect(variants[0].audio.codecs).toBe('vorbis');
expect(variants[1].video.codecs).toBe(vp09Codecs);
expect(variants[1].audio.codecs).toBe('opus');
});

it('chooses preferred audio codecs', () => {
// If no preferred video codecs is specified or can be found, choose the
// variants with preferred audio codecs.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
addVariant1080Avc1(manifest);
addVariant1080Vp9(manifest);
addVariant2160Vp9(manifest);
});
const variants =
shaka.util.StreamUtils.choosePreferredCodecs(manifest.variants,
/* preferredVideoCodecs= */['foo'],
/* preferredAudioCodecs= */['opus']);

expect(variants.length).toBe(2);
expect(variants[0].video.codecs).toBe(avc1Codecs);
expect(variants[0].audio.codecs).toBe('opus');
expect(variants[1].video.codecs).toBe(vp09Codecs);
expect(variants[1].audio.codecs).toBe('opus');
});

it('chooses variants with different sizes (density) by codecs', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
addVariant1080Avc1(manifest);
addVariant1080Vp9(manifest);
addVariant2160Vp9(manifest);
});

shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest, 2, []);
shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
/* preferredVideoCodecs= */[],
/* preferredAudioCodecs= */[],
/* preferredAudioChannelCount= */2,
/* preferredDecodingAttributes= */[]);

expect(manifest.variants.length).toBe(2);
expect(manifest.variants.length).toBe(1);
expect(manifest.variants[0].video.codecs).toBe(vp09Codecs);
expect(manifest.variants[1].video.codecs).toBe(vp09Codecs);
});

it('chooses variants with same sizes (density) by codecs', () => {
Expand All @@ -720,7 +782,11 @@ describe('StreamUtils', () => {
addVariant1080Vp9(manifest);
});

shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest, 2, []);
shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
/* preferredVideoCodecs= */[],
/* preferredAudioCodecs= */[],
/* preferredAudioChannelCount= */2,
/* preferredDecodingAttributes= */[]);

expect(manifest.variants.length).toBe(1);
expect(manifest.variants[0].video.codecs).toBe(vp09Codecs);
Expand Down Expand Up @@ -759,7 +825,11 @@ describe('StreamUtils', () => {
await StreamUtils.getDecodingInfosForVariants(manifest.variants,
/* usePersistentLicenses= */false);

shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest, 2,
shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
/* preferredVideoCodecs= */[],
/* preferredAudioCodecs= */[],
/* preferredAudioChannelCount= */2,
/* preferredDecodingAttributes= */
[shaka.util.StreamUtils.DecodingAttributes.SMOOTH]);
// 2 video codecs are smooth. Choose the one with the lowest bandwidth.
expect(manifest.variants.length).toBe(1);
Expand Down

0 comments on commit 9a706ef

Please sign in to comment.