WebRTC對(duì)等體還需要查找并交換本地和遠(yuǎn)程音頻和視頻媒體信息,例如分辨率和編解碼器功能。 交換媒體配置信息的信令通過(guò)使用被稱為SDP的會(huì)話描述協(xié)議格式來(lái)交換,被稱為提議和應(yīng)答的元數(shù)據(jù)塊
WebRTC 音視頻通信基本流程
- 一方發(fā)起調(diào)用 getUserMedia 打開(kāi)本地?cái)z像頭
- 媒體協(xié)商(信令交換,媒體協(xié)商主要指 SDP 交換。)
- 建立通信
發(fā)起端 Amy 創(chuàng)建 Offer 并將 Offer 信息,并調(diào)用 setLocalDescription 將其保存起來(lái),通過(guò)信令服務(wù)器傳送給接收端 Bob
接收端 Bob 收到對(duì)等端 Amy 的 Offer 信息后調(diào)用 setRemoteDescription 方法將其保存起來(lái),并創(chuàng)建 Answer 信息,同理也將 Answer 消息通過(guò) setLocalDescription 保存,并通過(guò)信令服務(wù)器傳送給呼叫端 Amy
呼叫端 Amy 收到對(duì)等端 Blob 的 Answer 信息后調(diào)用 setRemoteDescription 方法將其 Answer 保存起來(lái)
為什么需要媒體協(xié)商?
媒體協(xié)商的作用就是讓雙方找到共同支持的媒體能力,從而能實(shí)現(xiàn)彼此之間的音視頻通信。比如兩個(gè)人想聊天,一個(gè)人只會(huì)講中文,喜歡討論前端;一個(gè)人只會(huì)講英文,不喜歡前端技術(shù);通過(guò)互換資料,發(fā)現(xiàn)這沒(méi)法聊天。但如果出現(xiàn)另一個(gè)人會(huì)講中文,喜歡研究代碼和前端技術(shù),跟第一個(gè)人交換信息后,發(fā)現(xiàn)這可以聊天。所以發(fā)起端與接收端能不能進(jìn)行通信。
媒體協(xié)商是在做什么?
媒體協(xié)商就是在交換 SDP的過(guò)程。會(huì)話發(fā)起者通過(guò)創(chuàng)建一個(gè)offer,經(jīng)過(guò)信令服務(wù)器發(fā)送到接收方,接收方創(chuàng)建answer并返回給發(fā)送方,完成交換。
SDP 是什么?
SDP(Session Description Protocol)指會(huì)話描述協(xié)議,是一種通用的協(xié)議,基于文本,其本身并不屬于傳輸協(xié)議,需要依賴其它的傳輸協(xié)議(如 RTP 交換媒體信息。
SDP 主要用來(lái)描述多媒體會(huì)話,用途包括會(huì)話聲明、會(huì)話邀請(qǐng)、會(huì)話初始化等。通俗來(lái)講,它可以表示各端的能力,記錄有關(guān)于你音頻編解碼類型、編解碼器相關(guān)的參數(shù)、傳輸協(xié)議等信息。
交換 SDP 時(shí),通信的雙方會(huì)將接受到的 SDP 和自己的 SDP 進(jìn)行比較,取出他們之間的交集,這個(gè)交集就是協(xié)商的結(jié)果,也就是最終雙方音視頻通信時(shí)使用的音視頻參數(shù)及傳輸協(xié)議。
offer 和 answer 是什么?
在雙方要建立點(diǎn)對(duì)點(diǎn)通信時(shí),發(fā)起端發(fā)送的 SDP 消息稱為 Offer,接收端發(fā)送的 SDP 消息稱為 Answer
所以,offer 和 answer 本質(zhì)就是存有 SDP 信息的對(duì)象,所以也會(huì)叫做 SDP Offer 和 SDP Answer。
信令與信令服務(wù)器
信令通常指的是為了網(wǎng)絡(luò)中各種設(shè)備協(xié)調(diào)運(yùn)作,在設(shè)備之間傳遞的控制信息。
對(duì)于 WebRTC 通信來(lái)說(shuō),發(fā)起端發(fā)送 Offer SDP 和接收端接受 Answer
SDP,要怎么發(fā)給對(duì)方呢?這個(gè)過(guò)程還需要一種機(jī)制來(lái)協(xié)調(diào)通信并發(fā)送控制消息,這個(gè)過(guò)程就稱為信令。
而信令對(duì)應(yīng)的服務(wù)器就叫信令服務(wù)器,作為中間人幫助建立連接,主要負(fù)責(zé):
信令的處理,如媒體協(xié)商消息的傳遞
管理房間信息。比如用戶連接時(shí)告訴信令服務(wù)器自身的房間號(hào),由信令服務(wù)器找到也在該房間號(hào)的對(duì)等端并開(kāi)始嘗試通信,也通知用戶誰(shuí)加入了房間和離開(kāi)了房間,通知房間人數(shù)是否已滿等等,所以也叫信令服務(wù)器也叫房間服務(wù)器。
WebRTC 并沒(méi)有規(guī)定信令必須使用何種實(shí)現(xiàn),目前業(yè)界使用較多的是 WebSocket + JSON/SDP 的方案。其中 WebSocket 用來(lái)提供信令傳輸通道,JSON/SDP 用來(lái)封裝信令的具體內(nèi)容。
ICE
當(dāng)媒體協(xié)商完成后,WebRTC 就開(kāi)始建立網(wǎng)絡(luò)連接,其過(guò)程稱為 ICE(Interactive Connectivity Establishment)交互式連接建立。
ICE 不是一種協(xié)議,整合了 STUN 和 TURN 兩種協(xié)議(用于 NAT 穿透)的框架。
ICE 是在各端調(diào)用 setLocalDescription() 后就開(kāi)始了,其操作過(guò)程如下:
- 收集 Candidate
- 交換 Candidate
- 按優(yōu)先級(jí)嘗試連接
什么是 Candidate?
比如想用 socket 連接某臺(tái)服務(wù)器,一定要知道這臺(tái)服務(wù)器的一些基本信息,如服務(wù)器的 IP 地址、端口號(hào)以及使用的傳輸協(xié)議。只有知道了這些信息,才能與這臺(tái)服務(wù)器建立連接。而 Candidate 正是 WebRTC 用來(lái)描述它可以連接的遠(yuǎn)端的基本信息,因此 Candidate 是至少包括 IP 地址、端口號(hào)、協(xié)議的一個(gè)信息集。
const peerA = new RTCPeerConnection()
const peerB = new RTCPeerConnection()
API
pc.createOffer:創(chuàng)建 offer 方法,方法會(huì)返回 SDP Offer 信息
pc.setLocalDescription 設(shè)置本地 SDP 描述信息
pc.setRemoteDescription:設(shè)置遠(yuǎn)端的 SDP 描述信息,即對(duì)方發(fā)過(guò)來(lái)的 SDP 信息
pc.createAnswer:遠(yuǎn)端創(chuàng)建應(yīng)答 Answer 方法,方法會(huì)返回 SDP Offer 信息
pc.ontrack:設(shè)置完遠(yuǎn)端 SDP 描述信息后會(huì)觸發(fā)該方法,接收對(duì)方的媒體流
pc.onicecandidate:設(shè)置完本地 SDP 描述信息后會(huì)觸發(fā)該方法,打開(kāi)一個(gè)連接,開(kāi)始運(yùn)轉(zhuǎn)媒體流
pc.addIceCandidate:連接添加對(duì)方的網(wǎng)絡(luò)信息
pc.setLocalDescription:將 localDescription 設(shè)置為 offer,localDescription 即為我們需要發(fā)送給應(yīng)答方的 sdp,此描述指定連接本地端的屬性,包括媒體格式
pc.setRemoteDescription:改變與連接相關(guān)的描述,該描述主要是描述有些關(guān)于連接的屬性,例如對(duì)端使用的解碼器
RTCPeerConnection就代表著一個(gè)peer的連接,它是WebRTC應(yīng)用核心。
- 1、調(diào)用peerA (RTCPeerConnection對(duì)象) createOffer方法準(zhǔn)備創(chuàng)建SDP
- 2、在createOffer的回調(diào)方法里,同時(shí)做了這兩件事
a、調(diào)用peerA的setLocalDescription(description)方法,這個(gè)方法會(huì)觸發(fā)peerA的icecandidate
監(jiān)聽(tīng)方法handleConnection. 在這個(gè)方法里,會(huì)將peerA的icecandidate發(fā)送給peerB.
然后PeerB執(zhí)行addIceCandidate(candidate),將peerA的candidate登記在案.
b、將peerA的description (就是SDP)發(fā)送給peerB
- 3、peerB收到peerA發(fā)來(lái)的SDP,執(zhí)行createAnswer,在這個(gè)回調(diào)方法里,同時(shí)做兩件事
a、調(diào)用peerB的setLocalDescription(description)方法,這個(gè)方法會(huì)觸發(fā)peerB的icecandidate監(jiān)聽(tīng)方法handleConnection,在這個(gè)方法里,會(huì)將peerB的icecandidate發(fā)送給peerA.
peerA收到后執(zhí)行addIceCandidate(candidate),將peerB的candidate也登記
b、將peerB的SDP發(fā)送給peerA.文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-469074.html
- 4、peerA和peerB開(kāi)始傳遞音視頻
總結(jié):整個(gè)過(guò)程就是peerA和peerB互相交換iceCandidate和SDP的過(guò)程。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-469074.html
import { useEffect, useRef, useState } from 'react'
import './App.css'
function App() {
const localVideoRef = useRef<HTMLVideoElement>(null)
const remoteVideoRef = useRef<HTMLVideoElement>(null)
const pc = useRef<RTCPeerConnection>()
const localStreamRef = useRef<MediaStream>()
const wsRef = useRef(new WebSocket('ws://127.0.0.1:1234'))
const username = (Math.random() + 1).toString(36).substring(7)
const [status, setStatus] = useState('開(kāi)始通話')
useEffect(() => {
initWs()
getMediaDevices().then(() => {
createRtcConnection()
addLocalStreamToRtcConnection()
})
}, [])
const initWs = () => {
wsRef.current.onopen = () => console.log('ws 已經(jīng)打開(kāi)')
wsRef.current.onmessage = wsOnMessage
}
const wsOnMessage = (e: MessageEvent) => {
const wsData = JSON.parse(e.data)
console.log('wsData', wsData)
const wsUsername = wsData['username']
console.log('wsUsername', wsUsername)
if (username === wsUsername) {
console.log('跳過(guò)處理本條消息')
return
}
const wsType = wsData['type']
console.log('wsType', wsType)
if (wsType === 'offer') {
const wsOffer = wsData['data']
pc.current?.setRemoteDescription(new RTCSessionDescription(JSON.parse(wsOffer)))
setStatus('請(qǐng)接聽(tīng)通話')
}
if (wsType === 'answer') {
const wsAnswer = wsData['data']
pc.current?.setRemoteDescription(new RTCSessionDescription(JSON.parse(wsAnswer)))
setStatus('通話中')
}
if (wsType === 'candidate') {
const wsCandidate = JSON.parse(wsData['data'])
pc.current?.addIceCandidate(new RTCIceCandidate(wsCandidate))
console.log('添加候選成功', wsCandidate)
}
}
const wsSend = (type: string, data: any) => {
wsRef.current.send(JSON.stringify({
username,
type,
data,
}))
}
const getMediaDevices = async () => {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false,
})
console.log('stream', stream)
localVideoRef.current!.srcObject = stream
localStreamRef.current = stream
}
const createRtcConnection = () => {
const _pc = new RTCPeerConnection({
iceServers: [
{
urls: ['stun:stun.stunprotocol.org:3478'],
}
]
})
_pc.onicecandidate = e => {
if (e.candidate) {
console.log('candidate', JSON.stringify(e.candidate))
wsSend('candidate', JSON.stringify(e.candidate))
}
}
_pc.ontrack = e => {
remoteVideoRef.current!.srcObject = e.streams[0]
}
pc.current = _pc
console.log('rtc 連接創(chuàng)建成功', _pc)
}
const createOffer = () => {
pc.current?.createOffer({
offerToReceiveVideo: true,
offerToReceiveAudio: true,
})
.then(sdp => {
console.log('offer', JSON.stringify(sdp))
pc.current?.setLocalDescription(sdp)
wsSend('offer', JSON.stringify(sdp))
setStatus('等待對(duì)方接聽(tīng)')
})
}
const createAnswer = () => {
pc.current?.createAnswer({
offerToReceiveVideo: true,
offerToReceiveAudio: true,
})
.then(sdp => {
console.log('answer', JSON.stringify(sdp))
pc.current?.setLocalDescription(sdp)
wsSend('answer', JSON.stringify(sdp))
setStatus('通話中')
})
}
const addLocalStreamToRtcConnection = () => {
const localStream = localStreamRef.current!
localStream.getTracks().forEach(track => {
pc.current!.addTrack(track, localStream)
})
console.log('將本地視頻流添加到 RTC 連接成功')
}
return (
<div>
<div>{`username:${username}`}</div>
<video style={{ width: '400px' }} ref={localVideoRef} autoPlay controls></video>
<video style={{ width: '400px' }} ref={remoteVideoRef} autoPlay controls></video>
<br />
<p>{`當(dāng)前狀態(tài):${status}`}</p>
<br />
{status === '開(kāi)始通話' && (
<button onClick={createOffer}>撥號(hào)</button>
)}
{status === '請(qǐng)接聽(tīng)通話' && (
<button onClick={createAnswer}>接聽(tīng)</button>
)}
</div>
)
}
export default App
到了這里,關(guān)于【W(wǎng)ebRTC】音視頻通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!