概述
笔者在实现点对点视频通信过程中,遇到了 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
效果展示
文中图片若有侵权,请联系笔者及时删除