technical discussion - housekeeper-software/soft GitHub Wiki

voip的h264编码模式

平均码率编码

     平均码率编码可以使得传输的码流比较稳定,但是画面剧烈运动时,会降低编码质量。
     对带宽占用比较平均。
     可以设置video_encode_avg_bps = 256000, video_encode_max_bps=256000

固定质量编码

     质量固定,但码率随着画面的变化程度有很大变化,表现就是网络上的码流不稳定,但画面比较稳定。
     video_encode_crf = 23,这个质量比较高,640X480, 25 fps的传输需求,大概200K/s.
     RK3288/T800的摄像头均达不到25 fps/s,RK3288平均不到15,T800不到20
     我们采用这种编码方式。
     可以微调的参数有:
     video_encode_qmin,video_encode_qmax.

冗余编码

    在VOIP领域,媒体流通过RTP传输到对方,RTP流一般使用UDP传输,在互联网上,UDP存在丢包乱序等现象,实属正常。
    乱序的问题可以通过RTP的jitter buffer来处理,可以得到正确顺序的媒体流。但是RTP本身并不解决丢包的问题。丢包
    一般发生在中间某个节点(比如路由器)发生拥塞的时候,会丢弃一些数据包,导致对方接收到的数据不完整。对于VOIP来说,
    丢失一些数据包并不是致命的,如视频可能会出现短暂的马赛克,音频会出现卡顿。丢包的行为可以分为随机丢包或者突发性丢包,
    一般的处理方法(比如WebRTC)有两种:
    1.NACK,就是丢失了哪个包,可以通过RTCP协议通过发送方重传
    2.冗余编码,就是根据一定的冗余算法发送冗余信息,当丢包的时候,可以收到的冗余包进行恢复。这个也有标准协议,RFC2198和RFC5109.

慧管家中的处理方法

   慧管家目前只对视频(H264编码)做了冗余编码,打包的协议遵从RFC2198/RFC5109,打包实现完成抄写WebRTC,所以,可靠性有保障。
   鉴于老版本没有实现冗余编码,所以,是否支持冗余编码需要SIP协议来协商。最新发布的SDK需要上层配置red/ulpfec payload type格式,red=116, ulpfec=117
   只有双方都支持冗余编码,最终才会使用冗余编码。因为冗余编码改变了负载格式,所以,冗余编码后的数据流,无法直接使用。
   必须经过解包才能使用,所以,要想支持冗余编码,代理和终端都需要修改:
   1)冗余编码只会在RTP经过互联网并且支持ICE协议的情况下才会生效,也就是说,当手机端或者平板通过互联网连接到代理,或者手机端和平板通过互联网连接才会生效。
   2)冗余编码的冗余率根据当前的网络状况动态调整,最高达到50%冗余,可以抵抗20%的丢包率。
   同时,我们对室外机也进行了冗余处理,具体做法就是:
   如果要实现对某款室外机视频编码冗余(室外机本身不支持的),需要在代理中修改以下几个地方:
   1)首先要让代理明确支持冗余编码,需要在intercom_server.json中加上:
       "video_qos_red_payload_type":116,
	"video_qos_ulpfec_payload_type":117
  2)其次,所有的设备默认不支持冗余编码,除非在指定设备的device.ini中表明要支持冗余编码,具体的做法是:
      [base]
      force_video_use_rtp=1
      [media]
   video_format={"rtp":true,"codec":"h264","ts":3600,"w":640,"h":480,"packetizer":1,"pt":102,"early_media":true,"dir":3,"red_pt":116,"ulpfec_pt":117}
      默认情况下,代理中转视频是通过UDP方法,也就是说,不管什么协议,直接按照UDP收然后转发。但是要支持冗余,必须强制代理使用RTP收到室外机的视频数据。
      其他,要修改video_format,增加冗余编码负载。这个video_format会最终传给接收终端,接收终端看到有冗余编码负载类型,就知道对方支持冗余编码,同时终端知道自己也支持,最终代理会按照冗余编码打包视频。
      代理通过RTP收到室外机视频,要做两个事情:
      1)根据RTP包的timestamp,收下完整的视频帧(一帧视频一般分多个包传输,每个包不超过MTU限制)
      2)增加计算冗余编码,并且使用paced方式发送到终端。   

音频的冗余编码

     因为WebRTC没有直接实现,我们也没有实现。但是,WebRTC使用OPUS对音频进行编解码,而OPUS本身已经实现了冗余编码、PLC等高级的特性,所以,只要我们音频编码改成OPUS,这些就自然实现了。
     OPUS编码对CPU性能要求较高,我们计划在一体机中实现。

关于回音消除

    回音消除是VOIP中最难解决的一个问题,有两种方法可以处理:
    1)设备通过DSP来处理,这种处理效果好,但对设备本身要求较高,比如DSP的参数优化,MIC和speaker的功率匹配等问题都需要关注。只有较高技术水准的公司才能处理好,对于我们来说,过得去就行了。目前的品牌手机都支持,默认高通芯片就支持。山寨平板
基本上都没有实现。
    2)纯软件方法,目前来说,最好的、完全free的方法就是直接抄袭WebRTC的音频处理代码。我们就是这么干的。但是,光是抄袭解决不了问题,有一些关键得参数Google没法提供,要根据具体设备测试才能知道。其中关键得参数就是设备本身得播放和录音之间得固有延迟。一段音频数据丢进声卡得播放缓冲区,并通过扬声器播放,并同时被录制到MIC中,最终从录音缓冲区读取,这个时间差不多是固定得。播放得声音被再次录制,这就产生了回声。需要在发送到远端之前消除掉。我们目前测试得结果RK3288大概在80ms左右。
    大部分得回声是speaker出来立即被MIC采集而产生,这个过程在1ms左右。通过空间发射回来得回声称为残留回声,这个影响不是很大。

关于声音的增益

   SDK中提供了对即将播放的远端声音的增益和声卡录制声音的增益参数配置。具体要不要增益,需要看设备本身的技术参数来定。
   比如适配2019款全视通室外机的时候,因为回音消除的适配问题,我们迫不得已降低了RK3288 MIC的功率(减低到30%),这样
   一来,室外机听到的声音就变低很多,但是全时通自身有功放,所以通过调节室外机的输出音量,依然可以获得较高的音量输出。
   但是,有时候,我们会发现室外机传到平板的声音很小,主要是RK3288声卡这端没有功放或者品质较差,导致不能对远端声音进行
   自动增益。此刻,我们可以设置audio_play_agc_level,让底层强行对远端声音进行增益。比如我们的设置的音频采样率为8000,
   此刻,可以设置增益两倍,那这个参数就设置为16000,或者24000,但不能超过32768(最大值)。
   调高输出增益也会引起其他问题,比如回声变大,啸叫之类的。所以,合适的增益值需要针对设备来调试才能知道。
   PX30,有内置DSP处理声音,默认情况下,不需要增益,也不需要调节MIC功率。如果适配的不好,可以让工厂调节DSP的参数效果会
   更好。
   RK3288适配室内通,是可以降低MIC功率到30%,同时输出增益调节到16000,可以获得比以往更好的效果。比如相隔两个房间,应该
   不会出现啸叫。

VOIP 技术实现原因解析

  VOIP(voice over internet protocol),通过Internet协议的语音,由此可见,一开始并未包含视频,因为协议诞生的时候,带宽
  还不够宽,无法实现视频流的传输。
  一般而言,实现VOIP,涉及的协议包含SIP和RTP,SIP协议用于信令交换,RTP用于媒体传输。
  SIP协议一般基于UDP或者TCP,慧管家系统中,与室外机的SIP协议一般是使用UDP,与云端的呼叫一般使用TCP。如果SDP内容较大,比如超过了MTU(<=1500 Byte),则使用TCP协议,否则的话,可能导致SIP协议无法正常工作。
  SIP协议用于通讯双方的命令交换,比如一端发起一个呼叫,对方则收到一个incoming call事件,在此基础上继续应答,最终达成一致。
  SIP协议实现的是高层的信令,但在通讯时,还涉及到更多的信息交换,此刻就需要用到SDP协议。SDP是作为负载与SIP协议一起传输。如下所示:
  INVITE sips:[email protected] SIP/2.0 
   Via: SIP/2.0/TLS client.ANC.com:5061;branch = z9hG4bK74bf9 
   Max-Forwards: 70 
   From: Alice<sips:[email protected]>;tag = 1234567 
   To: Bob<sips:[email protected]>
   Call-ID: [email protected]  
   CSeq: 1 INVITE 
   Contact: <sips:[email protected]> 
   Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY 
   Supported: replaces 
   Content-Type: application/sdp 
   Content-Length: ...  
   
   v = 0 
   o = Alice 2890844526 2890844526 IN IP4 client.ANC.com 
   s = Session SDP 
   c = IN IP4 client.ANC.com 
   t = 3034423619 0 
   m = audio 49170 RTP/AVP 0 
   a = rtpmap:0 PCMU/8000
   这里是发起呼叫的一次信令,在v = 0之前的是SIP协议,从v = 0开始则是SDP协议。合成一个完整的呼叫请求报文发送到对方。SIP协议打包的方式类似HTTP协议。当我们发起一个新的呼叫的时候,必须通过SDP协议告诉对方,在本次会话中,我们打算使用的媒体格式、
传输地址等信息。比如这里SDP描述的是,本次会话只有语音,使用RTP协议传输,本机语音接收的RTP端口是49170,媒体参数也就是音频参数是:
 RTP负载类型为0(0就以为是音频编码格式为PCM ULAW格式,这个由规范定义的),PCMU/8000具体描述了音频的其他信息,PCMU就是
 PCM ULAW编码格式的标准定义,0就是RTP头里的负载代码。8000就是音频采样率为8K,其实还省略了通道,如果不加以描述,默认就是单声道,每个采样为16Bit宽,也就是2个字节。如果是立体声传输,则需要用PCMU/8000/2描述。

  如果还同时传输视频,则上面的SDP后面还附加视频的格式信息,如:
  m=video 40002 RTP/AVP 96//媒体、端口、传送层、格式列表
  a=rtpmap:96 H264/90000//净荷类型、编码名/时钟速率/编码参数(编码参数项为可选参数)
  a=fmtp:96 packetization-mode=1;sprop-parameter-sets=//视频“a=fmpt”字段参考RFC3984的8.2节
  其中,40002就是本机视频接收的UDP端口,96就是RTP的负载类型。packetization-mode 视频打包模式(0,1,2三种),sprop-parameter-sets:视频编码参数。有些视频编码时单独提供编码参数,而不是在视频流中传输这些参数,那就需要在这里描述,否则对方
无法正确解码。
 以上描述的是媒体流正式建立之前的信令和媒体参数格式交换,SIP协议有自身的逻辑,媒体参数还需要记忆不协商才能最终确定。
协商的过程是选择双方都支持的媒体编解码格式。比如一方支持三种音频格式,对方只支持一种,最终协商的结果就是双方格式的并集。
 真正传输媒体流的是RTP协议。
 通常情况下,RTP协议有个固定长度的(12个字节)的头,在头中描述了几个重要的信息:RTP负载格式,比如上述的音频格式0,视频格式96,当前包的seq(序列号),seq是递增的双字节,以及时间戳。时间戳很重要,对于音频来说,描述的是这个包音频的时间长度。
 在12个字节头后面紧接着是真实的媒体数据。因为采用UDP传输,一般每个包不超过MTU限制。对于音频来说,这个问题不大,比如每次
传输20ms的音频,如果8K/单声道/16bit的格式,20ms就是160个sample,每个sample两个字节,则一共有320个字节长度。再经过PCM ULAW编码(压缩比2:1),则最终每个RTP包的负载为160个字节。算上,RTP头12个字节,则通过抓包工具可以看到每个UDP包为172个字节长度。
 视频按编码帧传输,不同的编码质量帧的大小不同。一般而言,关键帧(I帧)较大,比如有10多K到几十K,P帧较小(增量帧),因为它依赖I帧才能解码。但是,一般而言,一帧总是大于MTU长度的。所以,要进行打包,打包的模式就是SDP中packetization-mode定义。
 不管哪种模式,打包的规范都是来源RFC,所以,不同的SIP软件都可以正确的工作。视频经过打包之后,确保每个包都小于MTU。seq依然逐包递增,但每帧视频不管分多个包传输,时间戳都是相同的,代表着同一帧。
 RTP协议还包含RTCP协议,RTCP协议用于交换传输的网络信息、收发包统计,用于QOS。
 RTP一般使用偶数端口,RTCP使用奇数端口。RTCP协议不是必须的,比如全视通,就没有支持RTCP协议。
 RTP的接收端收到的数据包可能是乱序的,就是seq不是按照递增顺序收到,这是UDP传输的特点,不保证顺序到达。因此,在接收端
 必须按照seq进行重新排序和缓冲。seq的价值就在于此。从seq的顺序还能知道有没有丢包。
 通过时间戳,我们可以知道一个包是不是来晚了,我们可以选择抛弃(如果前一帧已经送到解码器),如果前一帧还在缓冲器,我们
 继续交付给它。
 丢包:如果网络状况很差,丢包很严重,表现得结果就是声音断续,视频马赛克严重。音频丢包导致声音断续这个很容易理解,视频丢包会导致解码器无法正确解码,从而出现马赛克。一般而言,我们可以选择丢弃掉丢包得视频帧,但是丢包不是最好得选择,因为丢掉关键帧,导致其后所有的参考帧都无法解码。
 如何提高传输品质,这是个非常复杂的问题,比如WebRTC采用了丢包重传和冗余编码自适应的方式进行,要完整实现之,需要数万行代码。
 通常采用冗余编码可以明显的提高传输品质,比如视频可以采用ulpfec编码,音频使用OPUS编码(自带冗余编码),效果还是非常明显的。
 但是,如果只是局域网传输,以上手段都是多余的,因为丢包比较罕见。只有在Internet上传输,上述手段比较重要。
 完整的VOIP技术远不止如此,从摄像头采集视频到最终将视频显示在设备屏幕上,中间还有若干的技术细节需要考虑。

点对点协议(ICE协议)

  在Internet上传输媒体流,通常可以用位于云端的媒体服务器进行中转,这样比较简单。但是对服务器的压力也是可想而知的。毕竟,单台服务器的负载能力是有限的,如果并发很大,则需要增加物理服务器才能支撑。为了节省服务器的资源,往往使用点对点协议。
  点对点协议(俗称打洞),就是在互联网上的两个节点之间建立一条直连通道。可以简单理解为在两个路由器建立一条直连通道。我们知道,路由器只能从内往外发起连接,不能从外往里。只有从里往外发起连接之后,路由器才能为这条连接开辟从外往里的链路。ICE协议需要专门的STUN服务器,协助在两个路由之间建立直连链路。STUN服务器可能要求有两个IP地址,用于判断路由器的类型。某些路由器类型不能打洞,此刻,RTP传输就选择中转服务器进行。
  在慧管家中,如果两个平板在同一个路由下,或者路由之间有级联关系,比如KXRT_5G 和 sip_router,那么打洞是可以成功的。
我们会发现,这两个平板之间视频很流畅。因为是点对点直连,尽管双方都是通过云端SIP服务器呼叫的。