WebRTC:实时通信的未来

引言

在当今数字化的世界中,实时通信变得越来越重要。从视频会议到在线游戏,从远程医疗到物联网设备,人们希望能够立即与其他人或设备进行交流和互动。WebRTC(Web Real-Time Communication) 技术的出现填补了这一需求,为Web应用程序提供了强大的实时通信能力。本文将深入探讨 WebRTC 技术的原理、应用场景以及实现细节,带您了解这一引人注目的技术。

什么是WebRTC?

WebRTC 是一种开放标准的实时通信技术,允许浏览器之间进行点对点的音频、视频和数据传输,而无需任何插件或附加软件。它是由Google、Mozilla和Opera等公司发起的一个开放源代码项目,旨在为Web应用程序提供实时通信的能力。

WebRTC的核心特性

  1. 实时性
    WebRTC 提供了实时的音频和视频传输能力,使用户可以在几乎没有延迟的情况下进行实时通信。这种实时性对于视频会议、在线教育和远程医疗等应用非常重要。

  2. 安全性
    WebRTC 通过使用加密技术来保护通信内容的安全性。它使用 DTLS(Datagram Transport Layer Security)协议来保护数据传输的隐私,并使用 SRTP(Secure Real-time Transport Protocol)协议来加密音频和视频数据。

  3. 去中心化
    WebRTC 采用了点对点(P2P)通信模型,即浏览器之间直接进行通信,而不需要经过中间服务器。这种去中心化的通信方式可以提高通信的效率,并减少了对服务器资源的依赖。

  4. 跨平台
    WebRTC 可以在各种平台上运行,包括桌面浏览器、移动浏览器和原生应用程序。这使得开发人员可以轻松地创建跨平台的实时通信应用程序。

名词和方法解释

RTCPeerConnection

RTCPeerConnectionWebRTC API 中的一个关键接口,用于在两个对等端之间建立点对点连接。它提供了一种实现浏览器之间实时音视频通信的方式,允许在不同浏览器之间直接传输数据,而无需通过服务器。通过 RTCPeerConnection,用户可以在浏览器中创建一个实时通信的会话,并在其中发送和接收音频、视频或其他任何类型的数据。

作用

RTCPeerConnection 的主要作用包括:

  1. 建立和管理对等连接:RTCPeerConnection 提供了一种方法来建立和管理两个浏览器之间的点对点连接,使它们可以直接通信,无需通过中间服务器。

  2. 处理媒体流:它允许用户将本地音频、视频或数据流发送到远程对等端,并从远程对等端接收相应的媒体流。

  3. 网络协商和协议处理:RTCPeerConnection 处理与 ICESDP 等相关的网络协商和协议处理,以确保对等连接的正确建立和维护。

  4. 实现信号传输和媒体传输:它还负责在对等连接之间传输信令消息和媒体数据,以确保通信的顺利进行。

参数

RTCPeerConnection 构造函数包含的1个参数,一个 RTCConfiguration 对象,用于指定配置参数。这个对象包含以下属性:

  • iceServers:一个 RTCIceServer 对象数组,用于指定 ICE 服务器信息。
    • urlsICE 服务器的 URLURL 数组。
    • username (可选):ICE 服务器的用户名。
    • credential (可选):ICE 服务器的密码。
  • iceTransportPolicy (可选):一个枚举值,用于指定 ICE 传输策略,默认为 "all",目前支持的值有 "all" | "relay"
  • bundlePolicy (可选):一个枚举值,用于指定 SDP 处理策略,默认为 "balanced",目前支持的值有 "balanced" | "max-bundle" | "max-compat"
  • rtcpMuxPolicy (可选):一个枚举值,用于指定 RTCP 复用策略,默认为 "require",目前支持的值有 "require"
  • peerIdentity (可选):PEER 身份验证,默认为 null。
  • certificates (可选):一个 RTCCertificate 对象数组,用于指定本地证书数组。
  • iceCandidatePoolSize (可选):一个数值,指定 ICE 候选地址池的大小。

属性和方法

  1. restartIce()

restartIce() 方法用于在 WebRTC 对等连接中重新启动 ICE 连接过程。ICE(Interactive Connectivity Establishment)是一种用于建立对等连接的网络协议,它可以帮助确定网络上的最佳路径,以确保数据能够在两个 RTCPeerConnection 之间进行有效传输。

重新启动 ICE 连接过程通常在网络连接发生变化或连接质量变差时使用,以尝试建立更稳定的连接。这可能包括重新检测网络接口、重新收集 ICE 候选项、更新 ICE Agent 状态等操作。

restartIce() 方法不接受任何参数,调用该方法将触发 ICE 连接重新启动过程。在调用此方法之后,ICE Agent 将重新开始收集候选项并尝试建立新的 ICE 连接。

下面是一个示例代码:

1
2
// 重新启动 ICE 连接过程
peerConnection.restartIce();
  1. setLocalDescription
    setLocalDescription() 方法用于将本地描述(即 SDP,Session Description Protocol)设置为 RTCPeerConnection 对象的本地描述。本地描述包含了当前对等连接的配置信息,例如媒体类型、编解码器、传输协议等。

通常情况下,当我们创建一个新的对等连接时,会先通过调用 createOffer()createAnswer() 方法生成一个 SDP 描述,并将其设置为本地描述。然后,我们将本地描述发送给远程对等方,远程对等方通过调用 setRemoteDescription() 方法将其应用到对等连接中。这样,两个对等方就可以根据各自的本地描述进行协商,建立一条可靠的连接。

以下是一个简单的示例,展示了如何使用 setLocalDescription() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建 RTCPeerConnection
const peerConnection = new RTCPeerConnection();

// 创建一个 SDP offer
peerConnection.createOffer()
.then((offer) => {
// 设置本地描述为 SDP offer
return peerConnection.setLocalDescription(offer);
})
.then(() => {
console.log("Local description set successfully.");
})
.catch((error) => {
console.error("Error setting local description:", error);
});

在上面的示例中,我们首先创建了一个 SDP offer,然后调用 setLocalDescription() 方法将其设置为本地描述。成功设置本地描述后,我们会在控制台输出成功的消息。如果设置本地描述失败,则会捕获到错误并输出错误消息。

  1. setRemoteDescription
    setRemoteDescription() 方法用于将远程对等连接的描述信息设置到本地对等连接中。这个描述信息通常是由远程对等方通过 SDP(Session Description Protocol)提供的,包含了远程对等方的媒体信息和网络连接信息。

在 WebRTC 中,当本地对等连接收到远程对等方发送的 SDP offer 或 answer 时,需要通过 setRemoteDescription() 方法将其设置到本地对等连接中,以便进行后续的连接协商和媒体交换。

以下是一个简单的示例,展示了如何使用 setRemoteDescription() 方法将远程对等连接的描述信息设置到本地对等连接中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 假设 remoteDescription 是远程对等连接的描述信息(SDP offer 或 answer)
const remoteDescription = {
type: 'offer', // 描述类型为 SDP offer
sdp: '...' // SDP offer 的内容
};

// 设置远程对等连接的描述信息到本地对等连接中
peerConnection.setRemoteDescription(remoteDescription)
.then(() => {
console.log('Remote description set successfully.');
// 在这里可以继续进行后续的连接协商或媒体交换操作
})
.catch((error) => {
console.error('Error setting remote description:', error);
});

在上面的示例中,我们假设 remoteDescription 是远程对等连接的描述信息,包含了描述的类型(这里假设为 SDP offer)和 SDP offer 的内容。然后,我们通过 setRemoteDescription() 方法将这个描述信息设置到本地对等连接中。如果设置成功,则会打印日志表示远程描述已成功设置,否则会捕获到错误并输出错误消息。

  1. addTransceiver

addTransceiver() 方法用于向对等连接中添加新的媒体传输通道(RTCRtpTransceiver)。它的参数包括:

  • trackOrKind:要添加到传输通道的媒体轨道(MediaStreamTrack)或媒体类型(string)。如果是 MediaStreamTrack 类型,则表示要添加的具体媒体轨道。如果是 string 类型,则表示要添加的媒体类型,如 "audio""video"
  • init(可选):一个可选的 RTCRtpTransceiverInit 对象,用于配置传输通道的初始化选项。该对象包括以下属性:
    • direction:传输通道的传输方向,可以是 "sendrecv""sendonly""recvonly""inactive"
    • streams:一个包含了要与传输通道关联的 MediaStream 对象的数组。
    • sendEncodings:一个包含了与传输通道相关联的编码设置的数组,用于配置传输的编码参数。

该方法返回一个 RTCRtpTransceiver 对象,表示新添加的传输通道。这个传输通道可以用于控制和管理新添加的媒体流的传输。通过使用 addTransceiver() 方法,您可以动态地向对等连接中添加新的媒体轨道,从而实现更灵活和动态的媒体流管理。

事件

  1. onnegotiationneeded

onnegotiationneeded 事件是 WebRTC 中的一个事件,它在需要重新协商(negotiation)时触发。当需要创建或重新创建对等连接时(例如添加或删除数据流或轨道,或者更改连接配置),就会触发此事件。

在此事件触发时,通常会调用 createOffer()createAnswer() 方法来生成一个新的 SDP(会话描述协议)以进行协商。然后,这个新的 SDP 将被传递给远程 RTCPeerConnection,以更新对等连接的配置。

以下是一个示例代码:

1
2
3
4
5
6
7
8
9
10
peerConnection.onnegotiationneeded = async () => {
try {
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
// 将 SDP 发送给远程 `RTCPeerConnection`
sendOfferToRemotePeer(offer);
} catch (error) {
console.error('Error creating offer:', error);
}
};

在实际应用中,您可以根据需要在 onnegotiationneeded 事件中执行其他操作,例如创建或更新对等连接的配置,以确保连接的稳定性和可靠性。

  1. onicecandidate
    onicecandidate 事件在 ICE(Interactive Connectivity Establishment)协商过程中生成 ICE 候选时触发。ICE 候选用于发现网络路径和连接两个 RTCPeerConnection 之间的传输地址。当本地 RTCPeerConnection 发现一个新的 ICE 候选时,会触发 onicecandidate 事件,并将候选信息传递给应用程序。

通常,在收到 ICE 候选后,应用程序会将候选信息发送给远程 RTCPeerConnection,以便远程 RTCPeerConnection 能够知道如何直接与本地 RTCPeerConnection 进行通信。

以下是一个简单的示例,展示了如何使用 onicecandidate 事件处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 创建 RTCPeerConnection
const peerConnection = new RTCPeerConnection();

// 设置 onicecandidate 事件处理程序
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// 将 ICE 候选发送给远程 `RTCPeerConnection`
sendIceCandidateToRemotePeer(event.candidate);
} else {
// 所有 ICE 候选已发送
console.log("All ICE candidates have been sent.");
}
};

// 开始 ICE 协商过程
peerConnection.createOffer()
.then((offer) => {
// 设置本地描述
return peerConnection.setLocalDescription(offer);
})
.catch((error) => {
console.error("Error creating offer:", error);
});

在上面的示例中,onicecandidate 事件处理程序将在每次发现新的 ICE 候选时被触发。在处理程序中,我们检查 event.candidate 是否存在,如果存在则将候选信息发送给远程 RTCPeerConnection。当 event.candidatenull 时,表示所有的 ICE 候选已经发现并发送完毕。

RTCIceCandidate

RTCIceCandidateWebRTC 中的一个接口,表示 ICE (Interactive Connectivity Establishment) 候选者,用于描述网络中的地址信息,以帮助建立对等连接。以下是关于 RTCIceCandidate 接口的详细解释:

RTCIceCandidate 表示一个 ICE 候选者,包含了描述网络地址的相关信息。

RTCIceCandidateWebRTC 中用于建立对等连接的关键组成部分,用于确定连接双方的网络地址和传输协议。

参数

RTCIceCandidate 接口具体的参数如下所示:

  • candidate:描述 ICE 候选者的字符串,通常是以 SDP (Session Description Protocol) 格式表示的。该字符串包含了 ICE 候选者的网络地址、传输协议、优先级等信息。
  • sdpMid:候选者所对应的媒体流的标识符,用于区分不同的媒体流。
  • sdpMLineIndex:候选者所在的媒体流的索引,表示候选者属于媒体流描述中的哪一行。

案例

下面是一个简单的例子,展示了如何创建一个 RTCIceCandidate 对象,并将其添加到 RTCPeerConnection 中。ICE 候选者通常是通过信令服务器交换的,用于建立对等连接并进行网络地址交换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 从 Websocket 中收到的远端 RTCPeerConnection 的 ICE 候选者的的信息
const remoteIceCandidateInfo = {
candidate: 'candidate:1838085505 1 udp 2122260223 192.168.1.101 52041 typ host generation 0',
sdpMid: 'audio',
sdpMLineIndex: 0
}

// 创建一个新的 ICE 候选者对象
const iceCandidate = new RTCIceCandidate(remoteIceCandidateInfo);

// 将 ICE 候选者添加到本地的 PeerConnection
peerConnection.addIceCandidate(iceCandidate)
.then(() => {
console.log('ICE 候选者添加成功');
})
.catch(error => {
console.error('添加 ICE 候选者时出错:', error);
});

RTCSessionDescription

RTCSessionDescription 是 WebRTC API 中的一个重要接口,用于表示会话描述协议 (SDP) 中的一部分。SDP 是一种用于描述媒体会话的文本协议,包含了媒体流的相关信息,如编解码器、媒体类型、媒体格式、网络传输协议等。

在 WebRTC 中,RTCSessionDescription 主要用于描述通信会话中的两个关键部分:

  1. 描述远程媒体的信息:当一个端点(通常是浏览器)接收到来自远程端点(如另一个浏览器或媒体服务器)的 SDP 描述时,它会使用 RTCSessionDescription 对象来表示该描述。这个描述包含了远程端点发送的媒体流的相关信息,如编解码器、媒体类型、网络地址等。

  2. 描述本地媒体的信息:同样地,当一个端点想要向远程端点发送媒体流时,它会创建一个 RTCSessionDescription 对象,其中包含了本地媒体流的描述信息。这个描述会随后通过信令传输给远程端点,以便它能够了解到本地端点要发送的媒体流的相关信息。

属性

RTCSessionDescription 是 WebRTC 中用于描述会话信息的对象。它有两个主要属性:

  • type:描述会话的类型,通常是 "offer""answer""pranswer" 中的一个。

    • "offer":表示发起一个会话描述,通常由发送方生成并发送给接收方。
    • "answer":表示响应一个会话描述,接收方收到 "offer" 后生成的响应。
    • "pranswer":表示暂时的会话描述,用于表示正在协商会话的过程中,尚未达成最终协议的描述。
  • sdp:会话描述协议 (SDP) 的文本内容,包含了媒体流的相关信息,如编解码器、媒体类型、传输协议等。

案例

举个例子,一个典型的 RTCSessionDescription 对象可以看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 远端收到的Offer描述
const sessionDescription = {
type: 'offer',
sdp: 'v=0\no=- 123456789 0 IN IP4 127.0.0.1\ns=...\nt=0 0\n...'
};

// `setRemoteDescription()` 是 WebRTC 中 `RTCPeerConnection` 接口提供的方法之一,用于设置远端描述。它的作用是将远端的 Session Description(会话描述)应用到本地的 `RTCPeerConnection` 对象上,以建立或更新连接所需的各种参数和配置。
// 是一个异步操作,因为它可能需要与远端进行交互,解析和验证会话描述信息,并根据描述信息更新本地连接的状态。
peerConnection.setRemoteDescription(sessionDescription)
.then(() => {
// 处理成功设置远端描述后的逻辑
})
.catch(error => {
// 处理设置远端描述失败的逻辑
});

RTCCertificate

RTCCertificate 是用于配置和管理 WebRTC 连接所使用的安全证书的接口。它可以用来指定本地端点的安全凭证,以确保通信的安全性和可靠性。

RTCCertificate 接口本身并没有提供直接创建证书的方法。相反,WebRTC API 中的 RTCPeerConnection.generateCertificate() 方法用于生成新的证书。这个方法返回一个 Promise,在解析时会返回一个新的 RTCCertificate 实例,或者在生成证书时出现错误时返回一个错误对象。

属性和方法

RTCCertificate 接口没有提供构造函数来创建新的证书,而是通过 createOffer()createAnswer() 等方法间接使用。它的主要属性和方法包括:

  • expires:表示证书的到期时间,以 UNIX 时间戳表示。
  • getFingerprints():返回一个包含证书指纹信息的数组。

案例

以下是一个使用 RTCPeerConnection.generateCertificate() 方法生成证书的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// RSASSA-PKCS1-v1_5 算法
let stdRSACertificate = {
// name: 必需,指定生成证书所使用的算法名称。目前支持的值有 "RSASSA-PKCS1-v1_5"、"RSA-PSS" 和 "ECDSA"。
name: "RSASSA-PKCS1-v1_5",
// modulusLength: 对于 RSA 算法,指定生成的 RSA 密钥的长度(以位为单位)。可选值为 1024、2048、4096 等。默认为 2048。
modulusLength: 2048,
// publicExponent: 对于 RSA 算法,指定生成的 RSA 密钥的公共指数。可选值为 3、65537 等。默认为 65537。
publicExponent: new Uint8Array([1, 0, 1]),
// hash: 对于 RSA 算法,指定用于签名的散列算法。可选值为 "SHA-1"、"SHA-256"、"SHA-384" 等。默认为 "SHA-256"。
hash: "SHA-256",
};

// RSA-PSS 算法
let stdRSAPSSCertificate = {
// name: 必需,指定生成证书所使用的算法名称。目前支持的值有 "RSASSA-PKCS1-v1_5"、"RSA-PSS" 和 "ECDSA"。
name: 'RSA-PSS',
// modulusLength: RSA 公钥的模数长度,即 N 的位数。一般来说,这个值越大,密钥的安全性就越高。典型的值为 2048 或 4096。
modulusLength: 2048,
// publicExponent: RSA 公钥的指数部分,通常选择一个较小的质数,如 65537 (0x10001)。
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 对应于十进制的65537
// hash: 用于生成签名的哈希算法,如 SHA-256、SHA-384、SHA-512 等。
hash: 'SHA-256',
// saltLength: 签名中所使用的盐的长度,通常设置为哈希算法的输出长度。
saltLength: 32 // 假设使用32字节的盐
};

// ECDSA 算法
let stdECDSACertificate = {
// name: 必需,指定生成证书所使用的算法名称。目前支持的值有 "RSASSA-PKCS1-v1_5"、"RSA-PSS" 和 "ECDSA"。
name: "ECDSA",
// namedCurve 是用于指定生成椭圆曲线数字签名算法 (ECDSA) 证书时所使用的椭圆曲线的参数。目前支持的值有 "P-256"、"P-384" 和 "P-521"。
namedCurve: "P-256",
};

// 使用 RTCPeerConnection.generateCertificate() 方法生成新的证书
const certificatePromise = RTCPeerConnection.generateCertificate(stdECDSACertificate);

// 处理生成证书的结果
certificatePromise.then((certificate) => {
// 生成证书成功,可以使用 certificate 对象
console.log('New certificate generated:', certificate);

// 在这里可以使用生成的证书进行后续操作,例如创建 RTCPeerConnection 实例
}).catch((error) => {
// 生成证书失败,输出错误信息
console.error('Error generating certificate:', error);
});

RTCRtpTransceiver

RTCRtpTransceiver 接口表示一个媒体传输通道,用于在 WebRTC 对等连接(RTCPeerConnection)中传输媒体流。每个传输通道可以处理一个或多个媒体流的发送和接收。

RTCRtpTransceiver 接口通常由 RTCPeerConnection 对象的 addTransceiver() 方法创建,并在对等连接的创建和管理过程中使用。它提供了灵活的控制选项,使您能够更好地管理对等连接中的媒体流传输。

属性

RTCRtpTransceiver 表示 WebRTC 对等连接(RTCPeerConnection)中的一个媒体传输通道,该对象包含的属性和方法如下所示:

  • mid:传输通道的标识符(MediaStream ID),在 SDP 中用于唯一标识传输通道。
  • receiver:与传输通道相关联的接收器对象(RTCReceiver),用于接收远程媒体流。
  • sender:与传输通道相关联的发送器对象(RTCSender),用于发送本地媒体流。
  • direction:传输通道的传输方向,可以是 "sendrecv""sendonly""recvonly""inactive"
  • currentDirection:当前传输通道的传输方向。
  • stopped:指示传输通道是否已停止的布尔值。

方法

  • setCodecPreferences(codecs):设置传输通道的编解码器偏好,codecs 是通过 RTCRtpSender.getCapabilitiesRTCRtpReceiver.getCapabilities 静态方法获得的编解码器。
  • stop():停止传输通道,不再发送或接收媒体流。

RTCRtpSenderRTCRtpReceiver 是 WebRTC API 中用于发送和接收媒体流的对象。

RTCRtpSender:

RTCRtpSender 接口表示 RTCPeerConnection 中的媒体发送器。它负责发送媒体流到远程对等方。

属性

  • track:成员属性,代表当前与 RTCRtpSender 相关联的 MediaStreamTrack 对象。可以是音频轨道或视频轨道。

方法

  • getParameters():成员方法,返回一个 RTCRtpSendParameters 对象,该对象包含了当前发送器的参数信息,如编码器设置等。

  • setParameters(parameters):成员方法,用于设置发送器的参数,例如修改编码器设置等。参数 parameters 是一个包含新参数的 RTC RtpSendParameters 对象。

  • replaceTrack(newTrack):成员方法,用于替换当前发送器的媒体轨道。可以用于在运行时更改发送的媒体类型(例如切换摄像头)。

  • getCapabilities(kind):静态方法,用于获取特定类型(音频或视频)的编解码器能力信息。参数 kind 可以是 “audio” 或 “video”。

示例

以下是关于如何使用 getCapabilities 方法来获取编解码器能力信息的示例代码:

1
2
3
4
5
6
7
// 获取视频编解码器能力信息
const videoCapabilities = RTCRtpSender.getCapabilities('video');
console.log("Video Codecs:", videoCapabilities.codecs);

// 获取音频编解码器能力信息
const audioCapabilities = RTCRtpSender.getCapabilities('audio');
console.log("Audio Codecs:", audioCapabilities.codecs);

这段代码中,我们首先使用 RTCRtpSender.getCapabilities 方法来获取视频和音频编解码器的能力信息。然后,我们打印出返回的 codecs 属性,其中包含了关于支持的编解码器的详细信息,例如编解码器的类型、编码器和解码器参数等。

RTCRtpReceiver:

RTCRtpReceiver 接口表示 RTCPeerConnection 中的媒体接收器。它负责接收远程对等方发送的媒体流。

属性

  • track:属性,代表当前与 RTCRtpReceiver 相关联的 MediaStreamTrack 对象。可以是音频轨道或视频轨道。

方法

  • getParameters():成员方法,返回一个 RTC RtpReceiveParameters 对象,该对象包含了当前接收器的参数信息,如解码器设置等。

  • getContributingSources():成员方法,返回一个包含贡献源信息的数组,表示当前接收器接收到的媒体流的贡献者。

  • getStats():成员方法,返回一个 Promise,该 Promise 在解析后会提供有关接收器的统计信息。

  • getCapabilities(kind):静态方法,用于获取特定类型(音频或视频)的编解码器能力信息。参数 kind 可以是 “audio” 或 “video”。

示例

以下是一个使用 RTCRtpReceiver.getCapabilities 方法来获取音频和视频编解码器能力信息的示例代码:

1
2
3
4
5
6
7
// 获取视频编解码器能力信息
const videoCapabilities = RTCRtpReceiver.getCapabilities('video');
console.log("Video Codecs:", videoCapabilities.codecs);

// 获取音频编解码器能力信息
const audioCapabilities = RTCRtpReceiver.getCapabilities('audio');
console.log("Audio Codecs:", audioCapabilities.codecs);

这段代码中,我们使用 RTCRtpReceiver.getCapabilities 方法来获取音频和视频编解码器的能力信息。在真实使用当中,我们可以设置传输的音视频的编解码器
然后,我们打印出返回的 codecs 属性,其中包含了关于支持的编解码器的详细信息,例如编解码器的类型、编码器和解码器参数等。

其他名词术语解释

当涉及到 WebRTC 和实时通信时,以下术语是很常见的:

  • SDP(Session Description Protocol):会话描述协议,用于描述多媒体会话的参数,例如编解码器信息、媒体流传输方式等。在 WebRTC 中,SDP 用于建立和管理对等连接。

  • RTCP(Real-Time Control Protocol):实时控制协议,是用于在 RTP(实时传输协议)会话中提供控制和监控功能的协议。RTCP 主要用于传输统计信息和控制信息。

  • RTP(Real-Time Transport Protocol):实时传输协议,是一种用于在 IP 网络上传输多媒体数据的协议。RTP 通常与 RTCP 一起使用,用于在网络上传输音频和视频流。

  • ICE(Interactive Connectivity Establishment):交互式连接建立,是一种用于在两个设备之间建立网络连接的技术。ICE 使用一系列技术(包括 STUN、TURN 和 ICE)来克服网络地址转换(NAT)和防火墙等网络障碍。

这些术语在 WebRTC 中起着至关重要的作用,它们共同构建了实时通信系统的基础。

WebRTC的工作原理

WebRTC(Web Real-Time Communication) 是一项用于实现浏览器之间实时通信的开放标准技术。它允许在不需要额外插件的情况下,通过浏览器直接进行音频、视频和数据传输。WebRTC 技术的出现,极大地促进了在线会议、远程教育、视频直播等领域的发展。下面将深入探讨 WebRTC 的核心技术,包括媒体获取媒体传输信令交换以及NAT穿越等方面,并结合详细的例子进行说明。

摄像头媒体获取(Media Acquisition)

navigator.mediaDevices.getUserMedia() 是 WebRTC 提供的方法之一,用于从用户的摄像头和麦克风中获取媒体流。它允许 Web 应用程序访问用户的音视频设备,并将其转换为可用于实时通信、音视频录制等目的的媒体流。

示例代码

以下是关于 navigator.mediaDevices.getUserMedia() 方法的例子:

1
2
3
4
5
6
7
8
9
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((stream) => {
// 获取到用户的音视频流
const videoElement = document.getElementById('localVideo');
videoElement.srcObject = stream;
})
.catch((error) => {
console.error('获取用户媒体设备失败:', error);
});

参数说明

  • constraints 是一个可选参数,用于指定获取媒体流的约束条件。
  • 可以包含两个属性:
    • video:指定是否捕获视频。可以是布尔值或包含视频约束的对象。
    • audio:指定是否捕获音频。可以是布尔值或包含音频约束的对象。

返回值

  • getUserMedia() 方法返回一个 Promise 对象。
  • 如果用户授权并成功获取媒体流,则 Promise 对象的 then 回调函数会被调用,并传递一个 MediaStream 对象作为参数,其中包含捕获的视频和/或音频轨道。
  • 如果用户拒绝权限或获取媒体流失败,则 Promise 对象的 catch 回调函数会被调用,并传递一个 Error 对象作为参数,其中包含错误的详细信息。

用途

获取到媒体流后,您可以将其用于各种用途,例如:

  • <video> 元素中播放捕获的视频流。
  • 将视频流传输到远程对等端,实现实时视频通话或视频会议。
  • 对音频流进行处理、录制或转发等操作。

权限和安全性

  • 浏览器通常会在第一次调用 getUserMedia() 方法时向用户请求音视频设备的权限。
  • 用户需要允许或拒绝权限请求,以控制是否允许网站访问其摄像头和麦克风。
  • 为了保护用户隐私和安全,浏览器会限制某些情况下的媒体设备访问,例如当网页位于不安全的上下文中时。

总的来说,navigator.mediaDevices.getUserMedia() 方法为开发者提供了一种方便的方式来获取用户的音视频流,从而实现各种实时音视频通信、录制和处理等功能。

说明

上面的代码演示了如何使用 getUserMedia API 获取用户的音视频流,并将其绑定到一个 <video> 元素上进行实时预览。通过这种方式,我们可以轻松地在浏览器中获取用户的音视频数据,为后续的通信做准备。

桌面媒体获取(Media Acquisition)

navigator.mediaDevices.getDisplayMedia() 是 WebRTC 提供的方法之一,用于获取屏幕共享流。它允许用户捕获屏幕上的内容并将其转换为媒体流,以便在 Web 应用程序中进行处理和展示。

示例代码

让我们详细解释一下这个方法的用法和相关概念:

1
2
3
4
5
6
7
navigator.mediaDevices.getDisplayMedia(constraints)
.then(stream => {
// 处理媒体流
})
.catch(error => {
// 处理错误
});

参数说明

  • constraints 是一个可选参数,用于指定获取屏幕共享流的约束条件。
  • 可以包含两个属性:
    • video:指定是否捕获视频。默认值为 true
    • audio:指定是否捕获音频。默认值为 false

返回值

  • getDisplayMedia() 方法返回一个 Promise 对象。
  • 如果用户授权并成功获取屏幕共享流,则 Promise 对象的 then 回调函数会被调用,并传递一个 MediaStream 对象作为参数,其中包含屏幕共享的视频和/或音频轨道。
  • 如果用户拒绝权限或获取屏幕共享流失败,则 Promise 对象的 catch 回调函数会被调用,并传递一个 Error 对象作为参数,其中包含错误的详细信息。

用途

获取到媒体流后,您可以将其用于各种用途,例如:

  • <video> 元素中播放屏幕共享内容。
  • 将屏幕共享流传输到远程对等端,实现屏幕共享功能。
  • 对媒体流进行处理、录制或转发等操作。

权限和安全性

  • 浏览器通常会在第一次调用 getDisplayMedia() 方法时向用户请求屏幕共享权限。
  • 用户需要允许或拒绝权限请求,以控制是否允许网站捕获屏幕内容。
  • 为了保护用户隐私和安全,浏览器会限制某些情况下的屏幕共享,例如当网页位于不安全的上下文中时。

说明

navigator.mediaDevices.getDisplayMedia() 方法为开发者提供了一种方便的方式来获取屏幕共享流,从而实现屏幕录制、远程教学、视频会议等各种实时协作和分享功能。

媒体传输(Media Transmission)

WebRTC 的媒体传输主要涉及使用 RTCPeerConnection 建立点对点的连接,并将音视频数据传输给其他 RTCPeerConnection。这个过程涉及到 ICE 候选、会话描述等技术,以确保数据能够安全、高效地传输。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 创建一个RTCPeerConnection对象
const peerConnection = new RTCPeerConnection();

// 添加ICE候选
peerConnection.onicecandidate = function(event) {
let candidateObject = {};
const candidate = event.candidate;
if (!candidate) {
log.debug('message: Gathered all candidates and sending END candidate');
candidateObject = {
sdpMLineIndex: -1,
sdpMid: 'end',
candidate: 'end',
};
} else {
candidateObject = {
sdpMLineIndex: candidate.sdpMLineIndex,
sdpMid: candidate.sdpMid,
candidate: candidate.candidate,
};
if (!candidateObject.candidate.match(/a=/)) {
candidateObject.candidate = `a=${candidateObject.candidate}`;
}
}
// 发送ICE候选给对方
socket.sendSDP(
'connectionMessage',
{
roomId: 'xxx',
msg: {
type: 'candidate',
candidate: candidateObject
},
browser: 'xxx'
}
);
};

// 添加本地流
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((stream) => {
stream.getTracks().forEach((track) => {
peerConnection.addTrack(track, stream);
});
});

// 创建Offer
peerConnection.createOffer()
.then((offer) => {
return peerConnection.setLocalDescription(offer);
})
.then(() => {
// 将Offer发送给对方
socket.sendSDP(
'connectionMessage',
{
roomId: 'xxx',
msg: {
type: peerConnection.localDescription.type,
sdp: peerConnection.localDescription.sdp,
},
browser: 'xxx'
}
);
})
.catch((error) => {
console.error('创建Offer失败:', error);
});

说明

上面的代码演示了如何使用 RTCPeerConnection 建立点对点连接,并将本地的音视频流添加到连接中。然后,通过创建一个Offer,并将其发送给对方,以发起一个通信会话。在这个过程中,我们还需要处理 ICE 候选,以便在连接过程中进行 NAT 穿越。

信令交换(Signaling)

WebRTC 的信令交换主要涉及将会话描述、ICE 候选等元数据信息交换给其他 RTCPeerConnection,以建立和管理通信会话。这个过程通常需要借助于信令服务器来协调客户端之间的消息传递。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 建立WebSocket连接
const ws = new WebSocket('ws://localhost:8080');

// 监听WebSocket消息
ws.onmessage = function(event) {
const message = JSON.parse(event.data).msg;
// 当收到远端的Offer时,我们需要设置远端描述,并生成一个Answer,添加到本地描述里面,然后在通过WebSocket发生给远端,让对方设置的远端描述里面。
if (message.type === 'offer') {
// 收到对方的Offer
peerConnection.setRemoteDescription(new RTCSessionDescription(message))
.then(() => {
// 创建并发送Answer
return peerConnection.createAnswer();
})
.then((answer) => {
peerConnection.setLocalDescription(answer);
return answer
})
.then(() => {
// 将Answer发送给对方
socket.sendSDP(
'connectionMessage',
{ roomId: 'xxx', msg: answer, browser: 'xxx' }
);
})
.catch((error) => {
console.error('处理Offer失败:', error);
});
} else if (message.type === 'answer') {
// 当收到远端的Answer时,设置到远端描述,建立通信连接成功!
// 收到对方的Answer
peerConnection.setRemoteDescription(new RTCSessionDescription(message))
.then(() => {
console.log('成功建立通信连接!');
})
.catch((error) => {
console.error('处理Answer失败:', error);
});
} else if (message.type === 'candidate') {
// 收到远端的ICE候选描述信息时,需要创建RTCIceCandidate对象,然后调用addIceCandidate方法添加ICE候选
const candidateParams = typeof (message.candidate) === 'object' ? message.candidate : JSON.parse(message.candidate);

candidateParams.candidate = candidateParams.candidate.replace(/a=/g, '');
candidateParams.sdpMLineIndex = parseInt(candidateParams.sdpMLineIndex, 10);
const candidate = new RTCIceCandidate(candidateParams);
// 收到ICE候选
peerConnection.addIceCandidate(candidate)
.then(() => {
console.log('成功添加ICE候选!');
})
.catch((error) => {
console.error('添加ICE候选失败:', error);
});
}
};

说明

上面的代码演示了如何使用 WebSocket 进行信令交换。客户端通过 WebSocket 连接到信令服务器,并监听来自服务器的消息。当收到对方发送的 OfferAnswerICE 候选时,客户端需要相应地处理并执行相应的操作,以建立和管理通信会话。

NAT穿越(NAT Traversal)

NAT(Network Address Translation) 穿越是指在 NAT 环境下,通过一系列的技术手段实现两个位于私有网络中的计算机或设备之间的直接通信。WebRTC 通过使用 STUNTURN 服务器来实现 NAT 穿越,以确保在各种网络环境下都能够正常进行实时通信。

示例代码

1
2
3
4
5
6
7
8
9
10
// 配置ICE服务器
const configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:your-turn-server.com', username: 'username', credential: 'password' }
]
};

// 创建RTCPeerConnection对象
const peerConnection = new RTCPeerConnection(configuration);

添加传输通道

当通道和管理通信会话建立成功后,我们需要将我们本地的音视频流传输到远端的 RTCPeerConnection 中,所以我们需要调用 RTCPeerConnection.addTransceiver 方法。

addTransceiver() 方法用于向对等连接(RTCPeerConnection 对象)添加传输通道(transceiver),以便在单个轨道上发送和接收媒体流。通过这种方式,您可以更灵活地控制对等连接的行为,并更精细地管理媒体流的传输。

该方法接受一个或多个参数,具体取决于您要添加的传输通道的类型和配置。通常情况下,addTransceiver() 方法用于以下两种情况:

  1. 添加本地传输通道(用于发送):您可以通过将本地媒体轨道(例如音频或视频轨道)添加到对等连接中来创建一个新的传输通道,以便将该媒体流发送给远端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const transceivers = [];
localStream.getTracks().forEach(async (track) => {
const options = {
streams: [localStream],
};
if (track.kind === 'video') {
options = {
// 生成视频流编码的参数
sendEncodings: generateEncoderParameters(),
};
}
// 需要保存传输通道,在不需要传输的时候,方便停止传输
const transceiver = peerConnection.addTransceiver(track, options);
transceivers.push(transceiver);
});

当需要移除的时候

1
2
3
4
5
6
7
8
9
transceivers.forEach((transceiver) => {
console.debug('Stopping transceiver', transceiver);
// Don't remove the tagged m section, which is the first one (mid=0).
if (transceiver.mid === '0') {
peerConnection.removeTrack(transceiver.sender);
} else {
transceiver.stop();
}
});
  1. 添加远程传输通道(用于接收):当您接收到远端对等连接发送的媒体流时,WebRTC 会自动为每个远程媒体轨道创建传输通道,并将其添加到对等连接中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
peerConnection.ontrack = (event) => {
const remoteTrack = event.track;
if (trackEvt.streams.length !== 1) {
console.warning('More that one mediaStream associated with track!');
}

// 远端的流
const stream = trackEvt.streams[0];
// 监听流移除轨道事件
stream.onremovetrack = () => {
// 不存在音频和视频
if (stream.getTracks().length === 0) {
this.emit(ConnectionEvent({ type: 'remove-stream', stream }));
}
};
this.emit(ConnectionEvent({ type: 'add-stream', stream }));
// 处理远程视频轨道
};

说明

上面的代码示例中,我们通过配置 ICE 服务器来实现 NAT 穿越。其中,STUN 服务器用于获取公网 IP 地址和端口,而 TURN 服务器则用于在无法直接通信的情况下进行中转。通过合理配置 ICE 服务器,我们可以在不同的网络环境中都能够顺利地进行实时通信。

设置 Codec 编解码器

获取可用 Codec

1
const supportsSetCodecPreferences = window.RTCRtpTransceiver && 'setCodecPreferences' in window.RTCRtpTransceiver.prototype;

获取 Codec

通过 RTCRtpSender.getCapabilities('video') 获取可支持的 codec。然后把它们放进列表 codecPreferences 里,通过 Select 元素展示,让用户选择想使用的 codec

1
2
3
4
5
6
7
8
9
10
if (supportsSetCodecPreferences) {
const { codecs } = RTCRtpSender.getCapabilities('video');
const codecPreferences = [];
codecs.forEach(codec => {
if (['video/red', 'video/ulpfec', 'video/rtx'].includes(codec.mimeType)) {
return;
}
codecPreferences.push({ ...codec, value: (codec.mimeType + ' ' + (codec.sdpFmtpLine || '')).trim() });
});
}

配置 Codec

找到用户选择的 Codec。调用 transceiver.setCodecPreferences(codecs) ,把选中的 Codec 交给 transceiver 最上面,优先使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (supportsSetCodecPreferences) {
// 获取选择的codec
let selectedIndex = 1
const preferredCodec = codecPreferences[selectedIndex];
if (preferredCodec.value !== '') {
const [mimeType, sdpFmtpLine] = preferredCodec.value.split(' ');
const { codecs } = RTCRtpSender.getCapabilities('video');
const selectedCodecIndex = codecs.findIndex(c => c.mimeType === mimeType && c.sdpFmtpLine === sdpFmtpLine);
const selectedCodec = codecs[selectedCodecIndex];
// 先移除
codecs.splice(selectedCodecIndex, 1);
// 插入第一个
codecs.unshift(selectedCodec);
const transceiver = pc1.getTransceivers().find(t => t.sender && t.sender.track === localStream.getVideoTracks()[0]);
transceiver.setCodecPreferences(codecs);
}
}

WebRTC的应用场景

WebRTC 技术已经被广泛应用于多个领域,包括:

  1. 视频会议
    WebRTC 使得在网页浏览器中进行高清视频会议成为可能,用户可以通过浏览器直接加入会议室,与其他参与者进行实时视频通话。

  2. 在线教育
    教育机构可以利用 WebRTC 技术搭建在线教育平台,让老师和学生之间进行实时的远程教学,实现互动和教学资源共享。

  3. 社交应用
    社交应用程序可以利用 WebRTC 技术实现实时语音和视频通话功能,让用户之间进行更加直观和自然的交流。

  4. 远程医疗
    医疗机构可以利用 WebRTC 技术搭建远程医疗平台,实现医生和患者之间的远程会诊和医疗服务,提高医疗资源的利用效率。

WebRTC的未来发展趋势

随着实时通信需求的不断增加,WebRTC 技术也在不断发展和完善。未来,我们可以期待以下几个方面的发展趋势:

  1. 网络性能的提升
    随着网络基础设施的不断改善和网络带宽的增加,WebRTC 技术将能够实现更高质量、更稳定的实时通信体验。

  2. 新的应用场景
    随着 WebRTC 技术的成熟和普及,我们可以预见到它将被应用于更多领域,如工业控制、智能家居和虚拟现实等。

  3. 标准化和开放性
    WebRTC 作为一个开放标准的实时通信技术,将继续推动标准化工作的进行,以确保不同厂商和平台之间的互操作性和兼容性。

  4. 安全性和隐私保护
    随着个人隐私和数据安全意识的提高,WebRTC 技术将会加强对通信内容的加密和隐私保护,以确保用户数据的安全性。

总结

WebRTC 技术的出现为实时通信应用提供了强大的支持,使得开发者可以轻松构建具有高质量和稳定性的实时通信应用。随着技术的不断发展和完善,我们有理由相信 WebRTC 将会在未来的数字化世界中扮演越来越重要的角色,为用户带来更丰富、更便捷的通信体验。