-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Terrible video quality when using TransformStream with Simulcast
https://bugs.webkit.org/show_bug.cgi?id=257803 rdar://110395571 Reviewed by Jean-Yves Avenard. The libwebrtc backend provides one callback per ssrc generated by a sender. In non simulcast cases, there is only one callback. In simulcast case, there is one callback per simulcast encoding. Before the patch, we would use one callback for all video encoded frames, thus sending all simulcast streams in the same ssrc. This would confuse receivers and would lead to decoding failures, leading to sending PLI/FIR, thus leading to bad video quality. We are now storing these callbacks in a map, and use the callback associated to the ssrc of the frame. This allows to correctly send each simulcast stream with its associated ssrc. Covered by added test case. * LayoutTests/http/wpt/webrtc/video-script-transform-simulcast-expected.txt: Added. * LayoutTests/http/wpt/webrtc/video-script-transform-simulcast.html: Added. * LayoutTests/platform/glib/TestExpectations: * Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCRtpTransformBackend.cpp: (WebCore::LibWebRTCRtpTransformBackend::addOutputCallback): (WebCore::LibWebRTCRtpTransformBackend::removeOutputCallback): (WebCore::LibWebRTCRtpTransformBackend::sendFrameToOutput): (WebCore::LibWebRTCRtpTransformBackend::processTransformedFrame): (WebCore::LibWebRTCRtpTransformBackend::Transform): (WebCore::LibWebRTCRtpTransformBackend::RegisterTransformedFrameCallback): (WebCore::LibWebRTCRtpTransformBackend::RegisterTransformedFrameSinkCallback): (WebCore::LibWebRTCRtpTransformBackend::UnregisterTransformedFrameCallback): (WebCore::LibWebRTCRtpTransformBackend::UnregisterTransformedFrameSinkCallback): (WebCore::LibWebRTCRtpTransformBackend::setOutputCallback): Deleted. * Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCRtpTransformBackend.h: Canonical link: https://commits.webkit.org/268912@main
- Loading branch information
Showing
5 changed files
with
169 additions
and
21 deletions.
There are no files selected for viewing
5 changes: 5 additions & 0 deletions
5
LayoutTests/http/wpt/webrtc/video-script-transform-simulcast-expected.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
|
||
|
||
PASS setup | ||
PASS videos are playing with the correct resolutions | ||
|
127 changes: 127 additions & 0 deletions
127
LayoutTests/http/wpt/webrtc/video-script-transform-simulcast.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<script src="/resources/testharness.js"></script> | ||
<script src="/resources/testharnessreport.js"></script> | ||
<script src="/webrtc/third_party/sdp/sdp.js"></script> | ||
<script src="/webrtc/simulcast/simulcast.js"></script> | ||
</head> | ||
<body> | ||
<video id="videoL" controls autoplay playsInline></video> | ||
<video id="videoM" controls autoplay playsInline></video> | ||
<script> | ||
function waitForMessage(port, data) | ||
{ | ||
let gotMessage; | ||
const promise = new Promise((resolve, reject) => { | ||
gotMessage = resolve; | ||
setTimeout(() => { reject("did not get " + data) }, 5000); | ||
}); | ||
port.onmessage = event => { | ||
if (event.data === data) | ||
gotMessage(); | ||
}; | ||
return promise; | ||
} | ||
|
||
let worker; | ||
promise_test(async (test) => { | ||
worker = new Worker('context-transform.js'); | ||
const data = await new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); | ||
assert_equals(data, "registered"); | ||
|
||
const localStream = await navigator.mediaDevices.getUserMedia({video: true}); | ||
|
||
const senderChannel = new MessageChannel; | ||
const receiverChannelL = new MessageChannel; | ||
const receiverChannelM = new MessageChannel; | ||
let sender, receiverM, receiverL; | ||
const senderTransform = new RTCRtpScriptTransform(worker, {name:'MockRTCRtpTransform', mediaType:'video', side:'sender', port:senderChannel.port2}, [senderChannel.port2]); | ||
const receiverTransformL = new RTCRtpScriptTransform(worker, {name:'MockRTCRtpTransform', mediaType:'video', side:'receiver', port:receiverChannelL.port2}, [receiverChannelL.port2]); | ||
const receiverTransformM = new RTCRtpScriptTransform(worker, {name:'MockRTCRtpTransform', mediaType:'video', side:'receiver', port:receiverChannelM.port2}, [receiverChannelM.port2]); | ||
senderTransform.port = senderChannel.port1; | ||
receiverTransformL.port = receiverChannelL.port1; | ||
receiverTransformM.port = receiverChannelM.port1; | ||
|
||
const promise1 = waitForMessage(senderTransform.port, "started video sender"); | ||
const promise2 = waitForMessage(receiverTransformL.port, "started video receiver"); | ||
const promise3 = waitForMessage(receiverTransformM.port, "started video receiver"); | ||
|
||
await Promise.all([promise1, promise2, promise3]); | ||
|
||
const pc1 = new RTCPeerConnection(); | ||
const pc2 = new RTCPeerConnection(); | ||
|
||
pc1.onicecandidate = e => { | ||
const candidate = e.candidate; | ||
if (!candidate) | ||
return; | ||
const newCandidate = { candidate: candidate.candidate, sdpMid: "l", sdpMLineIndex:candidate.sdpMLineIndex }; | ||
pc2.addIceCandidate(newCandidate); | ||
} | ||
pc2.onicecandidate = e => { | ||
const candidate = e.candidate; | ||
if (!candidate) | ||
return; | ||
const newCandidate = { candidate: candidate.candidate, sdpMid: "0", sdpMLineIndex:candidate.sdpMLineIndex }; | ||
pc1.addIceCandidate(newCandidate); | ||
} | ||
|
||
sender = pc1.addTransceiver("video", {sendEncodings: [{rid: "l", scaleResolutionDownBy: 2}, {rid: "m", scaleResolutionDownBy: 1}]}).sender; | ||
sender.replaceTrack(localStream.getVideoTracks()[0]); | ||
sender.setStreams(localStream); | ||
sender.transform = senderTransform; | ||
|
||
const streamPromise = new Promise((resolve, reject) => { | ||
pc2.ontrack = (trackEvent) => { | ||
if (!receiverL) { | ||
receiverL = trackEvent.receiver; | ||
receiverL.transform = receiverTransformL; | ||
videoL.srcObject = trackEvent.streams[0]; | ||
return; | ||
} | ||
|
||
receiverM = trackEvent.receiver; | ||
receiverM.transform = receiverTransformM; | ||
videoM.srcObject = trackEvent.streams[0]; | ||
resolve(); | ||
}; | ||
setTimeout(() => reject("Getting stream timed out"), 5000); | ||
}); | ||
|
||
const offer = await pc1.createOffer(); | ||
await pc1.setLocalDescription(offer); | ||
await pc2.setRemoteDescription({ | ||
type: 'offer', | ||
sdp: swapRidAndMidExtensionsInSimulcastOffer(offer, ["l", "m"]), | ||
}); | ||
const answer = await pc2.createAnswer(); | ||
await pc2.setLocalDescription(answer); | ||
await pc1.setRemoteDescription({ | ||
type: 'answer', | ||
sdp: swapRidAndMidExtensionsInSimulcastAnswer(answer, pc1.localDescription, ["l", "m"]), | ||
}); | ||
|
||
await streamPromise; | ||
}, "setup"); | ||
|
||
async function waitForVideoSize(video, width, height) | ||
{ | ||
const max = 200 | ||
let counter = 0; | ||
while (++counter < max && video.videoWidth != width && video.videoHeight != height) | ||
await waitFor(50); | ||
|
||
if (counter === max) | ||
return Promise.reject("Video size not expected : " + video.videoWidth + " " + video.videoHeight); | ||
} | ||
|
||
promise_test(async () => { | ||
await Promise.all([videoL.play(), videoM.play()]); | ||
await waitForVideoSize(videoL, 320, 240); | ||
await waitForVideoSize(videoM, 640, 480); | ||
}, "videos are playing with the correct resolutions"); | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters