WebRTC中的远程视频为黑屏或空白

时间:2017-03-10 14:18:17

标签: webrtc

我在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);
    }
};

3 个答案:

答案 0 :(得分:3)

WebRTC 空白/空视频的原因之一是丢包率高。在这种情况下,在服务器和客户端日志中,它会显示连接成功且视频正常播放,因此您不会看到任何警告或错误。

要检查是否存在高丢包率,您可以在 firefox 上访问 about:webrtc,或者在 chrome 上访问 chrome://webrtc-internals。对于 Firefox,您可以导航到“RTP Stats”。您会看到它显示 Received: ... packetsLost: ... 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中,是否确实接收了远程视频轨道执行的计时器,然后在计时器选择器中添加了轨道以查看