我的 webrtc 在我的 sdp 中发送 recvonly 方向

Posted

技术标签:

【中文标题】我的 webrtc 在我的 sdp 中发送 recvonly 方向【英文标题】:My webrtc sends recvonly direction in my sdp 【发布时间】:2020-11-06 07:34:20 【问题描述】:

我尝试使用 webrtc 进行视频通话,但在测试时遇到了错误。我打算将此项目集成到 我的 android 应用程序的 webview 中。我使用 phone-pc 和 phone-phone 进行测试。

Case A当电脑初始化调用时一切正常。 没有问题

案例 B,当手机初始化对 pc 或其他手机的呼叫时,Receiver 在 SDp 中发回 recvonly 信息。 这是日志:

v=0\r\no=- 8723618501842184555 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=msid-semantic: WMS\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111 103 9 0 8 105 13 110 113 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:dfKu\r\na=ice-pwd:XEe1Yy0kmjgMoiA0dVhymtDc\r\na=ice-options:trickle\r\na=fingerprint:sha-256 69:0D:95:A9:41:C7:C8:EF:57:0F:65:44:62:64:59:96:7C:40:6A:61:CE:62:0F:A3:E9:D7:1B:D0:F1:4C:13:BC\r\na=setup:active\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:s-s-rc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=recvonly\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:103 ISAC/16000\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:105 CN/16000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:113 telephone-event/16000\r\na=rtpmap:126 telephone-event/8000\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:dfKu\r\na=ice-pwd:XEe1Yy0kmjgMoiA0dVhymtDc\r\na=ice-options:trickle\r\na=fingerprint:sha-256 69:0D:95:A9:41:C7:C8:EF:57:0F:65:44:62:64:59:96:7C:40:6A:61:CE:62:0F:A3:E9:D7:1B:D0:F1:4C:13:BC\r\na=setup:active\r\na=mid:1\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:13 urn:3gpp:video-orientation\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=recvonly\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=fmtp:98 profile-id=0\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=98\r\na=rtpmap:100 red/90000\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:102 ulpfec/90000\r\n"

这是我的信令服务器

'use strict';
const HTTPS_PORT = 8443;

const fs = require('fs');
const https = require('https');
const WebSockets = require('ws');
const WebSocketServer = WebSockets.Server;

// Yes, TLS is required
const serverConfig = 
    key: fs.readFileSync('key.pem'),
    cert: fs.readFileSync('cert.pem'),
    requestCert: false
;

// ----------------------------------------------------------------------------------------

// Create a server for the client html page
const handleRequest = function(request, response) 
  // Render the single client html file for any request the HTTP server receives
  console.log('request received: ' + request.url);

    switch(request.url) 
      case '/':
        response.writeHead(200, 'Content-Type': 'text/html');
        response.end(fs.readFileSync('client/index.html'));
        break;
       
      case '/webrtc.js':
        response.writeHead(200, 'Content-Type': 'application/javascript');
        response.end(fs.readFileSync('client/webrtc.js'));
        break;
       
      default:
        // code block
      
;

const httpsServer = https.createServer(serverConfig, handleRequest);
httpsServer.listen(HTTPS_PORT, '0.0.0.0');

// ----------------------------------------------------------------------------------------

// Create a server for handling websocket calls
const wss = new WebSocketServer(server: httpsServer);

//No Sql Database to Store Rooms
var rooms = ;



wss.on('connection', function(socket) 
     
   var socketId; 
   var socketRoomId;

  socket.on('message', function(message)  
      const msg = JSON.parse(message);
      const action = msg.action;
      const userId = msg.uuid;
      const roomId = msg.roomId;
      socketId = userId;
      socketRoomId = roomId;
      console.log('************* \n\ Action : ' + action +" \n\ **************");
        switch (action) 
            case "join":
                const joined = joinRoom(socket, roomId, userId);
                if(joined)
                    console.log('************* \n\ The user joined successfully the room : ' + action +" \n\ **************");
                    msg.action = "ready";
                     
                    wss.broadcast(message);
                    console.log('************************ \n\ The Socket : ' + userId +" is READY \n\***************************");
                else
                    console.log('************* \n\ The Room : ' + action +" is full \n\ **************");
                    msg.action = "full";
                     
                    wss.broadcast(message);
                 
                break;
            case "signal":  
                console.log('************* \n\ user '+userId+' Sent signlals: ' + message +" \n\ **************"); 
                 
                wss.broadcast(message);
                break;
            case "bye":  
                leave(roomId, userId);
                msg.action = "leave";
                // emit(roomId, JSON.stringify(msg));
                wss.broadcast(message);
                console.log(`************* \n\  Peer said bye on room $roomId. \n\ **************`);
                break;
                
            default:
                console.log(`************* \n\ No action was supplied \n\ **************`);
                break;
        
    
  );
 
 
  socket.on("close", () => 
      if(socketId !== null) leave(socketRoomId, socketId);
      console.log(`************* \n\ Socket is closed \n\ **************`);
  );
 
);
 
//Remove the socket in the room
function leave(roomId, userId)
    // not present: do nothing
    if(!(roomId in rooms) || !(userId in rooms[roomId])) return;
    
     // if the one exiting is the last one, destroy the room
    if(Object.keys(rooms[roomId]).length === 1) delete rooms[roomId];
    // otherwise simply leave the room
    else delete rooms[roomId][userId];


 
function joinRoom(socket, room, userId)
    if(room in rooms)
        console.log('************* \n\ Room ' + room +" exists \n\ **************");
        var clients = Object.keys(rooms[room]).length;
        if(clients < 2) 
           console.log('************* \n\ No body is in the room ' + room +" \n\ **************"); 
           if(!(userId in rooms[room])) rooms[room][userId] = socket;
           console.log('************* \n\ the Room ' + room +" now has "+Object.keys(rooms[room]).length+" \n\ **************");
           return true;
        else
            console.log('************* \n\ the room ' + room +" is full \n\ **************"); 
            return false;
        
    
    else
        console.log('************* \n\ the Room ' + room +" does not exists yet \n\ **************");
        rooms[room] = ;
        rooms[room][userId] = socket;
        console.log('************* \n\ the Room ' + room +" now has "+Object.keys(rooms[room]).length+" \n\ **************");
        return true;
    


//Send the message to everyone in a room
function emit(room, message)
    if(room in rooms)
    Object.entries(rooms[room]).forEach(([, sock]) => sock.send(message));
 
//Send the message to everyone but me in a room
function emitButMe(ownerId, room, message)
    if(room in rooms)
    Object.entries(rooms[room]).forEach(([userid, sock]) => sends(ownerId, userid, sock, message));
 
function sends(ownerId, userId, socket, message)
    if(ownerId !== userId) socket.send(message);



wss.broadcast = function(data) 
  this.clients.forEach(function(client) 
    if(client.readyState === WebSockets.OPEN) 
      client.send(data);
    
  );
;
console.log('Server running. Visit https://localhost:' + HTTPS_PORT + ' in Firefox/Chrome.\n\n\
Some important notes:\n\
  * Note the HTTPS; there is no HTTP -> HTTPS redirect.\n\
  * You\'ll also need to accept the invalid TLS certificate.\n'
);

在客户端,我实现了以下 javascript 以便与服务器和对等方进行通信。

let localVideo;
let localStream;
let remoteVideo;
let peerConnection;
let uuid;
let serverConnection;

let peerConnectionConfig = 
    'iceServers': [
         'urls': 'stun:stun.stunprotocol.org:3478' ,
         'urls': 'stun:stun.l.google.com:19302' ,
    ]
;

window.onload = pageReady;
btnStart.onclick = _ => start(true);

async function pageReady() 
    uuid = (new MediaStream()).id;

    localVideo = document.getElementById('localVideo');
    remoteVideo = document.getElementById('remoteVideo');

    serverConnection = new WebSocket('wss://' + window.location.hostname + ':8443');
    serverConnection.onmessage = gotMessageFromServer;
    serverConnection.onopen = joinRoom;
    
    const constraints = 
        video: true,
        audio: true,
    ;

    if (navigator.mediaDevices.getUserMedia) 
        try 
            localStream = await navigator.mediaDevices.getUserMedia(constraints);
            localVideo.srcObject = localStream;
         catch (err) 
            errorHandler(err);
        
     else 
        alert('Your browser does not support getUserMedia API');
    



//Join a Room
function joinRoom() 
    serverConnection.send(JSON.stringify( action: 'join', roomId: 'salon', uuid ));


async function start(isCaller) 
    peerConnection = new RTCPeerConnection(peerConnectionConfig);
    peerConnection.onicecandidate = gotIceCandidate;
    peerConnection.ontrack = gotRemoteStream;
    localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));

    if (isCaller) 
        const offer = await peerConnection.createOffer();
        await createdDescription(offer);
    


async function gotMessageFromServer(message) 
    if (!peerConnection) start(false);

    const signal = JSON.parse(message);

    // Ignore messages from ourself
    if (signal.uuid == uuid) return;

    try 
        if (signal.sdp) 
            await peerConnection.setRemoteDescription(signal.sdp);
            // Only create answers in response to offers
            if (signal.sdp.type == 'offer') 
                const answer = await peerConnection.createAnswer();
                await createdDescription(answer);
            
         else if (signal.ice) 
            peerConnection.addIceCandidate(signal.ice);
        
     catch (err) 
        errorHandler(err);
    


function gotIceCandidate(event) 
    if (event.candidate != null) 
        serverConnection.send(JSON.stringify( ice: event.candidate, uuid, action: 'signal', roomId: 'salon' ));
    


async function createdDescription(description) 
    console.log('got description');
    try 
        await peerConnection.setLocalDescription(description);
        serverConnection.send(JSON.stringify( sdp: peerConnection.localDescription, uuid, action: 'signal', roomId: 'salon' ));
     catch (err) 
        errorHandler(err);
    


function gotRemoteStream(event) 
    console.log('got remote stream');
    remoteVideo.srcObject = event.streams[0];


function errorHandler(error) 
    console.error(error);

而Html页面是这样的:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
    <script src="webrtc.js"></script>
  </head>

  <body>
    <video id="localVideo" autoplay muted style="width:40%;"></video>
    <video id="remoteVideo" autoplay style="width:40%;"></video>

    <br />

    <input type="button" id="start" onclick="start(true)" value="Start Video"></input>

    <script type="text/javascript">
        pageReady();
    </script>
  </body>
</html>

【问题讨论】:

【参考方案1】:

因为您使用的是then()Promise,这是因为在创建远程peerConnection 时,它会在peerConnection.addStream() 之前创建并发送答案。 如果您不使用then() 并重写为async/await,它应该可以工作。 peerConnection.addStream() 也是旧规范,已从新规范中删除。你应该改成peerConnection.addTrack()

可执行示例https://codepen.io/gtk2k/pen/JjGmZaz?editors=1010 (在两个选项卡中打开示例页面,然后单击“开始”按钮)

let localVideo;
let localStream;
let remoteVideo;
let peerConnection;
let uuid;
let serverConnection;

let peerConnectionConfig = 
    'iceServers': [
         'urls': 'stun:stun.stunprotocol.org:3478' ,
         'urls': 'stun:stun.l.google.com:19302' ,
    ]
;

window.onload = pageReady;
btnStart.onclick = _ => start(true);

async function pageReady() 
    uuid = (new MediaStream()).id;

    localVideo = document.getElementById('localVideo');
    remoteVideo = document.getElementById('remoteVideo');

    serverConnection = new WebSocket('wss://' + window.location.hostname + ':8443');
    serverConnection.onmessage = gotMessageFromServer;
    serverConnection.onopen = joinRoom;
    
    const constraints = 
        video: true,
        audio: true,
    ;

    if (navigator.mediaDevices.getUserMedia) 
        try 
            localStream = await navigator.mediaDevices.getUserMedia(constraints);
            localVideo.srcObject = localStream;
         catch (err) 
            errorHandler(err);
        
     else 
        alert('Your browser does not support getUserMedia API');
    



//Join a Room
function joinRoom() 
    serverConnection.send(JSON.stringify( action: 'join', roomId: 'salon', uuid ));


async function start(isCaller) 
    peerConnection = new RTCPeerConnection(peerConnectionConfig);
    peerConnection.onicecandidate = gotIceCandidate;
    peerConnection.ontrack = gotRemoteStream;
    localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));

    if (isCaller) 
        const offer = await peerConnection.createOffer();
        await createdDescription(offer);
    


async function gotMessageFromServer(message) 
    if (!peerConnection) start(false);

    const signal = JSON.parse(message);

    // Ignore messages from ourself
    if (signal.uuid == uuid) return;

    try 
        if (signal.sdp) 
            await peerConnection.setRemoteDescription(signal.sdp);
            // Only create answers in response to offers
            if (signal.sdp.type == 'offer') 
                const answer = await peerConnection.createAnswer();
                await createdDescription(answer);
            
         else if (signal.ice) 
            peerConnection.addIceCandidate(signal.ice);
        
     catch (err) 
        errorHandler(err);
    


function gotIceCandidate(event) 
    if (event.candidate != null) 
        serverConnection.send(JSON.stringify( ice: event.candidate, uuid, action: 'signal', roomId: 'salon' ));
    


async function createdDescription(description) 
    console.log('got description');
    try 
        await peerConnection.setLocalDescription(description);
        serverConnection.send(JSON.stringify( sdp: peerConnection.localDescription, uuid, action: 'signal', roomId: 'salon' ));
     catch (err) 
        errorHandler(err);
    


function gotRemoteStream(event) 
    console.log('got remote stream');
    remoteVideo.srcObject = event.streams[0];


function errorHandler(error) 
    console.error(error);

【讨论】:

感谢您的回答,我觉得它很好但是@kenji tanaka 如果您提供示例代码,我只是 javascript 的初学者,它会有所帮助 感谢再版。我使用 chrome 在我的本地网络中连接了两部手机尝试了您的代码。但我仍然有同样的问题。我的服务器代码是否正确? 并注意到,如果我使用手机联系电脑,一切正常。但是如果手机收到来自电脑的电话我有同样的问题 请@kenji tanaka 我用您在客户端的回答更新了问题。但我仍然遇到上面评论中提到的问题

以上是关于我的 webrtc 在我的 sdp 中发送 recvonly 方向的主要内容,如果未能解决你的问题,请参考以下文章

如何在 WebRTC 音频通话中控制单声道/立体声?

iOS 上的 webRTC:无法发送 SDP 应答,RTCPeerConnection.setRemoteDescription() 失败

WebRTC:对多个对等连接使用相同的 SDP?

是否可以将 WebRTC SDP 报价转换为答案?

我如何创建webrtc的仅接收sdp报价?

如何设置 WebRTC 数据通道最大比特率?