我在java和websocket中有信令服务器。 它适用于本地视频。但远程视频是黑屏或空白 但它并不总是空白。如果关闭服务器并再次打开,远程视频将显示在遥控器上。 为什么有时候它总是不会出现,有时它不会出来?
这是我的代码......
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition
|| window.msSpeechRecognition || window.oSpeechRecognition;
var localVideoStream = null;
var peerConn = null,
wsc = new WebSocket("ws://localhost:8080/signaling"),
peerConnCfg = {
'iceServers': [{
'url': 'stun:stun.l.google.com:19302'
}]
};
var videoCallButton = document.getElementById("caller");
var endCallButton = document.getElementById("callee");
var localVideo = document.getElementById('localVideo');
var remoteVideo = document.getElementById('remoteVideo');
videoCallButton.addEventListener("click", initiateCall);
endCallButton.addEventListener("click", function (evt) {
wsc.send(JSON.stringify({"closeConnection": true }));
});
var sdpConstraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true
}
};
function prepareCall() {
peerConn = new RTCPeerConnection(peerConnCfg);
// send any ice candidates to the other peer
peerConn.onicecandidate = onIceCandidateHandler;
// once remote stream arrives, show it in the remote video element
peerConn.onaddstream = onAddStreamHandler;
};
// run start(true) to initiate a call
function initiateCall() {
prepareCall();
// get the local stream, show it in the local video element and send it
navigator.getUserMedia({ "audio": true, "video": true }, function (stream) {
localVideoStream = stream;
localVideo.src = URL.createObjectURL(localVideoStream);
peerConn.addStream(localVideoStream);
createAndSendOffer();
}, function(error) { console.log(error);});
};
function answerCall() {
prepareCall();
// get the local stream, show it in the local video element and send it
navigator.getUserMedia({ "audio": true, "video": true }, function (stream) {
localVideoStream = stream;
localVideo.src = URL.createObjectURL(localVideoStream);
peerConn.addStream(localVideoStream);
createAndSendAnswer();
}, function(error) { console.log(error);});
};
wsc.onmessage = function (evt) {
var signal = null;
if (!peerConn) answerCall();
signal = JSON.parse(evt.data);
if (signal.sdp) {
console.log("Received SDP from remote peer.");
console.log("signal"+ signal);
peerConn.setRemoteDescription(new RTCSessionDescription(signal.sdp));
}
else if (signal.candidate) {
console.log("signal"+ signal.candidate);
console.log("Received ICECandidate from remote peer.");
peerConn.addIceCandidate(new RTCIceCandidate(signal.candidate));
} else if ( signal.closeConnection){
console.log("Received 'close call' signal from remote peer.");
endCall();
}else{
console.log("signal"+ signal.candidate);
}
};
function createAndSendOffer() {
peerConn.createOffer(
function (offer) {
var off = new RTCSessionDescription(offer);
peerConn.setLocalDescription(new RTCSessionDescription(off),
function() {
wsc.send(JSON.stringify({"sdp": off }));
},
function(error) { console.log(error);}
);
},
function (error) { console.log(error);}
);
};
function createAndSendAnswer() {
peerConn.createAnswer(
function (answer) {
var ans = new RTCSessionDescription(answer);
peerConn.setLocalDescription(ans, function() {
wsc.send(JSON.stringify({"sdp": ans }));
},
function (error) { console.log(error);}
);
},
function (error) {console.log(error);}
);
};
function onIceCandidateHandler(evt) {
if (!evt || !evt.candidate) return;
wsc.send(JSON.stringify({"candidate": evt.candidate }));
};
function onAddStreamHandler(evt) {
videoCallButton.setAttribute("disabled", true);
endCallButton.removeAttribute("disabled");
// set remote video stream as source for remote video HTML5 element
remoteVideo.src = window.URL.createObjectURL(evt.stream);
remoteVideo.play();
console.log("remote src : "+ remoteVideo.src);
};
function endCall() {
peerConn.close();
peerConn = null;
videoCallButton.removeAttribute("disabled");
endCallButton.setAttribute("disabled", true);
if (localVideoStream) {
localVideoStream.getTracks().forEach(function (track) {
track.stop();
});
localVideo.src = "";
}
if (remoteVideo){
remoteVideo.src = "";
window.URL.revokeObjectURL(remoteVideo);
}
};
答案 0 :(得分:3)
WebRTC 空白/空视频的原因之一是丢包率高。在这种情况下,在服务器和客户端日志中,它会显示连接成功且视频正常播放,因此您不会看到任何警告或错误。
要检查是否存在高丢包率,您可以在 firefox 上访问 about:webrtc
,或者在 chrome 上访问 chrome://webrtc-internals
。对于 Firefox,您可以导航到“RTP Stats”。您会看到它显示 Received: ... packets
和 Lost: ... packets
。您可以使用这些计数来计算丢包率。对于 chrome,有一个丢包率图表。您的丢包率可能非常高,例如 70%。
如果出现这种极高的丢包率,原因之一是客户端网络接口上的 MTU https://en.wikipedia.org/wiki/Maximum_transmission_unit 比服务器使用的 MTU 小。例如,您的客户端网络接口在未连接到 VPN 时可以有 MTU=1500 字节,在连接到 VPN 时 MTU=1250 字节。如果服务器正在发送 MTU=1400 的 RTP 数据包(通过 UDP),如果客户端未使用 VPN,则客户端可以接收到,但大于 1250 字节的数据包将被客户端网络接口丢弃。
如果想在本地查看客户端 MTU,可以在 mac 或 linux 上运行 ifconfig
。
Mac 示例,没有示例 vpn:
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
...
inet SOME_IP netmask 0xffffff00 broadcast 192.168.1.255
media: autoselect
status: active
Mac 示例,以 vpn 为例:
utun2: flags=80d1<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1250
inet SOME_IP --> SOME_IP netmask 0xffffffff
nd6 options=201<PERFORMNUD,DAD>
如何为服务器配置 MTU:
如果您将 GStreamer 用于 WebRTC 服务器,则负载生成器元素(例如 rtpvp8pay
)具有设置所需 MTU 值的属性。
通过使用 gst-inspect-1.0 rtpvp8pay
,您可以看到它默认使用 1400 作为 MTU,这可能比您的客户端网络接口可以处理的要大(例如上面示例中的 1250)。
您可以通过在 GStreamer 管道上设置较低的 MTU 使其工作,以便您的客户端网络接口不会再丢弃大部分包(仅通过更改服务器上 GStreamer 管道上的 MTU,包丢失率可以降低到 0.01%)。< /p>
在这种情况下,当 VPN 重新连接时,传入的视频可以工作约 10 秒,然后传入的视频可能会冻结,随后的页面刷新可能导致只有 70% 以上的数据包丢失的空白视频。
这是一个非常具体的场景,但当它发生时,它是一个完全无声/隐藏的错误,所以希望这对某人有所帮助。
答案 1 :(得分:1)
将oniceconnectionstatechange
添加到prepeareCall函数,看看是否因NAT问题导致ICE失败
function prepareCall() {
peerConn = new RTCPeerConnection(peerConnCfg);
// send any ice candidates to the other peer
peerConn.onicecandidate = onIceCandidateHandler;
// once remote stream arrives, show it in the remote video element
peerConn.onaddstream = onAddStreamHandler;
peerConn.oniceconnectionstatechange = function(){
console.log('ICE state: ',peerConn.iceConnectionState);
}
};
答案 2 :(得分:0)
在func中,是否确实接收了远程视频轨道执行的计时器,然后在计时器选择器中添加了轨道以查看