1、配置webrtc抗丢包方法为FEC Only。
启动配置的时候,可以选NACK Only 或FEC Only 或NACK、FEC混合模式。
为了测试webrtc的X264 FEC抗丢包性能,在启动配置的时候,选择FEC Only模式。
配置方法请参考:VCMLossProtectionLogic::SetMethod
2、使用Network Emulator Client配置网络丢包模型为随机丢包,随机丢包概率为5%
1、建立X264编码视频连接,查看视频清晰度及流畅性。
2、建立VP8编码视频连接,查看视频清晰度及流畅性。
视频无长时间卡顿,至少X264编解码效果要与VP8一致。
X264卡顿比VP8要卡顿很多。根据日志统计,
VP8解码前连续600帧观察,最长掉帧61帧,连续两次卡顿最短时间是18帧。
H264解码前连续600帧观察,最长掉帧86帧,根据时序图可以看出,只要启动丢包,X264会密集掉帧,效果比VP8要差很多。
视频数据从网卡收包到解码前,经过如下三方面的判断、处理:
1、RTP包连续性判断。这部分根据收到报文查看是否有丢包,若存在就尝试解析FEC报文,查看是否可以恢复。
2、视频帧完整性判断。这部分可以参考《webrtc QOS方法六(花屏问题解决方法)》,判断一帧frame_begin、frame_end是否存在,在frame_begin、frame_end之间报文的序列号是否连续,若有一样不满足,这个帧就不是完整帧,该帧需要丢弃。
3、视频帧间关系判断。视频帧间有相互参考关系,若P帧前面的I帧丢失,即便有完整的P帧,也无法解码。RtpFrameReferenceFinder::ManageFrameVp8、RtpFrameReferenceFinder::ManageFrameGeneric函数就是判断帧间的参考关系的。
上两个图可以看出,在判断帧间参考关系前就不一样。
两种模型下,丢包趋势相差不大。目前就只能排查帧完整性判断这块代码,处理有什么不一样。
PacketBuffer::FindFrames函数中X264和VP8处理差异还真比较大。
1、VP8的判断比较简单,只要有frame_begin、frame_end标志位,并且frame_begin、frame_end之间报文连续就认为是完整的一帧。
2、X264的判断在VP8的基础上又增加了IDR NAL、时间戳、序列号连续性判断。
std::vector> PacketBuffer::FindFrames(
uint16_t seq_num) {
std::vector> found_frames;
for (size_t i = 0; i < size_ && PotentialNewFrame(seq_num); ++i) {
size_t index = seq_num % size_;
sequence_buffer_[index].continuous = true;
// If all packets of the frame is continuous, find the first packet of the
// frame and create an RtpFrameObject.
if (sequence_buffer_[index].frame_end) {
size_t frame_size = 0;
int max_nack_count = -1;
uint16_t start_seq_num = seq_num;
// Find the start index by searching backward until the packet with
// the |frame_begin| flag is set.
int start_index = index;
size_t tested_packets = 0;
bool is_h264 = data_buffer_[start_index].codec == kVideoCodecH264;
bool is_h264_keyframe = false;
int64_t frame_timestamp = data_buffer_[start_index].timestamp;
while (true) {
++tested_packets;
frame_size += data_buffer_[start_index].sizeBytes;
max_nack_count =
std::max(max_nack_count, data_buffer_[start_index].timesNacked);
sequence_buffer_[start_index].frame_created = true;
if (!is_h264 && sequence_buffer_[start_index].frame_begin)
break;
if (is_h264 && !is_h264_keyframe) {
const RTPVideoHeaderH264& header =
data_buffer_[start_index].video_header.codecHeader.H264;
for (size_t i = 0; i < header.nalus_length; ++i) {
if (header.nalus[i].type == H264::NaluType::kIdr) {
is_h264_keyframe = true;
break;
}
}
}
if (tested_packets == size_)
break;
start_index = start_index > 0 ? start_index - 1 : size_ - 1;
// In the case of H264 we don't have a frame_begin bit (yes,
// |frame_begin| might be set to true but that is a lie). So instead
// we traverese backwards as long as we have a previous packet and
// the timestamp of that packet is the same as this one. This may cause
// the PacketBuffer to hand out incomplete frames.
// See: https://bugs.chromium.org/p/webrtc/issues/detail?id=7106
if (is_h264 &&
(!sequence_buffer_[start_index].used ||
data_buffer_[start_index].timestamp != frame_timestamp)) {
break;
}
--start_seq_num;
}
// If this is H264 but not a keyframe, make sure there are no gaps in the
// packet sequence numbers up until this point.
if (is_h264 && !is_h264_keyframe &&
missing_packets_.upper_bound(start_seq_num) !=
missing_packets_.begin()) {
uint16_t stop_index = (index + 1) % size_;
while (start_index != stop_index) {
sequence_buffer_[start_index].frame_created = false;
start_index = (start_index + 1) % size_;
}
return found_frames;
}
missing_packets_.erase(missing_packets_.begin(),
missing_packets_.upper_bound(seq_num));
found_frames.emplace_back(
new RtpFrameObject(this, start_seq_num, seq_num, frame_size,
max_nack_count, clock_->TimeInMilliseconds()));
}
++seq_num;
}
return found_frames;
}
分析这个函数,发现这段代码有问题
这个函数设计思想是判断帧的完整性,上面这个判断,实际上相当于增加了帧间参考关系的判断。帧间参考关系应该在RtpFrameReferenceFinder::ManageFrameInternal函数判断的。那边判断检测长期没有可解码帧,还发送I帧请求进一步进行保护。这里直接给丢了,没有任何其他保护措施。
明显没有之前那么密集掉帧。
PacketBuffer::FindFrames函数针对264编码又增加帧间参考关系的判断,并且没有请求I帧,导致视频效果弱于VP8。屏蔽这段代码,弱网下,视频流畅性与VP8持平。
webrtc之所以这么做是因为VP8的RTP协议里面有start标志位,H264没有。H264的start标志位是根据帧间时间戳变化来判断的,若帧首包丢失,webrtc判断不出来这是否是完整帧。