本文系微信公众号和知乎专栏《MediaStack》原创文章,欢迎大家关注,随时进行交流。
背景说明
上周技术支持反馈一个客户问题:期望我们设备能够支持VFU,因为他们的环境中需要该功能。一看这玩意有点懵,没有见过呀,利用周末时间所以赶紧查找相关资料,完成一次愉快的自我提升和补充学习之旅,也期望对大家有所帮助。

VFU介绍
VFU是什么?
视频快速更新 (VFU) 是 H.323 视频会议协议的一项功能,允许在视频会议通话期间快速恢复丢失的视频帧。当视频帧在传输过程中丢失时,接收端点可以向发送端点发送视频快速更新请求。发送端点将尽快重传丢失的帧,从而使接收端点能够快速恢复并显示丢失的视频信息。这可以通过减少丢失帧对视频流的影响来帮助提高视频会议的整体质量。
https://www.itu.int/rec/T-REC-H.323-202203-I/en
VFU使用场景
RFC5168中指明:在基于会话初始化协议(SIP)的系统中,并以交互操作的方式在不同供应商的实时交互应用程序中使用。VFU功能由 Microsoft、Polycom、Radvision 开发并由多个供应商使用。
RFC5186中提到VFU的缺点:使用SIP信令路径发送视频快速更新不如使用RTP控制协议(RTCP)反馈方法[7],因为命令流经信令路径中的所有代理,增加了消息延迟,并导致代理不必要的过载。所以大部分场景还是用RTCP消息端到端完成交互,而不是通过信令代理。
VFU方法传输的视频控制命令可能导致视频数据的发送方在允许的带宽内发送更多数据。
VFU的交互方式
VFU是以XML形式使用SIP INFO方法传输,“Content-Type”设置为“application/media_control+xml”。这种方法得益于SIP内置的可靠性。
VFU请求
错误反馈
VFU工作原理
视频通过SIP服务器从发送端传输到视频接收端。首先发送 I 帧,然后发送后续 P 帧。
* 此循环一直持续到发生视频丢失/损坏。在某些情况下发送的后续 P 帧可能会损坏,并且可能会发生视频丢失。在这种情况下,视频接收器将向视频发送端发送 VFU 请求。
* VFU 请求最初在 SIP INFO 消息中发送到 SIP服务器。视频SIP服务器将 VFU 请求转发到视频发送端。
* 一旦视频发送端接收到 VFU 请求,就会通过 RTP 流将 I 帧传输到视频接收端。一旦接收到 I 帧,后续的 P 帧按照新的I帧逐步发送。
抓包实例
实际抓包查看,SIP/XML请求的信息和RFC5168基本一致
在发送端收到该xml之后,完成vfu的解析,并调整发送内容,重新发送I帧。
VFU的功能有点类似RTCP中PLI或FRI的功能,这部分学习流媒体传输的就非常熟悉了(特别是熟悉Webrtc的童鞋),列入之后流媒体博客系列中,晚些时候介绍一下。

VFU代码走读
依据RFC5168中提到有很多应用场景,其中大部分VFU是用在会议,或者实时音视频场景下,正好当前正在学习linphone的源代码,其中包含了该部分内容,所以本次是以linphone代码完成走读。
VFU发送流程
linphone中有相关接口,需要时候调用接口即可:
之后会调用到liblinphone中同名API,过程比较容易理解,就不再啰嗦了。
之后会调用call的接口:
调用MediaSession的接口,在这里需要注意一下:如果RTCP相关信令开启的话,优先走RTCP的方式。只有RTCP没有使能时才会走VFU的方式。
其中AVPF相关流程会走FIR的流程,这个是通过RTCP控制信令进行关键帧I帧的获取,
sendVfuRequest() 
SalCallOp 类中的 sendVfuRequest() 函数。该函数用于向远程方发送视频快速更新(VFU)请求。

VFU接收流程
接收流程也是类似的,会在sal中收到相关信息
首先在coreinit时候完成相关callback函数的注册,sal收到sip反馈的VFU XML之后,就会通过callback回调方式完成相关I帧数据的发送。
具体的sal callback接口如下:
vfu_request()
vfu_request()是一个静态函数,用于向远程方发送视频快速更新(VFU)请求。
sendVfu()
MS2VideoControl 类中的 sendVfu() 函数。该函数用于向远程方发送视频快速更新(VFU)请求。
sendVfu() 函数首先从 MS2VideoControl 对象获取 VideoStream 对象。如果未找到 VideoStream 对象,则该函数返回。
sendVfu() 函数然后调用 VideoStream 对象上的 video_stream_send_vfu() 函数,以将 VFU 请求发送到远程方。
video_stream_send_vfu()
video_stream_send_vfu() 函数使用 RTP 协议向远程方发送 VFU 请求。VFU请求是一个特殊的RTP数据包,告诉发送方仅仅发送当前帧和前一帧之间的差异。这可以显著减少需要发送的数据量,特别是对于与前一帧仅略有不同的帧。
VideoEncoderInterface API
VFU在linphone中的VideoEncoderInterface接口宏定义

onRequestVfuCall()
onRequestVfuCall()是一个回调函数,用于处理视频快速更新(VFU)请求。
EncoderFilter 类
H26xEncoderFilter是EncoderFilter子类,用于H26X相关通用接口的构建:
requestVfu()
H26xEncoderFilter 类中的 requestVfu() 函数,用于向编码器请求视频快速更新(VFU)。
置位I帧请求相关标志位:
process()
H26xEncoderFilter 类中的 process() 函数用于处理视频帧。
feed()
feed函数用于Linphone中获取视频帧数据,如果requestIFrame置true,用于强制对关键帧进行编码。
至此相关函数调用就走读完成了。

我是一枚爱跑步的程序猿,维护公众号和知乎专栏《MediaStack》,有兴趣可以关注,一起学习音视频知识,时不时分享实战经验。
继续阅读
阅读原文