Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: PERIOD_FLATTENING_FAILED error when periods have different base sample types. #4206

Merged
merged 12 commits into from May 17, 2022
4 changes: 2 additions & 2 deletions lib/dash/dash_parser.js
Expand Up @@ -765,8 +765,8 @@ shaka.dash.DashParser = class {
// currently support that. Just choose one.
// TODO: https://github.com/shaka-project/shaka-player/issues/1528
stream.trickModeVideo = trickModeSet.streams.find((trickStream) =>
shaka.util.MimeUtils.getCodecBase(stream.codecs) ==
shaka.util.MimeUtils.getCodecBase(trickStream.codecs));
shaka.util.MimeUtils.getNormalizedCodec(stream.codecs) ==
shaka.util.MimeUtils.getNormalizedCodec(trickStream.codecs));
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions lib/util/mime_utils.js
Expand Up @@ -96,6 +96,28 @@ shaka.util.MimeUtils = class {
return codecs.split(',');
}

/**
* Get the codec from a codec string, ignoring differences in base sample
* types declared in the fourCC.
*
* @param {string} codecString
* @return {string}
*/
static getNormalizedCodec(codecString) {
ghouet marked this conversation as resolved.
Show resolved Hide resolved
switch (shaka.util.MimeUtils.getCodecBase(codecString)) {
ghouet marked this conversation as resolved.
Show resolved Hide resolved
case 'avc1':
case 'avc3':
return 'avc';
case 'hvc1':
case 'hev1':
return 'hevc';
case 'dvh1':
case 'dvhe':
return 'dovi';
ghouet marked this conversation as resolved.
Show resolved Hide resolved
}
return codecString;
ghouet marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Get the base codec from a codec string.
*
Expand Down
9 changes: 5 additions & 4 deletions lib/util/periods.js
Expand Up @@ -573,8 +573,8 @@ shaka.util.PeriodCombiner = class {
// TODO(#1528): Consider changing this when we support codec switching.
const hasCodec = outputStreams.some((s) => {
return s.mimeType == stream.mimeType &&
shaka.util.MimeUtils.getCodecBase(s.codecs) ==
shaka.util.MimeUtils.getCodecBase(stream.codecs);
shaka.util.MimeUtils.getNormalizedCodec(s.codecs) ==
shaka.util.MimeUtils.getNormalizedCodec(stream.codecs);
});
if (!hasCodec) {
continue;
Expand Down Expand Up @@ -1050,10 +1050,11 @@ shaka.util.PeriodCombiner = class {
* @private
*/
static areAVStreamsCompatible_(outputStream, candidate) {
const getCodecBase = (codecs) => shaka.util.MimeUtils.getCodecBase(codecs);
const getCodec = (codecs) =>
shaka.util.MimeUtils.getNormalizedCodec(codecs);
// Check MIME type and codecs, which should always be the same.
if (candidate.mimeType != outputStream.mimeType ||
getCodecBase(candidate.codecs) != getCodecBase(outputStream.codecs)) {
getCodec(candidate.codecs) != getCodec(outputStream.codecs)) {
return false;
}

Expand Down
6 changes: 4 additions & 2 deletions lib/util/stream_utils.js
Expand Up @@ -283,12 +283,14 @@ shaka.util.StreamUtils = class {
// both be considered the same codec: avc1.42c01e, avc1.4d401f
let baseVideoCodec = '';
if (variant.video) {
baseVideoCodec = shaka.util.MimeUtils.getCodecBase(variant.video.codecs);
baseVideoCodec =
shaka.util.MimeUtils.getNormalizedCodec(variant.video.codecs);
}

let baseAudioCodec = '';
if (variant.audio) {
baseAudioCodec = shaka.util.MimeUtils.getCodecBase(variant.audio.codecs);
baseAudioCodec =
shaka.util.MimeUtils.getNormalizedCodec(variant.audio.codecs);
}

return baseVideoCodec + '-' + baseAudioCodec;
Expand Down
66 changes: 66 additions & 0 deletions test/util/periods_unit.js
Expand Up @@ -996,6 +996,72 @@ describe('PeriodCombiner', () => {
expect(audio2.originalId).toBe('2,4');
});

it('Matches streams with related codecs', async () => {
const stream1 = makeVideoStream(1080);
stream1.originalId = '1';
stream1.bandwidth = 120000;
stream1.codecs = 'hvc1.1.4.L126.B0';

const stream2 = makeVideoStream(1080);
stream2.originalId = '2';
stream2.bandwidth = 120000;
stream2.codecs = 'hev1.2.4.L123.B0';

const stream3 = makeVideoStream(1080);
stream3.originalId = '3';
stream3.bandwidth = 120000;
stream3.codecs = 'dvhe.05.01';

const stream4 = makeVideoStream(1080);
stream4.originalId = '4';
stream4.bandwidth = 120000;
stream4.codecs = 'dvh1.05.01';

const stream5 = makeVideoStream(1080);
stream5.originalId = '5';
stream5.bandwidth = 120000;
stream5.codecs = 'avc1.42001f';

const stream6 = makeVideoStream(1080);
stream6.originalId = '6';
stream6.bandwidth = 120000;
stream6.codecs = 'avc3.42001f';

/** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
const periods = [
{
id: '0',
videoStreams: [
stream1, stream3, stream5,
],
audioStreams: [],
textStreams: [],
imageStreams: [],
},
{
id: '1',
videoStreams: [
stream2, stream4, stream6,
],
audioStreams: [],
textStreams: [],
imageStreams: [],
},
];

await combiner.combinePeriods(periods, /* isDynamic= */ true);
const variants = combiner.getVariants();
expect(variants.length).toBe(3);
// We can use the originalId field to see what each track is composed of.
const video1 = variants[0].video;
expect(video1.originalId).toBe('1,2');

const video2 = variants[1].video;
expect(video2.originalId).toBe('3,4');

const video3 = variants[2].video;
expect(video3.originalId).toBe('5,6');
});

it('Matches streams with most roles in common', async () => {
const makeAudioStreamWithRoles = (roles) => {
Expand Down