WebRTC 提供了一套标准 API,使 Web 应用可以直接提供实时音视频通信功能。大部分浏览器及操作系统都支持 WebRTC,直接可以在浏览器端发起实时音视频通话,本文以 WebRTC 初学者的视角去完成一个 1V1 网页版实时音视频通话。
完成音视频通话需要了解四个模块:音视频采集、STUN/TURN 服务器、信令服务器、端与端之间 P2P 连接。使用 WebRTC 的 API 完成音视频采集,配合信令服务器和 WebRTC 的RTCPeerConnection
方法能实现 1V1 通话,简易流程如下图:
接下来依次讲解它们的作用和核心 API。
WebRTC 使用getUserMedia
获取摄像头与话筒对应的媒体流对象MediaStream
,媒体流可以通过 WebRTC 进行传输,并在多个对等端之间共享。将流对象赋值给视频元素的 srcObject,实现本地播放音视频
属性 | 含义 |
---|---|
width | 视频的宽度 |
height | 视频的高度 |
aspectRatio | 比例 |
frameRate | 帧率 |
facingMode | 镜像模式 |
resizeMode | 大小模式 |
API:navigator.mediaDevices.getUserMedia
参数:constraints
返回:promise,方法调用成功得到MediaStream对象。
const localVideo = document.querySelector("video");
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
}
navigator.mediaDevices
.getUserMedia({
video: {
width: 640,
height: 480,
frameRate:15,
facingMode: 'enviroment', // 设置为后置摄像头
deviceId : deviceId ? {exact:deviceId} : undefined
},
audio: false
})
.then(gotLocalMediaStream)
.catch((error) => console.log("navigator.getUserMedia error: ", error));
知道怎么捕获本地音视频,接下来了解怎么与另一端建立连接传输音视频数据。
RTCPeerConnection
是 WebRTC 实现网络连接、媒体管理、数据管理的统一接口。建立 P2P 连接需要用到RTCPeerConnection
中的几个重要类:SDP
、ICE
、STUN/TURN
。
SDP 是各端的能力,包括音频编解码器类型、传输协议等。这些信息是建立连接是必须传递的,双方知道视频是否支持音频、编码方式是什么,都能通过 SDP 获得。
比如进行视频传输,我的编码是 H264 对方只能解 H265,就没法进行通信了。
SDP 描述分为两部分,分别是会话级别的描述(session level)和媒体级别的描述(media level),其具体的组成可参考 RFC4566[1],带星号 (*) 的是可选的。常见的内容如下:
Session description(会话级别描述)
v= (protocol version)
o= (originator and session identifier)
s= (session name)
c=* (connection information -- not required if included in all media) One or more Time descriptions ("t=" and "r=" lines; see below)
a=* (zero or more session attribute lines) Zero or more Media descriptions
Time description
t= (time the session is active)
Media description(媒体级别描述), if present
m= (media name and transport address)
c=* (connection information -- optional if included at session level)
a=* (zero or more media attribute lines)
SDP 解析时,每个 SDP Line 都是以 key=...
形式,解析出 key 是 a 后,可能有两种方式,可参考 RFC4566[2]:
a=<attribute>
a=<attribute>:<value>
有时候并非冒号 (:) 就一定是 <attribute>:<value>
,实际上 value 里面也会有冒号,比如:
a=fingerprint:sha-256 7C:93:85:40:01:07:91:BE
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=ssrc:2527104241 msid:gLzQPGuagv3xXolwPiiGAULOwOLNItvl8LyS
看一下具体例子:
alert(pc.remoteDescription.sdp);
v=0
o=alice 2890844526 2890844526 IN IP4 host.anywhere.com
s=
c=IN IP4 host.anywhere.com
t=0 0
//下面的媒体描述,在媒体描述部分包括音频和视频两路媒体
m=audio 49170 RTP/AVP 0
a=fmtp:111 minptime=10;useinbandfec=1 //对格式参数的描述
a=rtpmap:0 PCMU/8000 //对RTP数据的描述
...
//上面是音频媒体描述,下面是视频媒体描述
m=video 51372 RTP/AVP 31
a=rtpmap:31 H261/90000
...
m=video 53000 RTP/AVP 32
a=rtpmap:32 MPV/90000
WebRTC 点对点连接最方便的方法是双方 IP 直连,但是在实际的应用中,双方会隔着NAT
设备给获取地址造成了麻烦。
WebRTC 通过ICE
框架确定两端建立网络连接的最佳路径,为开发者者屏蔽了复杂的技术细节。
(NAT 及ICE
框架对于使用 WebRTC 的开发者是一个黑盒,为了优化阅读体验将这部分放在最后作为补充知识)
开发者需要了解
1 . 原理
两个节点交换 ICE 候选来协商他们自己具体如何连接,一旦两端同意了一个互相兼容的候选,该候选的 SDP 就被用来创建并打开一个连接,通过该连接媒体流就开始运转。
2 . 两个 API
onicecandidate
:本地代理创建 SDP Offer 并调用 setLocalDescription(offer) 后触发,在 eventHandler 中通过信令服务器将候选信息传递给远端。
addIceCandidate
:接收到信令服务器发送过来的候选信息后调用,为本机添加 ICE 代理。
API:pc.onicecandidate = eventHandler
pc.onicecandidate = function(event) {
if (event.candidate) {
// Send the candidate to the remote peer
} else {
// All ICE candidates have been sent
}
}
API:pc.addIceCandidate
pc.addIceCandidate(candidate).then(_=>{
// Do stuff when the candidate is successfully passed to the ICE agent
}).catch(e=>{
console.log("Error: Failure during addIceCandidate()");
});
WebRTC 的 SDP 和 ICE 信息需要依赖信令服务器
进行消息传输与交换、建立 P2P 连接,之后才能进行音视频通话、传输文本信息。如果没有信令服务器,WebRTC 无法进行通信。
通常使用socket.io
实时通信的能力来构建信令服务器。socket.io
跨平台、跨终端、跨语言,方便我们在各个端上去实现信令的各个端,去与我们的服务端进行连接。
这张图就表达了信令服务器在整个通话过程中它起到的作用。
用代码看一下如何建立 socket.io 信令服务器
var express = require("express");
var app = express();
var http = require("http");
const { Server } = require("socket.io");
const httpServer = http.createServer(app);
const io = new Server(httpServer);
io.on("connection", (socket) => {
console.log("a user connected");
socket.on("message", (room, data) => {
logger.debug("message, room: " + room + ", data, type:" + data.type);
socket.to(room).emit("message", room, data);
})
socket.on("join", (room) => {
socket.join(room);
})
});
A 和 B 建立网络连接的过程如图:
var pcConfig = {
iceServers: [
{
urls: "turn:stun.al.learningrtc.cn:3478",
credential: "mypasswd",
username: "garrylea",
},
{
urls:[
"stun:stun.example.com",
"stun:stun-1.example.com"
]
}
],
};
pc = new RTCPeerConnection(pcConfig);
createOffer
方法创建本地会话描述(SDP offer),SDP offer 包含有关已附加到 WebRTC 会话,浏览器支持的编解码器和选项的所有MediaStreamTrack
信息,以及ICE
代理,目的是通过信令信道发送给潜在远程端点,以请求连接或更新现有连接的配置。setLocalDescription
方法将提案设置为本地会话描述,并传递给 ICE 层。之后通过信令服务器将会话描述发送给 BAPI:pc.createOffer
参数:无
返回:SDP Offer
API:pc. setLocalDescription
参数:offer
返回:Promise<null>
function sendMessage(roomid, data) {
if (!socket) {
console.log("socket is null");
}
socket.emit("message", roomid, data);
}
const offer = await pc.createOffer()
await pc.setLocalDescription(offer).catch(handleOfferError);
message.log(`传输发起方本地SDP`);
sendMessage(roomid, offer);
RTCPeerConnection
,onicecandidate
事件会被触发。B 端接收到一个从远端页面通过信令发来的新的 ICE 候选地址信息,本机可以通过调用RTCPeerConnection.addIceCandidate()
来添加一个 ICE 代理。//A端
pc.onicecandidate = (event) => {
if (!event.candidate) return;
sendMessage(roomid, {
type: "candidate",
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate,
});
};
//B端
socket.onmessage = e => {
if (e.data.hasOwnProperty("type") && e.data.type === "candidate") {
var candidate = new RTCIceCandidate({
sdpMLineIndex: data.label,
candidate: data.candidate,
});
pc.addIceCandidate(candidate)
.then(() => {
console.log("Successed to add ice candidate");
})
.catch((err) => {
console.error(err);
});
}
}
addtrack
方法将音视频流流加入RTCPeerConnection
对象中传输给另一端,加入时另一端触发ontrack
事件。媒体流加入媒体轨道
API:stream.getTracks
参数:无
返回:媒体轨道对象数组
const pc = new RTCPeerConnection();
stream.getTracks().forEach((track) => {
pc.addTrack(track, stream);
});
const remoteVideo = document.querySelector("#remote-video");
pc.ontrack = (e) => {
if (e && e.streams) {
message.log("收到对方音频/视频流数据...");
remoteVideo.srcObject = e.streams[0];
}
};
setRemoteDescription
方法将提案传递到 ICE 层,调用 addTrack 方法加入 RTCPeerConnctionsetLocalDeacription
方法应答设置为本地会话并传递给 ICE 层。socket.onmessage = e => {
message.log("接收到发送方SDP");
await pc.setRemoteDescription(new RTCSessionDescription(e.data));
message.log("创建接收方(应答)SDP");
const answer = await pc.createAnswer();
message.log(`传输接收方(应答)SDP`);
sendMessage(roomid, answer);
await pc.setLocalDescription(answer);
}
pc.ontrack = (e) => {
if (e && e.streams) {
message.log("收到对方音频/视频流数据...");
remoteVideo.srcObject = e.streams[0];
}
};
RTCDataChannelton 通过 RTCPeerConnection API 可以建立点对点 P2P 互联,不需要中介服务器,延迟更低。
一端建立 datachannel, 另一端通过 ondatachannel 获取 datachannel 对象
API:pc.createDataChannel
参数:label 通道名
options? 通道参数
返回:RTCDataChannel
function receivemsg(e) {
var msg = e.data;
if (msg) {
message.log("-> " + msg + "\r\n");
} else {
console.error("received msg is null");
}
}
const dc = pc.createDataChannel("chat");
dc.onmessage = receivemsg;
dc.onopen = function () {
console.log("datachannel open");
};
dc.onclose = function () {
console.log("datachannel close");
};
pc.ondatachannel = e => {
if(!dc){
dc = e.channel;
dc.onmessage = receivemsg;
dc.onopen = dataChannelStateChange;
dc.opclose = dataChannelStateChange;
}
}; //当对接创建数据通道时会回调该方法。
上文提到ICE
集成了多种 NAT 穿越技术,比如 STUN、TURN,可以实现NAT
穿透,在主机之间发现 P2P 传输路径机制。接下来简单介绍下 NAT、STUN、TURN 是什么。
NAT
常部署在一个组织的网络出口位置。网络分为私网和公网两个部分,NAT 网关设置在私网到公网的路由出口位置,私网与公网间的双向数据必须都要经过 NAT 网关。组织内部的大量设备,通过 NAT 就可以共享一个公网 IP 地址,解决了 IPv4 地址不足的问题。
如下图所示,有两个组织,每个组织的 NAT 分配一个公网 IP,分别是 1.2.3.4 以及 1.2.3.5。每个组织私网设备通过 NAT 将内网地址转换为公网地址,然后加入互联网
NAT 对待 UDP 的实现方式有 4 种,分别为:完全圆锥型、地址受限锥型、端口受限锥型、对称型。
STUN
允许位于 NAT(或多重 NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的 NAT 之后以及 NAT 为某一个本地端口所绑定的公网端端口。
STUN
是 C/S 模式的协议,由客户端发送 STUN 请求、STUN 服务响应告知由NAT
分配给主机的 IP 地址和端口号,也是一种 Request/Response 的协议,默认端口号是 3478。
想让内网主机知道它的外网 IP,需要在公网上架设一台 STUN server,并向这台服务器发送 Request,服务器就会返回它的公网 IP 了。
下面是抓取的一对 STUN 绑定请求和响应。首先客户端向地址为 216.93.246.18 的 STUN 服务器发送 Binding Request(STUN 绑定请求)。
服务器回了 Binding Response,返回公网 IP:
TURN
是一种数据传输协议。允许通过 TCP 或 UDP 方式穿透 NAT 或防火墙。TURN 是一个 Client/Server 协议。TURN 的 NAT 穿透方法与 STUN 类似,都是通过取得应用层中的公网地址达到 NAT 穿透
ICE
两端并不知道所处的网络的位置和 NAT 类型,通过ICE
能够动态的发现最优的传输路径。ICE 端收集本地地址、通过STUN
服务收集 NAT 外网地址、通过TURN
收集中继地址,所以会有三种候选地址:
host 类型,即本机内网的 IP 和端口;
srflx 类型, 即本机 NAT 映射后的外网的 IP 和端口;
relay 类型,即中继服务器的 IP 和端口。
{
IP: xxx.xxx.xxx.xxx,
port: number,
type: host/srflx/relay,
priority: number,
protocol: UDP/TCP,
usernameFragment: string
...
}
下图中,Alice 与 Bob 通过 STUN 以及 TURN 服务器收集了三种类型的 candidate。
ICE 收集 candidate 后进行连通性检测,确定主机之间 P2P 最佳传输路径。
demo 项目:https://code.byted.org/yuanyuan.wallace/WebRTC/tree/master/demo
https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API
https://github.com/shushushv/webrtc-p2p
https://www.cnblogs.com/pannengzhi/p/5061674.html
[1]RFC4566: https://tools.ietf.org/html/rfc4566#page-24
[2]RFC4566: https://tools.ietf.org/html/rfc4566#page-24
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/ASxDxCR2YFO_4a3zLTKzrQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。