一個解決WebRTC在iOS模擬器紅屏問題的實踐 概述 背景 環境搭建 遇到問題 解決方案 效果展示

概述

筆者在實現點對點視頻通信過程中,遇到了 iOS 模擬器紅屏問題。本文主要記錄如何解決這一問題。主要對 SDP 進行解讀,分享在編輯 SDP 過程中遇到的問題。

背景

隨着音視頻、雲遊戲越來越火,一個開源解決方案 WebRTC 成爲了衆多技術者繞不過的框架。在瞭解 WebRTC 基礎流程後,筆者也萌生了一個想法:使用 WebRTC 實現一個點對點的視頻 & 文本單聊程序。根據 WebRTC 的框架能力,視頻聊天屬於其基礎功能。想到就做,筆者開始將想法落地。

環境搭建

WebRTC 雖然可以實現點對點通信,但是在其通道建立過程中,有一個信令交互環節,則必要無法完全脫離服務器,爲此我們需要搭建一個信令服務器。

我們可以使用 node.js 來實現一個簡單的 websocket 服務器,其只需要做一件事:廣播收到的數據。我們還可以選擇使用 swift 來實現這樣一個簡單的信令服務器。

總結一下,我們需要的環境:

  • 1)MacOS 設備,例如 Mac Mini
  • 2)信令服務器一臺

遇到問題

在環境搭建完畢,工程實現完成後,急切點開應用程序的視頻通話按鈕後,看到的是如下圖效果

是的,筆者遇到了模擬器上運行視頻通話後紅屏的問題。

這似乎是 WebRTC 在 H264 上的一個 bug,官方也發現了,所以出了一個 錯誤報告。該 bug 的具體原因不在本文討論。而想要解決該問題,則有兩種方案。

  • 1)修改源代碼

援引 Stack Overflow 上的方案

Fix is to not set attribute 'kCVPixelBufferIOSurfacePropertiesKey' in  RTCVideoDecoderH264.mm for TARGET_IPHONE_SIMULATOR

筆者親測有效。此方案的麻煩點在於,我們改動完源代碼後,需要自行編譯,重新生成 WebRTC.framework 文件。

  • 2)修改 SDP 內容

WebRTC 在 iOS 模擬器上會出現紅屏問題,主要還是因爲框架對 H264 的解碼出現的 bug,我們可以通過在模擬器上不使用 H264 來規避此問題。因此,我們可以在應用層編輯 SDP 內容來達到禁用 H264 編碼的目的。

SDP 是什麼

想要編輯 SDP,我們首先需要了解它。SDP(Session Description Protocol),會話描述的協議,它不包含傳輸協議。

組成結構

                                                 +---------------------+
                                                 |        v=           |
                                                 +---------------------+
                 +---------------------+         +---------------------+
         ====    |   Session Metadata  |  =====  |        o=           |
         |       +---------------------+         +----------------------
         |                                       +---------------------+
         |                                       |        t=           |
         |                                       +---------------------+
         |
         |
         |                                       +---------------------+
         |                                       |        c=           |
         |                                       +---------------------+
         |       +---------------------+
         ====    | Network Description |   =====
         |       +---------------------+
         |                                       +---------------------+
         |                                       |    a=candidate      |
         |                                       +---------------------+
         |
         |
         |                                       +---------------------+
         |                                       |        m=           |
         |                                       +---------------------+
         |        +---------------------+        +---------------------+
         ====     | Stream Description  |  ===== |      a=rtpmap       |
         |        +---------------------+        +----------------------
         |                                       +---------------------+
         |                                       |      a=fmtp         |
         |                                       +---------------------+
         |                                       +---------------------+
         |                                       |      a=sendrecv..   |
         |                                       +---------------------+
 +---------------+
 |    SEMANTIC   |
 | COMPONENTS OF |
 |     SDP       |
 +---------------+
         |                                       +---------------------+
         |                                       |      a=crypto       |
         |                                       +---------------------+
         |         +---------------------+       +---------------------+
         ====      |Security Descriptions|  =====|      a=ice-frag     |
         |         +---------------------+       +----------------------
                 |                                       +---------------------+
         |                                       |      a=ice-pwd      |
         |                                       +---------------------+
         |                                       +---------------------+
         |                                       |     a=fingerprint   |
         |                                       +---------------------+
         |
         |
         |
         |                                       +---------------------+
         |                                       |      a=rtcp-fb      |
         |                                       +---------------------+
         |         +---------------------+       +---------------------+
         ====      |   Qos,Grouping      |       |                     |
                   |   Descriptions      |  =====|       a=group       |
                   +---------------------+       +----------------------
                                                 +---------------------+
                                                 |       a=rtcpmux     |
                                                 +---------------------+

從上文可以知道 SDP 有五個部分:

  • Session Metadata
  • Network Description
  • Stream Description
  • Security Descriptions
  • Qos, Grouping Descriptions

解讀

下面根據 RTCPeerConnection.createOffer 產生的 SDP 進行解讀(RFC4566)。

// SDP 類型,有 offer、answer
offer
// ---------------------- Session Metadata -------------------//
// v = 0  “v =”字段給出SDP的版本,默認爲0。
v=0
// o = <username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address> 
// <username> 是用戶在始發主機上的登錄名
// <sess-id> <sess-id>是一個數字字符串,使得<username><sess-id>nettype><addrtype>和<unicast-address>的元組形成會話的全局唯一標識符。
// <sess-version>是此會話描述的版本號 
// <nettype>是一個給出網絡類型的文本字符串。 “IN”被定義爲具有“Internet”的含義
// <addrtype>是一個文本字符串,給出了後面的地址類型 定義了“IP4”和“IP6”
// <unicast-address>是創建會話的計算機的地址。 
o=- 4764183099742106259 2 IN IP4 127.0.0.1
// s = <會話名稱>  “s =”字段是文本會話名稱.每個會話描述必須有一個且只有一個“s =”字段。“s =”字段不能爲空
s=-
// t =<start-time> <stop-time>“t =”行指定會話的開始和停止時間。如果<stop-time>設置爲零,則會話不受限制,但在<start-time>之後纔會生效。如果<start-time>也爲零,則會話被視爲永久會話。
t=0 0
//********************* Session Metadata ********************//

//---------------------- Security Descriptions-------------------//
//a = <attribute>:<value> 
//a = <fingerprint> SRIP 所需的DTLS指紋信息
a=fingerprint:sha-256 00:B3:00:58:25:4A:7D:C7:CB:E3:C6:63:43:03:71:63:33:73:CE:F9:CE:12:52:4C:95:2E:0E:96:FC:93:CE:11
// Negotiating Media Multiplexing Using the Session Description Protocol
// 表示需要共用一個傳輸通道傳輸的媒體,通過ssrc進行區分不同的流。如果沒有這一行,音視頻數據就會分別用單獨udp端口來發送
a=group:BUNDLE 0 1 2
a=extmap-allow-mixed
// WebRTC MediaStream Identification in the Session Description Protocol
// 標識SDP中包含MediaStream的標識
a=msid-semantic: WMS stream
a=ice-ufrag:9gy+
a=ice-pwd:FlgstiDMt4ffZ2NumjV7UZPP
// Trickle ICE:Incremental Provisioning of Candidates for the Interactive Connectivity Establishment (ICE) Protocol
// ICE建立候選時 採用增量設置的方式
a=ice-options:trickle renomination
// DTLS 握手方式 "active" / "passive" / "actpass"/ "holdconn"   'actpass': 連接或啓動傳出連接。
a=setup:actpass
a=mid:0
//********************* Security Descriptions ********************//

//---------------------- Stream Description -------------------//
// m=<media> <port> <proto> <fmt> ...
// <media>是媒體類型 當前定義的媒體 "audio","video", "text", "application", and "message"
// <port>是傳輸媒體流的傳輸端口。在相關的“c =”字段中指定,以及在媒體字段的<proto>子字段中定義的傳輸協議。
// <proto>是傳輸協議。傳輸協議的含義取決於相關“c =”字段中的地址類型字段。
// <fmt>是媒體格式描述(編碼類型)。第四個和任何後續 如果<proto>子字段是“RTP / AVP”或“RTP / SAVP”,則<fmt> 子字段包含RTP有效載荷類型號。
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 104 9 102 0 8 106 105 13 110 112 113 126
// c=<nettype> <addrtype> <connection-address>
// 會話描述必須包含 每個媒體描述中的至少一個“c =”字段或會話級別的單個“c =”字段
// <nettype>   “IN”被定義爲具有“Internet”的含義
// <addrtype> 爲IP4和IP6時
c=IN IP4 0.0.0.0
// The URI for declaring this header extension in an extmap attribute is "urn:ietf:params:rtp-hdrext:ssrc-audio-level".
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
//a = sendrecv 這指定應以發送和接收模式啓動工具。對於具有默認爲僅接收模式的工具的交互式會議,這是必需的。
a=sendrecv
a=msid:stream audio0
// "a = rtcp-mux"屬性以指示需要RTP和RTCP多路複用
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:63 red/48000/2
a=fmtp:63 111/111
a=ssrc:3316495333 cname:FlAaGq79STLBNArX
a=ssrc:3316495333 msid:stream audio0
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 124 35 36 123 122 125
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:9gy+
a=ice-pwd:FlgstiDMt4ffZ2NumjV7UZPP
a=ice-options:trickle renomination
a=fingerprint:sha-256 00:B3:00:58:25:4A:7D:C7:CB:E3:C6:63:43:03:71:63:33:73:CE:F9:CE:12:52:4C:95:2E:0E:96:FC:93:CE:11
a=setup:actpass
a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:stream video0
a=rtcp-mux
a=rtcp-rsize
// rtpmap:<payload type> <encoding name> / <clock rate> [/ <encoding parameters>]
// <payload type> 此屬性從RTP有效內容類型編號(在 “m =”行中使用)映射到表示要使用的有效載荷格式,編碼類型 採樣率 編碼參數
// 而以下格式的編碼的格式要去相應編碼格式標準文檔中查看 比如H264 就是RFC3984 https://tools.ietf.org/html/rfc3984
a=rtpmap:96 H264/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
// a = rtcp-fb: RTCP-FB-PT SP RTCP-FB-VAL CRLF
// rtcp-fb-pt是有效負載類型
// rtcp-fb-val定義反饋消息的類型 ack,nack,trr-int和rtcp-fb-id
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 H264/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:125 ulpfec/90000
a=ssrc-group:FID 2531124184 1348659893
a=ssrc:2531124184 cname:FlAaGq79STLBNArX
a=ssrc:2531124184 msid:stream video0
a=ssrc:1348659893 cname:FlAaGq79STLBNArX
a=ssrc:1348659893 msid:stream video0
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:9gy+
a=ice-pwd:FlgstiDMt4ffZ2NumjV7UZPP
a=ice-options:trickle renomination
a=fingerprint:sha-256 00:B3:00:58:25:4A:7D:C7:CB:E3:C6:63:43:03:71:63:33:73:CE:F9:CE:12:52:4C:95:2E:0E:96:FC:93:CE:11
a=setup:actpass
a=mid:2
a=sctp-port:5000
a=max-message-size:262144

媒體流信息

// m=<media> <port> <proto> <fmt> ...
// <media>是媒體類型 當前定義的媒體 "audio","video", "text", "application", and "message"
// <port>是傳輸媒體流的傳輸端口。在相關的“c =”字段中指定,以及在媒體字段的<proto>子字段中定義的傳輸協議。
// <proto>是傳輸協議。傳輸協議的含義取決於相關“c =”字段中的地址類型字段。
// <fmt>是媒體格式描述(編碼類型)。第四個和任何後續 如果<proto>子字段是“RTP / AVP”或“RTP / SAVP”,則<fmt> 子字段包含RTP有效載荷類型號。
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 124 35 36 123 122 125
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:9gy+
a=ice-pwd:FlgstiDMt4ffZ2NumjV7UZPP
a=ice-options:trickle renomination
a=fingerprint:sha-256 00:B3:00:58:25:4A:7D:C7:CB:E3:C6:63:43:03:71:63:33:73:CE:F9:CE:12:52:4C:95:2E:0E:96:FC:93:CE:11
a=setup:actpass
a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:stream video0
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 H264/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 H264/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 VP9/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=rtpmap:124 rtx/90000
a=fmtp:124 apt=127
a=rtpmap:35 AV1/90000
a=rtcp-fb:35 goog-remb
a=rtcp-fb:35 transport-cc
a=rtcp-fb:35 ccm fir
a=rtcp-fb:35 nack
a=rtcp-fb:35 nack pli
a=rtpmap:36 rtx/90000
a=fmtp:36 apt=35
a=rtpmap:123 red/90000
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=123
a=rtpmap:125 ulpfec/90000
a=ssrc-group:FID 2531124184 1348659893
a=ssrc:2531124184 cname:FlAaGq79STLBNArX
a=ssrc:2531124184 msid:stream video0
a=ssrc:1348659893 cname:FlAaGq79STLBNArX
a=ssrc:1348659893 msid:stream video0

解決方案

上文中提到修改源代碼來解決紅屏問題。另外一種方式就是修改 SDP 內容,而 SDP 的內容修改,也有兩個方法。其一是修改底層源代碼生成 offer 的實現,將 H264 在模擬器環境下排除。另一種則是在應用層修改 SDP 內容。

在實際修改過程中,很可能會遇到 Session Description is NULL. 報錯。相信筆者,我們的實現思路是沒有錯的,只要再注意一些小細節即可。

  • 1)在 RTCPeerConnection.setRemoteDescription 前修改 SDP 內容。
  • 2)注意 \r\n 符號。

我們需要確保將上文示例 SDP 內容修改如下即可

m=video 9 UDP/TLS/RTP/SAVPF 100 101 127 124 35 36 123 122 125
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:9gy+
a=ice-pwd:FlgstiDMt4ffZ2NumjV7UZPP
a=ice-options:trickle renomination
a=fingerprint:sha-256 00:B3:00:58:25:4A:7D:C7:CB:E3:C6:63:43:03:71:63:33:73:CE:F9:CE:12:52:4C:95:2E:0E:96:FC:93:CE:11
a=setup:actpass
a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:stream video0
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 VP9/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=rtpmap:124 rtx/90000
a=fmtp:124 apt=127
a=rtpmap:35 AV1/90000
a=rtcp-fb:35 goog-remb
a=rtcp-fb:35 transport-cc
a=rtcp-fb:35 ccm fir
a=rtcp-fb:35 nack
a=rtcp-fb:35 nack pli
a=rtpmap:36 rtx/90000
a=fmtp:36 apt=35
a=rtpmap:123 red/90000
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=123
a=rtpmap:125 ulpfec/90000
a=ssrc-group:FID 2531124184 1348659893
a=ssrc:2531124184 cname:FlAaGq79STLBNArX
a=ssrc:2531124184 msid:stream video0
a=ssrc:1348659893 cname:FlAaGq79STLBNArX
a=ssrc:1348659893 msid:stream video0

效果展示

文中圖片若有侵權,請聯繫筆者及時刪除

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章