Skip to content

Commit

Permalink
MSE: Fix mediasource-changetype-play to work with MseBufferByPts
Browse files Browse the repository at this point in the history
This web-platform-test exercises changeType as it splice-overlaps
pairs of audio or video media streams at varying offsets in the
presentation timeline. Splice-overlapping an out-of-order decode stream
(such as the test AVC MP4 media) at arbitrary times can, per spec, drop
significant decode dependencies from a partially-overlapped GOP such
that a buffered range gap could result.

This change is more careful about where it performs splice-overlaps when
the overlapped media is out-of-order-decode, adjusting the splice point
to be at or very near to the next overlapped keyframe. This prevents
removing out-of-order non-keyframes and their dependents from the
overlapped media such that no buffered range gap nor playback stall
should result.

Note that Chromium is sensitive to such out-of-order buffering overlaps
with the new, compliant, MseBufferByPts behavior. Fixing
w3c/media-source#160 could greatly simplify
this problem by allowing apps to explicitly control how the
user agent behaves at these small gaps.

BUG=807793

Change-Id: I020e244c230756eaa1804f81b58a577124a6a28b
  • Loading branch information
wolenetz authored and chromium-wpt-export-bot committed Mar 21, 2019
1 parent dbe68b6 commit 0df482c
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 24 deletions.
2 changes: 1 addition & 1 deletion media-source/mediasource-changetype-play.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
function generateChangeTypeTests(audio_types, video_types)
{
async_test(function(test) {
assert_true(audio_types.length > 1, "Browser doesn't support at least 2 types of audio test media" + audio_types);
assert_true(audio_types.length > 1, "Browser doesn't support at least 2 types of audio test media");
assert_true(video_types.length > 1, "Browser doesn't support at least 2 types of video test media");
test.done();
}, "Check if browser supports enough test media types");
Expand Down
122 changes: 99 additions & 23 deletions media-source/mediasource-changetype-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,52 @@ function findSupportedChangeTypeTestTypes(cb)
{
type: 'video/webm; codecs="vp8"',
is_video: true,
url: 'webm/test-v-128k-320x240-24fps-8kfr.webm'
url: 'webm/test-v-128k-320x240-24fps-8kfr.webm',
start_time: 0.0
// keyframe_interval: N/A since DTS==PTS so overlap-removal of
// non-keyframe should not produce a buffered range gap.
},
{
type: 'video/webm; codecs="vp9"',
is_video: true,
url: 'webm/test-vp9.webm'
url: 'webm/test-vp9.webm',
start_time: 0.0
// keyframe_interval: N/A since DTS==PTS so overlap-removal of
// non-keyframe should not produce a buffered range gap.
},
{
type: 'video/mp4; codecs="avc1.4D4001"',
is_video: true,
url: 'mp4/test-v-128k-320x240-24fps-8kfr.mp4'
url: 'mp4/test-v-128k-320x240-24fps-8kfr.mp4',
start_time: 0.083333,
keyframe_interval: 0.333333
},
{
type: 'audio/webm; codecs="vorbis"',
is_video: false,
url: 'webm/test-a-128k-44100Hz-1ch.webm'
url: 'webm/test-a-128k-44100Hz-1ch.webm',
start_time: 0.0
// keyframe_interval: N/A since DTS==PTS so overlap-removal of
// non-keyframe should not produce a buffered range gap. Also, all
// frames in this media are key-frames (it is audio).
},
{
type: 'audio/mp4; codecs="mp4a.40.2"',
is_video: false,
url: 'mp4/test-a-128k-44100Hz-1ch.mp4'
url: 'mp4/test-a-128k-44100Hz-1ch.mp4',
start_time: 0.0
// keyframe_interval: N/A since DTS==PTS so overlap-removal of
// non-keyframe should not produce a buffered range gap. Also, all
// frames in this media are key-frames (it is audio).
},
{
type: 'audio/mpeg',
is_video: false,
url: 'mp3/sound_5.mp3'
url: 'mp3/sound_5.mp3',
start_time: 0.0
// keyframe_interval: N/A since DTS==PTS so overlap-removal of
// non-keyframe should not produce a buffered range gap. Also, all
// frames in this media are key-frames (it is audio).
}
];

Expand All @@ -49,7 +69,6 @@ function findSupportedChangeTypeTestTypes(cb)
}
}


cb(audio_result, video_result);
}

Expand All @@ -76,41 +95,99 @@ function trimDuration(test, mediaElement, mediaSource, newDuration)
mediaSource.duration = newDuration;
}

function runChangeTypeTest(test, mediaElement, mediaSource, typeA, dataA, typeB, dataB)
function runChangeTypeTest(test, mediaElement, mediaSource, metadataA, dataA, metadataB, dataB)
{
var sourceBuffer = mediaSource.addSourceBuffer(typeA);
// Some streams, like the MP4 video stream, contain presentation times for
// frames out of order versus their decode times. If we overlap-append the
// latter part of such a stream's GOP presentation interval, a significant
// portion of decode-dependent non-keyframes with earlier presentation
// intervals could be removed and a presentation time buffered range gap
// could be introduced. Therefore, we test overlap appends with the overlaps
// occurring very near to a keyframe's presentation time to reduce the
// possibility of such a gap.

function findSafeOffset(targetTime, overlappedMediaMetadata, overlappedStartTime, overlappingMediaMetadata) {
assert_greater_than_equal(targetTime, overlappedStartTime);

var offset = targetTime;
if (overlappingMediaMetadata["start_time"] != undefined)
offset -= overlappingMediaMetadata["start_time"];

// If the media being overlapped is not out-of-order decode, then we can
// safely use the supplied times.
if (overlappedMediaMetadata["keyframe_interval"] == undefined) {
return { "offset": offset, "adjustedTime": targetTime };
}

// Otherwise, we're overlapping media that needs care to prevent
// introducing a gap. Adjust offset and adjustedTime to make the
// overlapping media start at the next overlapped media keyframe at or
// after targetTime.
var gopsToRetain = Math.ceil((targetTime - overlappedStartTime) / overlappedMediaMetadata["keyframe_interval"]);
var adjustedTime = overlappedStartTime + gopsToRetain * overlappedMediaMetadata["keyframe_interval"];

assert_greater_than_equal(adjustedTime, targetTime);
offset += adjustedTime - targetTime;
return { "offset": offset, "adjustedTime": adjustedTime };
}

var sourceBuffer = mediaSource.addSourceBuffer(metadataA.type);

appendBuffer(test, sourceBuffer, dataA);
var lastStart = metadataA["start_time"];
if (lastStart == undefined)
lastStart = 0.0;

// changeType A->B and append B starting at 0.5 seconds.
// changeType A->B and append the first media of B effectively at 0.5
// seconds (or at the first keyframe in A at or after 0.5 seconds if it has
// keyframe_interval defined).
test.waitForExpectedEvents(function()
{
sourceBuffer.changeType(typeB);
sourceBuffer.timestampOffset = 0.5;
var safeOffset = findSafeOffset(0.5, metadataA, lastStart, metadataB);
lastStart = safeOffset["adjustedTime"];
sourceBuffer.changeType(metadataB.type);
sourceBuffer.timestampOffset = safeOffset["offset"];
appendBuffer(test, sourceBuffer, dataB);
});

// changeType B->B and append B starting at 1.0 seconds.
// changeType B->B and append B starting at 1.0 seconds (or at the
// first keyframe in B at or after 1.0 seconds if it has keyframe_interval
// defined).
test.waitForExpectedEvents(function()
{
sourceBuffer.changeType(typeB);
sourceBuffer.timestampOffset = 1.0;
assert_less_than(lastStart, 1.0);
var safeOffset = findSafeOffset(1.0, metadataB, lastStart, metadataB);
lastStart = safeOffset["adjustedTime"];
sourceBuffer.changeType(metadataB.type);
sourceBuffer.timestampOffset = safeOffset["offset"];
appendBuffer(test, sourceBuffer, dataB);
});

// changeType B->A and append A starting at 1.5 seconds.
// changeType B->A and append A starting at 1.5 seconds (or at the first
// keyframe in B at or after 1.5 seconds if it has keyframe_interval
// defined).
test.waitForExpectedEvents(function()
{
sourceBuffer.changeType(typeA);
sourceBuffer.timestampOffset = 1.5;
assert_less_than(lastStart, 1.5);
var safeOffset = findSafeOffset(1.5, metadataB, lastStart, metadataA);
// Retain the previous lastStart because the next block will append data
// which begins between that start time and this block's start time.
sourceBuffer.changeType(metadataA.type);
sourceBuffer.timestampOffset = safeOffset["offset"];
appendBuffer(test, sourceBuffer, dataA);
});

// changeTypoe A->A and append A starting at 1.3 seconds.
// changeType A->A and append A starting at 1.3 seconds (or at the first
// keyframe in B at or after 1.3 seconds if it has keyframe_interval
// defined).
test.waitForExpectedEvents(function()
{
sourceBuffer.changeType(typeA);
sourceBuffer.timestampOffset = 1.3;
assert_less_than(lastStart, 1.3);
// Our next append will begin by overlapping some of metadataB, then some of
// metadataA.
var safeOffset = findSafeOffset(1.3, metadataB, lastStart, metadataA);
sourceBuffer.changeType(metadataA.type);
sourceBuffer.timestampOffset = safeOffset["offset"];
appendBuffer(test, sourceBuffer, dataA);
});

Expand Down Expand Up @@ -148,9 +225,8 @@ function mediaSourceChangeTypeTest(metadataA, metadataB, description)
mediaElement.addEventListener('error', test.unreached_func("Unexpected event 'error'"));
MediaSourceUtil.loadBinaryData(test, metadataA.url, function(dataA) {
MediaSourceUtil.loadBinaryData(test, metadataB.url, function(dataB) {
runChangeTypeTest(test, mediaElement, mediaSource, metadataA.type, dataA, metadataB.type, dataB);
runChangeTypeTest(test, mediaElement, mediaSource, metadataA, dataA, metadataB, dataB);
});
});
}, description);
}

0 comments on commit 0df482c

Please sign in to comment.