网上搜索webrtc + freeswitch得到结果基本都是基于网页的,利用 javascript +jsSip实现与freeswitch对接,而讲解如何直接利用webrtc的native模块来实现与freeswitch对接的文章几乎没有。众所周知,webrtc的底层实现都是c++代码,对于一个不擅长javascript的c++码农,能够直接利用webrtc模块来搭建一个具备音视频通信功能的客户端无疑是让人兴奋的。

本文简要记录一下对接过程,包括对接过程遇到的一些问题以及解决方案,在博客写出来也希望能给有同样业务需求的小伙伴一点点帮助。

先上一张webrtc客户端与手机sip终端(imsdroid)进行视频通话的效果图吧:

freeswitch 对接深圳电信IMS freeswitch 运营商对接_数据

  1. sip信令的交互。webrtc本身不提供信令交互功能,为实现与freeswitch信令交互,我选择了eXosip这个开源库来做信令交互。eXosip实现了sip协议栈和sip数据包的收发,轻量级、使用比较简单,本人以前工作中也使用过pjsip,虽然pjsip也实现了sip协议,但是pjsip包含了音视频的采集、编码解码、传输等,很明显不适合用于此处。在一个项目中同时应用webrtc和eXosip会遇到一个编译链接错误,大概是什么符号冲突之类的错误,原因是eXosip使用了openssl,而webrtc使用了boringssl,boringssl是google根据自己需要由openssl发展出来的一个分支,那么出现符号冲突就不奇怪了。我的解决方案是在应用程序中动态加载eXosip,避开符号冲突的问题,毕竟openssl那么庞大的库出现那么多的符号冲突,问题可不好解决,惹不起那就躲得起。
  2. 使用websocket连接到freeswitch。freeswitch经过配置后可以支持与webrtc终端的交互,但前提是这个sip终端得通过wss方式来向freeswitch注册,这样freeswitch才会认为这个sip终端是个webrtc类型的终端,当freeswitc向webrtc终端发送invite消息时才会附带上webrtc所能正确识别的sdp消息,而通过udp或者tcp方式注册的终端则不会。freeswitch向普通sip终端发出的invite中的sdp消息大概如下:
v=0
o=FreeSWITCH 1592401897 1592401898 IN IP4 192.168.1.240
s=FreeSWITCH
c=IN IP4 192.168.1.240
t=0 0
m=audio 43022 RTP/AVP 0 101
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-16
a=ptime:20
m=video 42672 RTP/AVP 102
b=AS:1024
a=rtpmap:102 H264/90000
a=fmtp:102 profile-level-id=42800d;max-mbps=11880;max-fs=396; impl=FFMPEG
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 ccm tmmbr
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli

而向webrtc终端发送的的invite中的sdp消息如下:

v=0
o=FreeSWITCH 1592402840 1592402841 IN IP4 192.168.1.240
s=FreeSWITCH
c=IN IP4 192.168.1.240
t=0 0
a=msid-semantic: WMS adF68stJyAmY0aEFOWBBOi2JOdB5Qx4R
m=audio 42468 RTP/SAVPF 0 101
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000
a=fingerprint:sha-256 CA:0F:2B:4C:D8:E6:94:DB:9A:E7:98:4B:68:E7:DD:4C:FA:C0:A0:FE:F3:7C:EA:E4:53:66:79:53:EC:DE:51:C8
a=setup:actpass
a=rtcp-mux
a=rtcp:42468 IN IP4 192.168.1.240
a=ssrc:3280840740 cname:AzNyS68hwk5nlATZ
a=ssrc:3280840740 msid:adF68stJyAmY0aEFOWBBOi2JOdB5Qx4R a0
a=ssrc:3280840740 mslabel:adF68stJyAmY0aEFOWBBOi2JOdB5Qx4R
a=ssrc:3280840740 label:adF68stJyAmY0aEFOWBBOi2JOdB5Qx4Ra0
a=ice-ufrag:mkr1tjA7n123oXKO
a=ice-pwd:mod5kbcHm8UyyEoKOFC7O251
a=candidate:7777397031 1 udp 659136 192.168.1.240 42468 typ host generation 0
a=candidate:7777397031 2 udp 659136 192.168.1.240 42468 typ host generation 0
a=ptime:20
m=video 42012 RTP/SAVPF 102
b=AS:1024
a=rtpmap:102 H264/90000
a=fmtp:102 profile-level-id=42800d;max-mbps=11880;max-fs=396; impl=FFMPEG
a=fingerprint:sha-256 CA:0F:2B:4C:D8:E6:94:DB:9A:E7:98:4B:68:E7:DD:4C:FA:C0:A0:FE:F3:7C:EA:E4:53:66:79:53:EC:DE:51:C8
a=setup:actpass
a=rtcp-mux
a=rtcp:42012 IN IP4 192.168.1.240
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 ccm tmmbr
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=ssrc:2484639382 cname:AzNyS68hwk5nlATZ
a=ssrc:2484639382 msid:adF68stJyAmY0aEFOWBBOi2JOdB5Qx4R v0
a=ssrc:2484639382 mslabel:adF68stJyAmY0aEFOWBBOi2JOdB5Qx4R
a=ssrc:2484639382 label:adF68stJyAmY0aEFOWBBOi2JOdB5Qx4Rv0
a=ice-ufrag:otBxzPiFyGKmDYFZ
a=ice-pwd:7cvLTwkie4sErcDGw53VKcr9
a=candidate:2454663916 1 udp 659136 192.168.1.240 42012 typ host generation 0
a=candidate:2454663916 2 udp 659135 192.168.1.240 42012 typ host generation 0
a=end-of-candidates

可以看到向webrtc发出的sdp消息多了ice candidate以及ice协商过程的认证信息,只有这样的sdp消息才能够被webrtc正确识别。

  1. websocket代理。上面说到我使用了eXosip来作sip信令交互,但是eXosip只支持udp或者tcp方式,并不支持websocket,而与freeswitch对接又必须使用websocket,最终我的解决方案是弄一个websocket代理,如下图:

eXosip将数据发给代理,由代理通过websocket发级freeswitch,这样实现简单客户端改动也小。

  1. 手机视频的显示。平时我们所说的720p视频,是指 分辨率为 1280x720的视频,是宽比高大的,而对于手机的720p视频,则是720x1280(大家的手机都是宽比高小的),所以webrtc解码得到的视频帧也是720x1280的,同时不知道解码器内部作了什么鬼操作,得到的每行yuv数据居然不是720、360、360,而是768、384、384,在渲染yuv数据的时候需要按真实的720x1280的大小来读数据,把多出来的填充数据给踢除,否则显示出来的图像会异常。

总结:背靠大树好乘凉,webrtc作为一个行业标准,其稳定性、跨平台、优异的音视频处理能力是不用怀疑的,毕竟是google出产嘛。但webrtc是主是面向web应用的,所以使用javascript可以相对轻松的实现音视频功能,但是做native开发可就没有那么轻松了,网上相关资料少,想要实现什么功能只能从webrtc源码里面找,或者分析相关的demo。webrtc源码中的demo比较少,并且功能也比较简单,而javascript实现的demo官网就有不少,我的分析之道就是调试chromium,看看这些demo在webrtc内部是如何实现的,把这些demo分析透了,那么做native开发时实现这些功能自然就不是问题。

后话:当调试chromium时才发现,一个高配置的电脑对于提高工作效率是多么的重要,当vs2017附加了5、6个chromium进程时,如果没有固态硬盘、没有16g内存、没有8代i5/i7以上的u,你是不会想继续下去的。