首页 > 编程知识 正文

webrtc开源项目,nodejs webrtc

时间:2023-05-05 08:04:30 阅读:153483 作者:4844

在WebRTC中,前向纠错(FEC )和丢包重传(NACK )是用于抵抗网络错误的重要手段。 FEC在发送端将冗馀纠错码附加到分组,并将纠错码与包一起发送到接收端; 接收方根据纠错码进行数据检查和纠正。 RFC5109[1]定义FEC包的格式。 NACK在接收端检测到数据的分组丢失后,将NACK消息发送到发送端; 发送方根据NACK消息中的序列号,在发送缓冲区中找到对应的数据包,重新发送到接收方。 NACK需要发送侧的发送缓冲区的支持,RFC5104[2]定义NACK包的格式。

本文在研究WebRTC源代码的基础上,以视频数据包的收发为例,深入分析了ANCK丢包重传机制的实现。 主要内容包括: SDP协商NACK、接收方丢包判定、NACK消息结构、发送、接收和解析、RTP数据包重发。

1 NACK框架1.1 NACK介绍ACK是一种到达通知技术。 以TCP为例,这是因为他在接收方收到数据后,会向发送方回复“收到了数据”的信息(ACK ),告诉发送方“收到了”,以确保信息的可靠性。

NACK也是一种通知技术,但触发通知的条件正好与ACK相反,即在没有收到消息时通知发送方“我没有收到消息”,即未达到的通知。

有两种类型的数据定义可以通过rfc4585协议重新发送:

1 RTPFB:rtp消息丢失并重新发送。

2 PSFB )指定净载荷传递,净载荷传递中分为以下三种

(1) pli ) picturelossindication )视频帧被丢失并被传输。

)2) sli )滑动)滑动丢失并重新旋转。

)3) rpsi (参考图片选择)参考帧的丢失重发。

创建视频连接的SDP协议将协商上述类型的NACK重定向。 以webrtc为例,协商两种NACK。 rtp消息丢包重传的NACK(nack之后是无参数、默认RTPFB )、PLI视频帧丢失重传的nack )。

1.2基本流程NACK作为RTP层反馈参数与视频编码器相关联。 WebRTC在初始化阶段,在创建媒体引擎时,会收集所有本地支持的视频编码,而NACK作为编码的属性一起收集。 在下一个SDP协商过程中,NACK属性将协商到提供/应答。

当估计RTT延迟相对较小时,webRTC将使用NACK进行丢包补偿。 NACK是请求重发的过程,其流程如下图所示。 在这个过程中,在网络抖动和数据包损失严重的情况下,在同一时刻有可能收到很多NACK的重发请求,发送方会将这些重发请求瞬间放入pacer中进行重发,因此pacer的延迟会增大,pace的参照编码率会增加WebRTC设计了在处理NACK重发时,通过统计在单位时间窗口的周期内发送的字节数据来限制流的重发码率控制器。 如果在该时间窗口内发送的数据的编码率大于estimator评估的编码率,则不重新发送当前的NACK请求,而是等待下一个NACK。

2 NACK算法框架2.1媒体引擎NACK算法媒体引擎中的NACK算法思想:

1媒体引擎启动,接收rtp数据包;

2进行RTP分组报头分析,并传送到具体编码格式以分析具体的编解码参数信息;

3 vi_channel打开线程,jitterbuffer进行包处理、数据帧的获取、包的插入、包的排序等操作;

GetFrame :获取用于保存当前包empty类型的图像帧;

根据包的时间戳计算图像帧render时间戳;

jitterbuffer寻找对应的图像帧,完成包的插入、排序操作;

4 vi_channel打开重发线程,jitterbuffer执行NACK构建; 通过rtcp将构建完毕的NACKlist反馈到发送端,进行数据包的再发送;

首先获取当前序列号的最大值和最小值,并用于构建初始nack_seq_nums_internal列表。 如果最大值和最小值都为-1,则发出密钥帧请求而不是NACK重发;

如果最大值和最小值都大于0,则计算NACKList的大小; 如果NACKList大于NACK阈值,则需要进行NACKList减肥并在FrameList中查找关键帧。 丢弃小于此关键帧的上一个数据帧,并重复此过程以完成减肥。

获得符合的NACKList初始列表nack_seq_nums_internal后,以最小值1为初始值,依次递增NACKList列表nack_seq_nums_internal进行初始化;

依次对照初始化了的nack_seq_nums_internal和FrameList的各帧中的分组序号,在存在的情况下将NACKList设为-1; 同时处理NACKList空包数据,设为-2;

压缩nack_seq_nums_internal中的数据。 接收到的数据包(已经设定为-1)和空数据包-

2)进行过滤;

依次将NACKList初始列表nack_seq_nums_internal中的数据添加到NACKList nack_seq_nums中,完成NACKList构建;

将构建完成的NACKlist反馈给发送端,进行重传;

5 接收新的数据包,将完整的数据发送给解码端进行解码;

每次一个完整图像帧接收到后,更新一下解码状态标志中最小值;以便后续NACK获取时最小值的更新。

6 将解码后的图像数据发送给render进行渲染。

2.2 WebRTC 最新NACK算法

新的WebRTC的NACK算法和MediaEngine中的大致相同,但是构建NACKList的方式不同。

1 MediaEngine启动,并接收rtp包;

2 rtp包头部解析,并转发给具体的编码格式解析具体编解码参数信息;

3 vi_channel 开启线程,jitterbuffer对数据包进行处理、数据帧获取、数据包插入,数据包重排等操作;jitterbuffer进行插包处理,同时进行NACKList构建;

每次进行插包处理时,都将接收到数据包序列号和之前已经保存的最新序列号latest_received_sequence_number进行比对:如果不是新的数据包,则从missing_sequence_numbers中剔除;如果是新的数据包,则进行NACK构建,以上次保存的序列号+1为起始值,以新收到的序列号为结束,将之间的序列号先缓存到missing_sequence_numbers中;

插入该序列号完成后,需要判断当前missing_sequence_numbers是否过大、是否收到的序列号是否太旧,并进行NACKList过大、序列号过久等处理;完成NACKList瘦身;

判断并更新当前序列号为latest_received_sequence_number;

这样每次接受到一个数据包,都可以形成一个较小的NACK List

4 vi_channel开启重传线程,jitterbuffer进行NACK获取,得到NACKlist通过rtcp反馈给发送端,进行数据包重发;

这时只需要从missing_sequence_numbers中获取当前的NACK列表即可,可以保证是最新的NACK请求列表。 3 音视频流畅度优化问题汇总

之前cdy进行NACK优化:包括RecycleFramesUntilKeyFrame函数、NACK其他参数优化等,但是出现一个问题:建立通话后有7~15s左右时间黑屏现象;

3.1 马赛克

分析原因如下:

由于建立通话时出现黑屏现象,因此尝试将decodable属性放开,可以保证所有图像帧在一个RTT时间后可以解码,而不是出现循环等待现象,这样黑屏现象可以解决,但是出现马赛克。原因就是每帧图象帧只等待有限RTT时间就送去解码,没有等待跟多次NACK请求,这时数据帧是不完整的因此会出现马赛克现象。

解决办法

将decodable属性关闭,并参考黑屏现象解决办法 void VCMSessionInfo::UpdateDecodableSession(const FrameData& frame_data) { if (complete_ || decodable_) return; if (frame_type_ == kVideoFrameKey || !HaveFirstPacket()) return; decodable_ = true;} 3.2 黑屏

分析原因如下:

由于原MediaEngine中构建NACKList时都需要获取最大值和最小值,但该最大值和最小值又依赖获取得到一帧完整图像帧所有数据,因此在建立通话伊始阶段,一直获取不到完整图像帧所有的包,导致MediaEngine一直请求关键帧,但没有完整数据帧可以解码。

解决办法

尝试在接受到一帧图像帧数据后,如果该帧图像已经有起始码标志位、同时有结束码标志,虽然不是完整帧,但是可以设置为NACK标志位,并在获取最大最小值时进行判断,用于最小值一直获取不到的破解办法; void VCMSessionInfo::UpdateCompleteSession() { if (packets_.front().isFirstPacket && packets_.back().markerBit) { // Do we have all the packets in this session? bool complete_session = true; PacketIterator it = packets_.begin(); PacketIterator prev_it = it; ++it; for (; it != packets_.end(); ++it) { if (!InSequence(it, prev_it)) { complete_session = false; break; }else{session_nack_ = true; } prev_it = it; } complete_ = complete_session; }} if(*low_seq_num == -1 && frame_buffers_[i]->IsRetransmitted()){*low_seq_num = last_decoded_state_.sequence_num();} 3.3 卡顿

对比iOS和Android版本和E10通话流程,发现iOS通话过程更加流畅,抓包分析发现NACK请求数据比较少,但是更加敏捷,反应比较迅速。而Android版本,前几次需要发送多个关键帧请求,然后再进行NACK请求,同时每次NACK请求相比iOS数据量都比较大。

分析原因如下:

如第二节列举的NACK请求算法的差异,导致NACK请求后NACKList数据的差异,影响到是否快速响应的效率,同时也影响NACKList数据大小问题。

解决办法

尝试将新的NACK计算算法移植到MediaEngine中,正在调优中。

碰到的问题:

查看log和 抓包发现:存在一种可能,每一帧关键帧或者IDR帧的第一个包(sps包)可能丢包,但是新的旧的NACK算法都无法判断出来,进行请求,导致I帧和P帧都解码失败。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。