音視頻流媒體開發(fā)【七十九】- WebRTC6-音視頻一對一通話

音視頻流媒體開發(fā)-目錄
iOS知識點(diǎn)-目錄
Android-目錄
Flutter-目錄
數(shù)據(jù)結(jié)構(gòu)與算法-目錄
uni-pp-目錄

6 實(shí)現(xiàn)音視頻一對一通話

1. 語法補(bǔ)充 =>

=>是es6語法中的arrow function

06/6.1 arrow.html

<html>
  <head>
    <title>arrow</title>
  </head>
  <body>
    <script>
      console.log("普通函數(shù)方式");
      var arr1 = [1, 2, 3, 4, 5];
      arr1.forEach(function(e) {
        console.log(e);
      });

      console.log("箭頭函數(shù)方式");
      var arr2 = [1, 2, 3, 4, 5];
      arr2.forEach((e) => {
        console.log(e);
      });
    </script>
  </body>
</html>

2. 語法補(bǔ)充promise

promise的then是異步執(zhí)行,但鏈路的then/catch是順序執(zhí)行,我們直接看范例

代碼:06/6.1 promise.html

<html>
  <head>
    <title>arrow</title>
  </head>
  <body>
    <script>
      function taskA() {
        console.log("Task A");
      }

      function taskB() {
        console.log("Task B");
        throw new Error("taskB掉坑里了");
      }

      function onRejected(error) {
        console.log("onRejected catch Error: A or B", error);
      }

      function finalTask() {
        console.log("Final Task");
      }

      var promise = Promise.resolve();
      promise
      .then(taskA)
      .then(taskB)
      .catch(onRejected)
      .then(finalTask);
    </script>
  </body>
</html>

代碼流程


6.1 一對一通話原理

對于我們WebRTC應(yīng)用開發(fā)人員而言,主要是關(guān)注RTCPeerConnection類,我們以(1)信令設(shè)計;(2)媒體協(xié)商;(3)加入Stream/Track;(4)網(wǎng)絡(luò)協(xié)商 四大塊繼續(xù)講解通話原理

6.1.1 信令協(xié)議設(shè)計

采用json封裝格式

  1. join 加入房間
  2. resp-join 當(dāng)join房間后發(fā)現(xiàn)房間已經(jīng)存在另一個人時則返回另一個人的uid;如果只有自己則不返回
  3. leave 離開房間,服務(wù)器收到leave信令則檢查同一房間是否有其他人,如果有其他人則通知他有人離開
  4. new-peer 服務(wù)器通知客戶端有新人加入,收到new-peer則發(fā)起連接請求
  5. peer-leave 服務(wù)器通知客戶端有人離開
  6. offer 轉(zhuǎn)發(fā)offer sdp
  7. answer 轉(zhuǎn)發(fā)answer sdp
  8. candidate 轉(zhuǎn)發(fā)candidate sdp
join
var jsonMsg = {
  'cmd': 'join',
  'roomId': roomId,
  'uid': localUserId,
};
resp--join
jsonMsg = {
  'cmd': 'resp‐join',
  'remoteUid': remoteUid
};
leave
var jsonMsg = {
  'cmd': 'leave',
  'roomId': roomId,
  'uid': localUserId,
};
new--peer
var jsonMsg = {
  'cmd': 'new‐peer',
  'remoteUid': uid
};
peer--leave
var jsonMsg = {
  'cmd': 'peer‐leave',
  'remoteUid': uid
};
offer
var jsonMsg = {
  'cmd': 'offer',
  'roomId': roomId,
  'uid': localUserId,
  'remoteUid':remoteUserId,
  'msg': JSON.stringify(sessionDescription)
};
answer
var jsonMsg = {
  'cmd': 'answer',
  'roomId': roomId,
  'uid': localUserId,
  'remoteUid':remoteUserId,
  'msg': JSON.stringify(sessionDescription)
};
candidate
var jsonMsg = {
  'cmd': 'candidate',
  'roomId': roomId,
  'uid': localUserId,
  'remoteUid':remoteUserId,
  'msg': JSON.stringify(candidateJson)
};
6.1.2 媒體協(xié)商
  • createOffer
    基本格式
    aPromise = myPeerConnection.createOffer([options])?
  • [options]
var options = {
  offerToReceiveAudio: true, // 告訴另一端,你是否想接收音頻,默認(rèn)true
  offerToReceiveVideo: true, // 告訴另一端,你是否想接收視頻,默認(rèn)true
  iceRestart: false, // 是否在活躍狀態(tài)重啟ICE網(wǎng)絡(luò)協(xié)商
};

ICE Restart (webrtc.github.io)

iceRestart:只有在處于活躍的時候,iceRestart=false才有作用。

  • createAnswer
    基本格式
    aPromise = RTCPeerConnection .createAnswer([ options ])? 目前createAnswer的options是無效的。

  • setLocalDescription
    基本格式
    aPromise = RTCPeerConnection .setLocalDescription(sessionDescription);

  • setRemoteDescription
    基本格式
    aPromise = pc.setRemoteDescription(sessionDescription);

6.1.3 加入Stream/Track
  • addTrack
    基本格式
    rtpSender = rtcPeerConnection .addTrack(track,stream ...);
    track:添加到RTCPeerConnection中的媒體軌(音頻track/視頻track)
    stream:getUserMedia中拿到的流,指定track所在的stream
6.1.4 網(wǎng)絡(luò)協(xié)商
  • addIceCandidate
    基本格式
    aPromise = pc.addIceCandidate(候選人);

  • candidate


注意Android和Web端的不同。

6.2 RTCPeerConnection補(bǔ)充

6.2.1 構(gòu)造函數(shù)

語法
pc = new RTCPeerConnection([ configuration ]);

configuration可選
  • bundlePolicy 一般用max-bundle
    banlanced:音頻與視頻軌使用各自的傳輸通道
    max-compat:每個軌使用自己的傳輸通道
    max-bundle:都綁定到同一個傳輸通道

  • iceTransportPolicy 一般用all
    指定ICE的傳輸策略
    relay:只使用中繼候選者
    all:可以使用任何類型的候選者

  • iceServers

其由RTCIceServer組成,每個RTCIceServer都是一個ICE代理的服務(wù)器

  • rtcpMuxPolicy 一般用require

rtcp的復(fù)用策略,該選項(xiàng)在收集ICE候選者時使用

6.2.2 重要事件
  • onicecandidate 收到候選者時觸發(fā)的事件
  • ontrack 獲取遠(yuǎn)端流
  • onconnectionstatechange PeerConnection的連接狀態(tài),參考
pc.onconnectionstatechange = function(event) {
  switch(pc.connectionState) {
    case "connected":
      // The connection has become fully connected
      break;
    case "disconnected":
    case "failed":
      // One or more transports has terminated unexpectedly or in an error
      break;
    case "closed":
      // The connection has been closed
      break;
   }
}

6.3 實(shí)現(xiàn)WebRTC音視頻通話

開發(fā)步驟
1. 客戶端顯示界面
2. 打開攝像頭并顯示到頁面
3. websocket連接
4. join、new-peer、resp-join信令實(shí)現(xiàn)
5. leave、peer-leave信令實(shí)現(xiàn)
6. offer、answer、candidate信令實(shí)現(xiàn)
7. 綜合調(diào)試和完善

6.3.1 客戶端顯示界面

步驟:創(chuàng)建html頁面
主要是input、button、video控件的布局。

6.3.2 打開攝像頭并顯示到頁面

需要通過

6.3.3 websocket連接
6.3.4 join、new-peer、resp-join信令實(shí)現(xiàn)

思路:(1)點(diǎn)擊加入開妞;(2)響應(yīng)加入按鈕事件;(3)將join發(fā)送給服務(wù)器;(4)服務(wù)器 根據(jù)當(dāng)前房間的人數(shù)做處理,如果房間已經(jīng)有人則通知房間里面的人有新人加入(new-peer),并通知自己房間里面是什么人(respjoin)。

6.3.5 leave、peer-leave信令實(shí)現(xiàn)

思路:(1)點(diǎn)擊離開按鈕;(2)響應(yīng)離開按鈕事件;(3)將leave發(fā)送給服務(wù)器;(4)服務(wù)器處理leave,將發(fā)送者刪除并通知房間(peer-leave)的其他人;(5)房間的其他人在客戶端響應(yīng)peer-leave事件。

6.3.6 offer、answer、candidate信令實(shí)現(xiàn)

思路:
(1)收到new-peer (handleRemoteNewPeer處理),作為發(fā)起者創(chuàng)建RTCPeerConnection,綁定事件響應(yīng)函數(shù),加入本地流;
(2)創(chuàng)建offer sdp,設(shè)置本地sdp,并將offer sdp發(fā)送到服務(wù)器;
(3)服務(wù)器收到offer sdp 轉(zhuǎn)發(fā)給指定的remoteClient;
(4)接收者收到offer,也創(chuàng)建RTCPeerConnection,綁定事件響應(yīng)函數(shù),加入本地流;
(5)接收者設(shè)置遠(yuǎn)程sdp,并創(chuàng)建answer sdp,然后設(shè)置本地sdp并將answer sdp發(fā)送到服務(wù)器;
(6)服務(wù)器收到answer sdp 轉(zhuǎn)發(fā)給指定的remoteClient;
(7)發(fā)起者收到answer sdp,則設(shè)置遠(yuǎn)程sdp;
(8)發(fā)起者和接收者都收到ontrack回調(diào)事件,獲取到對方碼流的對象句柄;
(9)發(fā)起者和接收者都開始請求打洞,通過onIceCandidate獲取到打洞信息(candidate)并發(fā)送給對方
(10)如果P2P能成功則進(jìn)行P2P通話,如果P2P不成功則進(jìn)行中繼轉(zhuǎn)發(fā)通話。

6.3.7 綜合調(diào)試和完善

思路:
(1)點(diǎn)擊離開時,要將RTCPeerConnection關(guān)閉(close);
(2)點(diǎn)擊離開時,要將本地攝像頭和麥克風(fēng)關(guān)閉;
(3)檢測到客戶端退出時,服務(wù)器再次檢測該客戶端是否已經(jīng)退出房間。
(4)RTCPeerConnection時傳入ICE server的參數(shù),以便當(dāng)在公網(wǎng)環(huán)境下可以進(jìn)行正常通話。

啟動coturn

# nohup是重定向命令,輸出都將附加到當(dāng)前目錄的 nohup.out 文件中; 命令后加 & ,后臺執(zhí)行起來后按ctr+c,不會停止
sudo nohup turnserver ‐L 0.0.0.0 ‐a ‐u lqf:123456 ‐v ‐f ‐r nort.gov &
# 前臺啟動
sudo turnserver ‐L 0.0.0.0 ‐a ‐u lqf:123456 ‐v ‐f ‐r nort.gov
#然后查看相應(yīng)的端口號3478是否存在進(jìn)程
sudo lsof ‐i:3478

設(shè)置configuration,先設(shè)置為relay中繼模式,只有relay中繼模式可用的時候,才能部署到公網(wǎng)去(部署到公網(wǎng)后也先測試relay)。

var defaultConfiguration = {
  bundlePolicy: "max‐bundle",
  rtcpMuxPolicy: "require",
  iceTransportPolicy:"relay",//relay
  // 修改ice數(shù)組測試效果,需要進(jìn)行封裝
  iceServers: [
    {
      "urls": [
        "turn:192.168.221.134:3478?transport=udp",
        "turn:192.168.221.134:3478?transport=tcp" // 可以插入多個進(jìn)行備選
      ],
      "username": "lqf",
      "credential": "123456"
    },
    {
      "urls": [
        "stun:192.168.221.134:3478"
      ]
    }
  ]
};
pc = new RTCPeerConnection(defaultConfiguration);

relay中繼網(wǎng)絡(luò)狀況

局域網(wǎng)P2P

6.4 部署到公網(wǎng)

公網(wǎng)防火墻問題,比如 coturn涉及到的3478端口是否開放

啟動coturn

sudo nohup turnserver -L 0.0.0.0 -a -u lqf:123456 -v -f -r nort.gov &

# nohup是重定向命令,輸出都將附加到當(dāng)前目錄的 nohup.out 文件中; 命令后加 & ,后臺執(zhí)行起來后按ctr+c,不會停止
sudo nohup turnserver ‐L 0.0.0.0 ‐a ‐u lqf:123456 ‐v ‐f ‐r nort.gov &
# 前臺啟動
sudo turnserver ‐L 0.0.0.0 ‐a ‐u lqf:123456 ‐v ‐f ‐r nort.gov
#然后查看相應(yīng)的端口號3478是否存在進(jìn)程
sudo lsof ‐i:3478
編譯和啟動nginx
sudo apt‐get update
#安裝依賴:gcc、g++依賴庫
sudo apt‐get install build‐essential libtool
#安裝 pcre依賴庫(http://www.pcre.org/)
sudo apt‐get install libpcre3 libpcre3‐dev
#安裝 zlib依賴庫(http://www.zlib.net)
sudo apt‐get install zlib1g‐dev
#安裝ssl依賴庫
sudo apt‐get install openssl

#下載nginx 1.15.8版本
wget http://nginx.org/download/nginx‐1.15.8.tar.gz
tar xvzf nginx‐1.15.8.tar.gz
cd nginx‐1.15.8/

# 配置,一定要支持https
./configure ‐‐with‐http_ssl_module
# 編譯
make
#安裝
sudo make install

默認(rèn)安裝目錄:/usr/local/nginx

啟動:sudo /usr/local/nginx/sbin/nginx

停止:sudo /usr/local/nginx/sbin/nginx -s stop

重新加載配置文件:sudo /usr/local/nginx/sbin/nginx -s reload

產(chǎn)生證書
mkdir ‐p ~/cert
cd ~/cert
# CA私鑰
openssl genrsa ‐out key.pem 2048
# 自簽名證書
openssl req ‐new ‐x509 ‐key key.pem ‐out cert.pem ‐days 1095
配置web服務(wù)器
  1. 配置自己的證書
    ssl_certificate /home/lqf/cert/cert.pem? // 注意證書所在的路徑
    ssl_certificate_key /home/lqf/cert/key.pem?
  2. 配置主機(jī)域名或者主機(jī)IP
    server_name 192.168.221.134?
  3. web頁面所在目錄
    root /mnt/hgfs/ubuntu/ubuntu/module/webrtc/src/06/6.4/client?

完整配置文件:/usr/local/nginx/conf/conf.d/webrtc-https.conf

server {
  listen 443 ssl;
  ssl_certificate /home/lqf/cert/cert.pem;
  ssl_certificate_key /home/lqf/cert/key.pem;
  charset utf‐8;
  # ip地址或者域名
  server_name 192.168.221.134;
  location / {
    add_header 'Access‐Control‐Allow‐Origin' '*';
    add_header 'Access‐Control‐Allow‐Credentials' 'true';
    add_header 'Access‐Control‐Allow‐Methods' '*';
    add_header 'Access‐Control‐Allow‐Headers' 'Origin, X‐Requested‐With, Content‐Type,Accept';
    # web頁面所在目錄
    root /mnt/hgfs/ubuntu/ubuntu/module/webrtc/src/06/6.4/client;
    index index.php index.html index.htm;
  }
}

編輯nginx.conf文件,在末尾}之前添加包含文件

  include /usr/local/nginx/conf/conf.d/*.conf;
}
配置websocket代理

ws 不安全的連接 類似http
wss是安全的連接,類似https

https不能訪問ws,本身是安全的訪問,不能降級做不安全的訪問。

image.png

ws協(xié)議和wss協(xié)議兩個均是WebSocket協(xié)議的SCHEM,兩者一個是非安全的,一個是安全的。也是統(tǒng)一的資源標(biāo)志符。就好比HTTP協(xié)議和HTTPS協(xié)議的差別。

Nginx主要是提供wss連接的支持,https必須調(diào)用wss的連接。

完整配置文件:/usr/local/nginx/conf/conf.d/webrtc-websocket-proxy.conf

map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close;
}
upstream websocket {
  server 192.168.221.134:8099;
}
server {
  listen 8098 ssl;
  #ssl on;
  ssl_certificate /home/lqf/cert/cert.pem;
  ssl_certificate_key /home/lqf/cert/key.pem;
  server_name 192.168.221.134;
  
  location /ws {
    proxy_pass http://websocket;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
  }
}

wss://192.168.221.134:8098/ws 端口是跟著IP后面

信令服務(wù)器后臺執(zhí)行

sudo nohup node ./signal_server.js &
解決websocket自動斷開

我們在通話時,出現(xiàn)60秒后客戶端自動斷開的問題,是因?yàn)榻?jīng)過nginx代理時,如果websocket長時間沒有收發(fā)消息則該websocket將會被斷開。我們這里可以修改收發(fā)消息的時間間隔。

proxy_connect_timeout :后端服務(wù)器連接的超時時間_發(fā)起握手等候響應(yīng)超時時間

proxy_read_timeout:連接成功后等候后端服務(wù)器響應(yīng)時間其實(shí)已經(jīng)進(jìn)入后端的排隊(duì)之中等候處理(也可以說是后端服務(wù)器處理請求的時間)

proxy_send_timeout :后端服務(wù)器數(shù)據(jù)回傳時間_就是在規(guī)定時間之內(nèi)后端服務(wù)器必須傳完所有的數(shù)據(jù)nginx使用proxy模塊時,默認(rèn)的讀取超時時間是60s。

完整配置文件:/usr/local/nginx/conf/conf.d/webrtc-websocket-proxy.conf

map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close;
}

upstream websocket {
  server 192.168.221.134:8099;
}

server {
  listen 8098 ssl;
  ssl_certificate /home/lqf/cert/cert.pem;
  ssl_certificate_key /home/lqf/cert/key.pem;
  server_name 192.168.221.134;
  
  location /ws {
    proxy_pass http://websocket;
    proxy_http_version 1.1;
    proxy_connect_timeout 4s; #配置點(diǎn)1
    proxy_read_timeout 6000s; #配置點(diǎn)2,如果沒效,可以考慮這個時間配置長一點(diǎn)
    proxy_send_timeout 6000s; #配置點(diǎn)3
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
  }
}

客戶端 - 服務(wù)器 信令:心跳包

keeplive 間隔5秒發(fā)送一次給信令服務(wù)器,說明客戶端一直處于活躍的狀態(tài)。

6.5 Web和Android實(shí)現(xiàn)通話

本章主要內(nèi)容

  1. 獲取權(quán)限和引入庫(WebRTC、websocket)
  2. 信令處理
  3. Android WebRTC框架分析
  4. Android實(shí)戰(zhàn)-走讀代碼
6.5.1 獲取權(quán)限和引入庫

涉及到

  1. camera權(quán)限
  2. audio訪問權(quán)限
  3. 網(wǎng)絡(luò)訪問權(quán)限
    使用Android studio 3.2 開發(fā)
1 Android權(quán)限管理

申請靜態(tài)權(quán)限

AndroidManifest.xml文件配置

<uses‐permission android:name="android.permission.CAMERA" />
<uses‐permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses‐permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses‐permission android:name="android.permission.RECORD_AUDIO" />
<uses‐permission android:name="android.permission.INTERNET" />
<uses‐permission android:name="android.permission.ACCESS_NETWORK_STATE" />

動態(tài)申請權(quán)限

void requestPermissions(
      @NonNull Activity host, @NonNull String rationale,
      int requestCode, @Size(min = 1) @NonNull String... perms)

申請范例

String[] perms = {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
if (!EasyPermissions.hasPermissions(this, perms)) {
  EasyPermissions.requestPermissions(this, "Need permissions for camera &
  microphone", 0, perms);
}
2 引入庫
// WebRTC庫
implementation 'org.webrtc:google‐webrtc:1.0.+'
// websocket庫
implementation "org.java‐websocket:Java‐WebSocket:1.4.0"
// 處理權(quán)限庫
implementation 'pub.devrel:easypermissions:1.1.3'
6.5.2 信令處理

和js代碼一致,我們重點(diǎn)關(guān)注代碼的基本流程。

通過 RTCSignalClient類

配置websocket地址(RTCSignalClient類)(一定要根據(jù)自己的IP地址):
private static final String WS_URL = "ws://192.168.2.112:8099";
主動調(diào)用函數(shù)
  1. joinRoom
  2. leaveRoom
  3. sendOffer
  4. sendAnswer
  5. sendCandidate
回調(diào)函數(shù)
public interface OnSignalEventListener {
  void onConnected();
  void onConnecting();
  void onDisconnected();
  void onClosse();

  void onRemoteNewPeer(JSONObject message); // 新人加入
  void onResponseJoin(JSONObject message); // 加入回應(yīng)
  void onRemotePeerLeave(JSONObject message);
  void onRemoteOffer(JSONObject message);
  void onRemoteAnswer(JSONObject message);
  void onRemoteCandidate(JSONObject message);
}
6.5.3 Android WebRTC框架分析
配置coturn地址(CallActivity類):
private static MyIceServer[] iceServers = {
  new MyIceServer("stun:192.168.2.112:3478"),
  new MyIceServer("turn:192.168.2.112:3478?transport=udp",
    "lqf",
    "123456"),
  new MyIceServer("turn:192.168.2.112:3478?transport=tcp",
    "lqf",
    "123456")
};

Android端需要使用addstream的方式添加audiotrack 和videotrack,否則會出現(xiàn)web端聽不到Android端的的聲音。

web端和Android端的candidate格式是有一定的區(qū)別。
(1)發(fā)送傳輸

Android

web

(2)接收處理

Android 端

接口:

Web端

web端和Android端的sdp有區(qū)別。

6.5.4 Android實(shí)戰(zhàn)-走讀代碼
  • 權(quán)限
    在 manifests文件中添加權(quán)限


  • 在module的gradle中添加依賴庫

  • 收發(fā)信令
    實(shí)現(xiàn)Activity的切換
    編寫signal類使用websocket收發(fā)信令

  • 創(chuàng)建PeerConnection
    音視頻數(shù)據(jù)采集
    創(chuàng)建PeerConnection

  • 媒體協(xié)商
    協(xié)商媒體能力

  • 網(wǎng)絡(luò)協(xié)商
    candidate連通檢測

  • 視頻渲染

6.5.5 Web和Android通話總結(jié)

Web客戶端、Android客戶端、Nginx服務(wù)器一定要按照自己的IP去設(shè)置相關(guān)的連接,比如websocket和coturn地址。

要啟動的服務(wù)器:
(1)nginx
(2)信令服務(wù)器 signal_server
(3)打洞服務(wù)器 coturn (stun+turn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容