WebRTC+libwebsockets+Janus的秒开实践

背景

客户端SDK集成了WebRTC和libwebsockets,服务端使用了Janus,需要支持拉流秒开。

关于WebSocket

      Janus作为SFU,使用WebSocket协议与客户端通信。客户端在挑选开源库时其实没有太多选择,C层主要是libwebsockets库,这个也是Janus使用的库,还有Boost的Beast库,不过比较新,不敢踩坑,IOS上有RocketSocket,但不是跨平台,因此最后采用了libwebsockets库。
      libwebsockets库主要的问题是IO接口不太友好,需要自己启动一个线程轮询获取IO事件,在其回调中处理所有事件。

秒开要考虑的问题

  • libwebsockets IO的优化
          主要是写数据的处理。
          libwebsockets需要调用者自己维护发送队列,调用者调用lws_callback_on_writable来告知libwebsockets有数据要写,然后数据放入发送队列,libwebsockets会通过LWS_CALLBACK_CLIENT_WRITEABLE事件通知可写,这个时候调用者才可以从发送队列取出数据发送,这个是一个异步的过程。
          在libwebsockets的IO事件循环中,lws_service用于阻塞等待IO事件,但是lws_callback_on_writable并不会让lws_service退出阻塞状态,lws_service有一个最大等待时间,如果等待lws_service超时才处理待发送的数据无疑会增加整体接续时间。这里可以通过在调用线程中调用lws_cancel_service方法强制lws_service退出阻塞来立刻处理发送队列中的数据。这里需要用互斥锁对发送队列做一个同步。
  • Janus信令流程的简化
    Janus拉流的流程跟其插件式结构有关,主要流程:
    1.客户端创建Janus session;
    2.客户端创建Janus插件handle;
    3.客户端发Join请求给Janus,拉某个目标流;
    4.Janus通过事件通知该流的sdp;
    5.客户端发Start消息给Janus,告知客户端的sdp,这样完成了sdp的交互和协商;
    6.同时客户端发送Candidate给Janus,用于进行ICE通信、DTLS握手;
    7.Janus发送事件给客户端,告知流的启动结果。
    这些交互步骤中有些是必须串行的,每次交互都会消耗一个RTT,势必会增加接续时间,精简后的流程如下:
    1.客户端把创建Session、插件Handle、Join请求合并成一个命令发给Janus;
    2.Janus回复目标流的sdp;
    3.客户端发Start消息给Janus,告知客户端的sdp,这样完成了sdp的交互和协商;
    4.同时客户端发送Candidate给Janus,用于进行ICE通信、DTLS握手;
    5.Janus发送事件给客户端,告知流的启动结果。
  • 客户端的处理流程优化
    音视频设备的初始化不管是在哪个平台是都一个耗时的过程,可以放在初始化的时候执行,而不是每次接续的时候都执行。
  • DTLS握手的优化
          WebRTC和Janus会在ICE打洞成功之后进行DTLS握手,这里DTLS主要是用来交换SRTP的加密密钥。目前版本的Janus有一个问题,在ICE打洞成功之后并不能很实时的告知DTLS的握手模块,以至于总会丢失第一个客户端的握手包,这样必须等待客户端重发握手包。
          目前版本的WebRTC在使用BoringSSL的时候其重发超时依赖于RTT估算,最低是50ms,但是如果使用OpenSSL1.0.2则需要等待1S,因为OpenSSL1.0.2还没有设置DTLS握手超时的接口,需要自己适配。最好的办法是修改Janus,让Janus尽快响应DTLS握手包,否则这里将无差别的增加继续时间。
          另外,Janus还有一个问题,必须等待第一个客户端的Candidate信令到达之后才会激活DTLS握手过程,实际上,Janus作为SFU服务端可以开启ICE-Lite,其ICE过程应该是被动的,不需要主动向客户端的地址打洞。这样等待第一个客户端的Candidate信令到达之后才会激活DTLS握手过程这个特性实际上没有太多理由,会增加半个RTT的时间,因为客户端发送的第一个Candidate是本地的地址,并不需要跟Stun通信,跟Stun交互不影响DTSL的握手时间。
  • 服务端GOP缓存
          除了信令、通道建立时间需要优化以外,传统直播中秒开需要考虑的GOP缓存在这里也需要考虑,应该确保第一个发给客户端的数据就是可直接解码的I帧,否则客户端即使收到数据也会因为无法解码导致黑屏。
          Janus要缓存最近一个GOP的数据,在建立好通道后,立刻发送最近的一个I帧,而GOP内的非关键帧可以用抽帧的方式发给客户端,不用发送完整GOP,这样发送的数据减少,且让客户端以加速播放的方式追上当前的播放时间。
  • CDN的调度
          要提供全网的优质实时直播服务,Janus必须部署到CDN中,Janus与CDN的接入是另外一个话题,涉及很多协议的互转。

猜你喜欢

转载自blog.csdn.net/sonysuqin/article/details/82050970
今日推荐