WebRTC 代码实现详述

(一)WebRTC 通过 HTTP 交换信令的处理流程

1. WebRTC 基本介绍

WebRTC (Web Real-Time Communication) 是一种支持浏览器进行实时媒体通信的技术,允许音视频通话和数据共享,而不需要安装额外的插件。为了使 WebRTC 进行 P2P(点对点)通信,必须通过信令服务来交换 SDP(Session Description Protocol)和 ICE(Interactive Connectivity Establishment)候选者信息。接下来介绍如何使用 HTTP 来交换 WebRTC 信令。

2. 创建端和接收端的概念

  • 创建端(Offerer):发送 SDP offer 的一方。
  • 接收端(Answerer):接受并回复 SDP answer 的一方。

在 WebRTC 中,创建端首先创建一个 offer,通过 HTTP 请求发送到信令服务器,接收端接收到 offer 后,生成 answer 并返回给创建端,完成连接。

3. 创建端的代码示例

// 创建 RTCPeerConnection 对象
let pc = new RTCPeerConnection();

// 当接收到远端的媒体流时,设置到本地的 video 元素中
pc.ontrack = function(event) {
    document.querySelector("video").srcObject = event.streams[0];
};

// 添加接收音频和视频的 Transceivers
pc.addTransceiver("audio", { direction: "recvonly" });
pc.addTransceiver("video", { direction: "recvonly" });

// 创建 SDP Offer
pc.createOffer().then(offer => {
    return pc.setLocalDescription(offer).then(() => {
        return offer;
    });
}).then(offer => {
    // 将 SDP Offer 通过 HTTP 发送到信令服务器
    return new Promise((resolve, reject) => {
        let url = "http://signaling-server-ip";
        let data = {
            api: url,
            streamurl: "stream-url",
            clientip: null,
            sdp: offer.sdp
        };

        // 发送 HTTP POST 请求
        fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
        }).then(response => response.json())
        .then(res => resolve(res.sdp))
        .catch(reject);
    });
}).then(answerSdp => {
    // 设置远端 SDP Answer
    return pc.setRemoteDescription(new RTCSessionDescription({
        type: "answer",
        sdp: answerSdp
    }));
}).catch(error => {
    console.error("Error during WebRTC negotiation:", error);
});

4. 接收端的代码示例

接收端的处理逻辑包括接收 offer,生成 answer 并发送回创建端。

// 接收端的 RTCPeerConnection 配置
let pc = new RTCPeerConnection();

// 当接收到远端的媒体流时,处理媒体流
pc.ontrack = function(event) {
    document.querySelector("video").srcObject = event.streams[0];
};

// 接收 SDP Offer,并设置为远端描述
fetch('http://signaling-server-ip', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ streamurl: 'stream-url' })
}).then(response => response.json())
.then(offerSdp => {
    return pc.setRemoteDescription(new RTCSessionDescription({
        type: "offer",
        sdp: offerSdp
    }));
}).then(() => {
    // 创建 SDP Answer 并设置本地描述
    return pc.createAnswer();
}).then(answer => {
    return pc.setLocalDescription(answer).then(() => {
        return answer;
    });
}).then(answer => {
    // 发送 SDP Answer 回到创建端
    return fetch('http://signaling-server-ip', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            sdp: answer.sdp,
            streamurl: 'stream-url'
        })
    });
}).catch(error => {
    console.error("Error during WebRTC negotiation:", error);
});

5. 说明

RTCPeerConnection
  • RTCPeerConnection 是 WebRTC 中核心的对象,用于创建、保持和监控 P2P 连接。
  • 在本例中,我们设置了 ontrack 事件监听器来接收远程媒体流,并通过 srcObject 将其传输到 HTML video 元素。
Transceivers
  • addTransceiver 用于在 P2P 连接中添加发送或接收音频和视频的功能。
  • 我们使用了 recvonly 模式,表示该端只接收媒体流。
Signaling Server
  • 信令服务器在本例中通过 HTTP POST 请求来实现 SDP 的交换。
  • createOffercreateAnswer 分别创建了 offeranswer,通过 HTTP 发送和接收。

6. 完整流程示意图

Offerer Signaling Server Answerer Send SDP Offer Forward SDP Offer Send SDP Answer Forward SDP Answer Set Remote Description Set Local Description Offerer Signaling Server Answerer

7. 运行步骤

  1. 创建端:运行创建端的代码来生成 offer 并通过 HTTP 发送到信令服务器。
  2. 接收端:接收端的代码运行后,接收 offer,生成 answer,并通过 HTTP 返回。
  3. 连接成功:创建端和接收端各自设置远程描述,成功建立 P2P 连接。

问题1:接收端如何知道来自于创建端的连接请求?

上面通过http来交换信令的方式存在一个很大的问题,在 WebRTC 信令过程中,接收端确实需要通过某种方式来知道何时接收到创建端发来的 SDP offer。通常情况下,接收端并不会主动发送 HTTP 请求,而是通过信令服务器被动接收 SDP offer。在我的代码示例中,接收端发送 HTTP 请求的设计并不准确,因为它应该由信令服务器来通知接收端,而不是接收端主动获取 SDP offer

(二)WebRTC 通过 WebSocket 交换信令的处理流程

正确的信令逻辑

接收端不应该主动发送 HTTP 请求来获取 offer,而是由信令服务器通知接收端。例如,通过轮询、WebSocket 或其他实时通信机制,接收端会在信令服务器通知它收到 SDP offer 时,开始处理。然后,接收端在处理完 offer 后,将 SDP answer 发送回信令服务器。

1. 通过 WebSocket 实现更合适的信令

相较于 HTTP 请求,更推荐使用 WebSocket 来进行实时信令传输。WebSocket 能够实现双向通信,信令服务器可以实时推送 SDP offer 给接收端,接收端处理后将 SDP answer 发送回去。

修改后的信令流程
  • 创建端:生成 SDP offer,并通过信令服务器发送给接收端。
  • 信令服务器:将 offer 转发给接收端。
  • 接收端:接收到 offer 后,生成 SDP answer,并发送回信令服务器。
  • 信令服务器:将 answer 转发给创建端。

2. 创建端的代码

以下代码展示了如何使用 WebSocket 来实现创建端(Offerer),生成 SDP offer 并发送给信令服务器。

// 创建 WebSocket 连接到信令服务器
const signalingSocket = new WebSocket("ws://signaling-server-ip");

const configuration = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };
const pc = new RTCPeerConnection(configuration);

// 获取本地媒体流
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
  .then(stream => {
    document.querySelector("#localVideo").srcObject = stream;
    stream.getTracks().forEach(track => pc.addTrack(track, stream));
  }).catch(error => {
    console.error("Error accessing media devices.", error);
  });

// 处理 ICE 候选
pc.onicecandidate = (event) => {
  if (event.candidate) {
    // 发送 ICE 候选给远端
    signalingSocket.send(JSON.stringify({
      type: 'ice-candidate',
      candidate: event.candidate
  }));
  }
};

// 当接收到远端的媒体流时,设置到本地的 video 元素中
pc.ontrack = function(event) {
    document.querySelector("video").srcObject = event.streams[0];
};

// 添加音频和视频 Transceivers
pc.addTransceiver("audio", { direction: "recvonly" });
pc.addTransceiver("video", { direction: "recvonly" });

// WebSocket 连接成功后,生成 SDP offer 并发送到信令服务器
signalingSocket.onopen = function() {
    pc.createOffer().then(offer => {
        return pc.setLocalDescription(offer).then(() => {
            // 通过 WebSocket 发送 SDP offer 到信令服务器
            signalingSocket.send(JSON.stringify({
                type: "offer",
                sdp: offer.sdp
            }));
        });
    }).catch(error => {
        console.error("Error creating offer:", error);
    });
};

// 接收来自信令服务器的 SDP answer,并设置为远端描述
signalingSocket.onmessage = function(event) {
    let data = JSON.parse(event.data);

    if (data.type === "answer") {
        pc.setRemoteDescription(new RTCSessionDescription({
            type: "answer",
            sdp: data.sdp
        })).then(() => {
            console.log("Successfully set remote description.");
        }).catch(error => {
            console.error("Error setting remote description:", error);
        });
    }

    // 处理来自创建端的 ICE 候选信息
    else if (data.type === 'ice-candidate') {
      pc.addIceCandidate(new RTCIceCandidate(data.candidate));
    }   
};

// 处理 WebSocket 连接关闭和错误
signalingSocket.onclose = function() {
    console.log("WebSocket connection closed.");
};

signalingSocket.onerror = function(error) {
    console.error("WebSocket error:", error);
};
2.1 创建端流程分析
  • RTCPeerConnection:用于管理和协商点对点连接。
  • createOffer():创建一个 SDP offer,并通过 setLocalDescription 设置本地描述。
  • WebSocket 发送 offer:通过 WebSocket 连接将 offer 发送到信令服务器。
  • 接收 SDP answer:通过 WebSocket 接收接收端生成的 SDP answer,并将其设置为远端描述。

3. 接收端的代码

以下是接收端(Answerer)的代码,展示了如何通过 WebSocket 接收 SDP offer,生成 SDP answer,并返回给信令服务器。

// WebSocket 连接到信令服务器
const signalingSocket = new WebSocket("ws://signaling-server-ip");

const configuration = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };
const pc = new RTCPeerConnection(configuration);

// 获取本地媒体流
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
  .then(stream => {
    document.querySelector("#localVideo").srcObject = stream;
    stream.getTracks().forEach(track => pc.addTrack(track, stream));
  }).catch(error => {
    console.error("Error accessing media devices.", error);
  });

// 处理 ICE 候选
pc.onicecandidate = (event) => {
  if (event.candidate) {
    signalingSocket.send(JSON.stringify({
          type: 'ice-candidate',
          candidate: event.candidate
      }));
  }
};


// 当接收到远端媒体流时,处理媒体流
pc.ontrack = function(event) {
    document.querySelector("video").srcObject = event.streams[0];
};

// 当 WebSocket 接收到消息时,处理 SDP offer
signalingSocket.onmessage = function(event) {
    let data = JSON.parse(event.data);

    if (data.type === 'offer') {
            // 设置远端 SDP offer
        pc.setRemoteDescription(new RTCSessionDescription({
            type: 'offer',
            sdp: data.sdp
        })).then(() => {
            // 创建 SDP answer
            return pc.createAnswer();
        }).then(answer => {
            // 设置本地 SDP answer
            return pc.setLocalDescription(answer).then(() => {
                return answer;
            });
        }).then(answer => {
            // 通过 WebSocket 发送 SDP answer 回信令服务器
            signalingSocket.send(JSON.stringify({
                type: 'answer',
                sdp: answer.sdp
            }));
        }).catch(error => {
            console.error("Error during WebRTC negotiation:", error);
        });
    }
    // 处理来自创建端的 ICE 候选信息
    else if (data.type === 'ice-candidate') {
      pc.addIceCandidate(new RTCIceCandidate(data.candidate));
    }   
};
3.1 接收端流程分析
  • RTCPeerConnection:同样用于管理 P2P 连接。
  • 接收 SDP offer:通过 WebSocket 接收来自创建端的 SDP offer,并将其设置为远端描述。
  • createAnswer():生成一个 SDP answer 并通过 WebSocket 返回给信令服务器。
  • WebSocket 发送 answer:将 answer 通过 WebSocket 发送回创建端。

4 说明

  • 上面代码主要目的在说明基本的流程,具体的逻辑需要根据需要来调整,比如接收端的 pc 创建可以在 socket 接收到消息之后;比如 ice 候选项发送可以在 socket 创建完成之后 open 事件发生之后 等等,总而言之,具体逻辑需要自己调整
  • 本文主要在于介绍网页端的创建接收实现代码,这是需要与信令服务器配合定协议的,本文也不涉及信令服务器的代码。

唯一标识符?

其实到这里也还是有问题的,创建端和接收端如何进行匹配的,简单说就是 clientA 找到 clientB,而不是找到 clientC,反之,clientB 怎么就知道 回复给 clientA?
这里的关键也还是信令服务器的中转,实现方法各异,下面提供一种常见思路;
创建端与指定接收端进行通信的关键在于使用唯一标识符(如房间号或用户 ID)来进行匹配和路由。简要流程如下:

  1. 创建端加入房间:创建端通过 WebSocket 向信令服务器发送一个请求,指定一个房间号或用户 ID,表示要加入这个房间。

  2. 信令服务器匹配房间:信令服务器根据房间号或用户 ID 来匹配已经加入的接收端。如果有接收端已经在这个房间,服务器会将创建端的信令(例如 SDP offer)转发给该接收端。

  3. 接收端接收信令:接收端加入相同房间后,信令服务器将创建端的信令(如 SDP offer)发送给接收端。

  4. 接收端发送响应:接收端生成 SDP answer 并通过信令服务器发送回创建端。

通过这种方式,信令服务器充当中间人角色,利用唯一标识符将创建端和接收端正确匹配并建立通信。

总结

通过 WebSocket 来实现 WebRTC 的信令交换,能有效提高实时性和减少通信延迟。信令服务器在这个过程中承担了创建端和接收端之间信令的中转角色,WebSocket 的全双工通信使得信令交换变得更加流畅。

拓展

通过 WebRTC 可以实现 A 与 B 建立音视频、数据的交换等基本操作。但随着对即时通讯场景的发展与要求,比如音频、视频通话、即时消息、在线游戏和其他多媒体通信形式、视频会议的创建与管理等。这时候信令服务器就会越来越复杂,这时候就需要一种会话协议来进行管理与信令的处理。目前应用最多的是 SIP 协议

  • SIP(Session Initiation Protocol,会话发起协议)是一种用于建立、修改和终止多媒体通信会话的应用层协议
  • WebRTC主要用于基于浏览器操纵音视频媒体,进行实时通信。
  • SIP是个信令协议,其本身并不实现实时音视频通信,它只是用于建立用于实时音视频通信的会话。

因此 SIP WebRTC 常常配合使用,在浏览器中实现 SIP 协议,支持 WebRTC 的开源库有如 SipML5、sip.js等。。。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个使用WebRTC实现音频降噪的示例代码: ```javascript // 创建一个音频上下文 const audioContext = new (window.AudioContext || window.webkitAudioContext)(); // 获取用户媒体流(麦克风输入) navigator.mediaDevices.getUserMedia({ audio: true }) .then((stream) => { // 创建媒体流源节点 const source = audioContext.createMediaStreamSource(stream); // 创建音频处理节点 const processor = audioContext.createScriptProcessor(1024, 1, 1); // 加载降噪模块 processor.onaudioprocess = (event) => { const inputBuffer = event.inputBuffer; const outputBuffer = event.outputBuffer; for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) { const inputData = inputBuffer.getChannelData(channel); const outputData = outputBuffer.getChannelData(channel); // 在这里应用降噪算法 // WebRTC没有内置的降噪算法,你可以使用第三方库或自定义算法来处理音频数据 for (let i = 0; i < inputBuffer.length; i++) { outputData[i] = inputData[i]; } } }; // 连接节点 source.connect(processor); processor.connect(audioContext.destination); }) .catch((error) => { console.error('获取用户媒体流失败:', error); }); ``` 这段代码创建了一个音频上下文,并获取用户的媒体流(麦克风输入)。然后,它创建了一个音频处理节点(ScriptProcessor),并在`onaudioprocess`事件处理程序中实现了降噪算法。 请注意,WebRTC本身并没有内置的降噪算法。在上述代码中,你需要替换注释部分的代码来应用实际的降噪算法。你可以使用第三方库(如RNNoise、Speex等)或自己实现降噪算法。 最后,将媒体流源节点、音频处理节点和目标节点(如扬声器)连接起来,以便实时处理音频数据并播放出来。 这只是一个简单的示例代码,实际的实现可能因需求和环境而有所不同。建议参考WebRTC和音频处理相关的文档、示例代码和社区资源,以获取更详细和具体的指导。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值