webrtc 判断是否支持_WebRTC Qos 杂问

为什么一开始fps会降到1,后来有了正常的两方通话后又恢复到30

WebRTC对每一帧调用 VideoStreamEncoder::OnFrame,然后调用VideoStreamEncoder::MaybeEncodeVideoFrame 这个方法中可能会执行最终的编码。

定义了一个 posted_frames_waiting_for_encode_变量表示当前等待编码的帧数,通过它判断是否应该跳过来不及编码的帧。

每次OnFrame调用会在调用的线程执行 posted_frames_waiting_for_encode_++,然后在编码线程中如果判断posted_frames_waiting_for_encode_ > 1则跳过编码,如果posted_frames_waiting_for_encode_ == 1则进行编码。不论是否跳过还是执行了编码,这个值都会减一posted_frames_waiting_for_encode_--。这样就保证了如果有多个帧正在等待编码,则会编码这些帧中的最晚的帧。

如果等待编码的帧有多个,说明编码性能赶不上设备采集帧率,编码器的性能会最终影响fps,低性能会导致实际fps降低。

另一方面,在VideoSender中也通过改变编码器的参数来改变实际的编码帧率,该参数为encoder_params_.input_frame_rate。

VideoSender调用VideoSender::UpdateEncoderParameters 更新帧率参数,它调用media_optimization::MediaOptimization对象_mediaOpt 的

InputFrameRate()方法得到估算的帧率。

MediaOptimization是一个工具类,它的功能之一就是估算input_frame_rate,它会记录每一帧的时间戳,然后根据最近两秒的帧数来估算帧率。

对每一帧,调用MediaOptimization::DropFrame ,这个接口是用来判断是否丢帧的,每一帧都会调用这个方法(名字起得不好,害得我查了很久才发现这个是每一帧都调用的)。具体方法:

每次DropFrame时记录一个时间戳,插入到队列中。

调用 InputFrameRate 时在队列中查找一个区间,计算这个区间的每帧平均时间,即最大时间戳 减 最小时间戳除以数量。

区间的计算:从队列尾部开始,到与尾部时间戳之差小于2秒的最大值,即这个区间最大长度为2秒。

最后使用这个每帧平均时间,计算 input_frame_rate。

为什么后来又升上去了,还没有研究,大概是因为分辨率的降低导致编码速度加快,然后通过MediaOptimization的估算慢慢提升了帧率。

为什么分辨率会由刚开始的 720x1280 经过几秒后降到 360x640

VideoStreamEncoder::OnFrame

VideoStreamEncoder::MaybeEncodeVideoFrame

// 判断是否应该降低视频能级,如果降级则调用 AdaptDown ,并且跳过该帧的解码

VideoStreamEncoder::DropDueToSize

VideoStreamEncoder::AdaptDown

VideoStreamEncoder::VideoSourceProxy::RequestResolutionLowerThan

VideoSourceInterface::AddOrUpdateSink // 最终改变输入帧率的方法

DegradationPreference:猜测是协议层由对端设置的,表示使用何种策略降低视频能级。可选值有4种:

DISABLED

MAINTAIN_FRAMERATE

MAINTAIN_RESOLUTION

BALANCED

根据log判断默认值是 MAINTAIN_FRAMERATE,后面只有在结束通信时设置成了 DISABLED。猜测可能一直保持MAINTAIN_FRAMERATE不变。

VideoStreamEncoder::DropDueToSize 根据一个初始码率encoder_start_bitrate_bps_来限制分辨率。将初始码率分成3个档次对应一个最大分辨率:

[0, 300kbps]=>320x240

[300kbps, 500kbps]=>640x480

[500kbps, )=>没有限制,

如果大于该档次的最大分辨率就判断为需要降低分辨率。

DropFrame是如何运作的

MediaOptimization,在两个类中使用:

vcm::VideoSender

webrtc::VCMEncodedFrameCallback

MediaOptimization内部使用FrameDropper,用来计算什么时候丢帧。

FEC在WEBRTC是怎么使用的?

在 rtp_sender_video.cc 文件中处理FEC、NACK等Qos功能。

payload,即SDP中定义的负载类型id。在代码中,payload >= 0 表示启用,payload < 0表示关闭。

FlexfecSender flexfec_sender 负责flexfec的对象。google的 demo server 都不支持,估计很少有支持的吧。如果有的话,它是比ulpfec优先的,可以在demo的设置项中打开开关。

red_payload_type_: redundant payload。red用来持有ulpfec,没有red也就没有ulpfec。

ulpfec_payload_type_: ulpfec payload。

RTPSenderVideo::SendVideo() 最终发送视频数RTP包的方法,这个方法先计算包的数量,然后计算出来详细的包大小,最后一个包一个包的填充数据并发送。

对每个包

如果开启了flexfec,则发送flexfec包

如果开启了red,则发送携带ulpfec的red包,调用SendVideoPacketAsRedMaybeWithUlpfec

否则直接发送video数据

为什么H264的时候没有启用

RtpVideoSender::ConfigureProtection

PayloadTypeSupportsSkippingFecPackets

// 它判断了只有vp8和vp9才开启FEC,也就是说h264不开启FEC。

衡量FEC的效果

NACK with H264 为什么会导致 FEC 包的重传?

一个完整的帧包含所有的数据,是不需要重传的,产生 NACK 的原因一定是判定了一帧中的某些包丢失了。那么一定有个机制来判断一帧的完整性,而这种机制对 H264 with FEC 一定是有缺陷的。可能是因为,原始包 + FEC包都到达才判定为完整,因此导致了即使所有的原始数据包都到达,有FEC包没到达,也会被判定为帧不完整。

先要搞清楚两个问题:一是选择哪些包发送NACK?另一个是怎么判断帧的完整性?

哪些包需要发送NACK?

返回 NACK 列表的调用顺序:

ModuleProcessThread

VideoReceiver::Process

VCMReceiver::NackList // 没有计算,直接调用下面的方法

VCMJitterBuffer::GetNackList(bool* request_key_frame) // 执行具体计算的地方

ModuleProcessThread 周期性调用 VideoReceiver::Process 方法,最后调用的 VCMJitterBuffer 中的方法获取 NACK 列表,VCMJitterBuffer 负责计算哪些包是需要发送 NACK 的,也就是确定 NACK 列表。

VCMJitterBuffer::GetNackList 方法先根据时间和缓冲区大小更新missing_sequence_numbers_ 集合,使之不要超出最大限制。主要是根据一些条件删除 missing_sequence_numbers_ 中的数据,但这些判断与 VCMFrameBuffer 的状态没有太大关系。更多的是要判断是否应该 request_key_frame,即请求关键帧。

然后 VCMJitterBuffer::GetNackList 将 missing_sequence_numbers_ 集合中的数据转化成一个列表返回。

另一个更新 missing_sequence_numbers_ 集合的方法是VCMJitterBuffer::UpdateNackList,调用顺序:

VCMJitterBuffer::InsertPacket(packet)

VCMJitterBuffer::UpdateNackList(sequence_number)

UpdateNackList 的参数 sequence_number 就是 InsertPacket 的参数 packet 所持有的 sequence_number。UpdateNackList 根据新插入的 sequence_number 更新 missing_sequence_numbers_ 集合。主要逻辑如下:

latest_received_sequence_number_ 表示最新接收到的 sequence number,这个值会在 InsertPacket 和 UpdateNackList 中更新。

sequence number 应是连续的整数,如果传入的 sequence_number 比 latest_received_sequence_number_ 要大,也就是时间顺序上要晚,如果两者不是连续的(sequence_number > latest_received_sequence_number_ + 1),说明它们之间有其他 packet 没有接收到。那么这个区间内的所有 sequence number 就要添加到 missing_sequence_numbers_ 集合中。

如果sequence_number 比 latest_received_sequence_number_ 要小,说明这个 packet 是迟到的一个包,应该在之前的处理过程中,已经添加到了 missing_sequence_numbers_中,因此在 missing_sequence_numbers_ 中删除这个 sequence number 即可。

如何判断帧完整

VCMJitterBuffer

VCMFrameBuffer.GetState()

VCMSessionInfo.complete()

判断 VCMSessionInfo 完整有几个条件:

VCMSessionInfo 中有第一个 packet

VCMSessionInfo 中有最后一个 packet

所有的 packet 的 sequence number 都是连续的

这个条件很容易理解,也没有什么特别之处,关键在于如何判断是否有最后一个 packet,在代码中由变量 last_packet_seq_num_ 存储最后一个 packet 的 sequence number。

只有 VCMSessionInfo::InsertPacket 方法会更新 last_packet_seq_num_,而其中也明确区分了 H264 和其他 codec:

if (packet.codec == kVideoCodecH264) {

...

if (packet.markerBit &&

(last_packet_seq_num_ == -1 ||

IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_))) {

last_packet_seq_num_ = packet.seqNum;

}

} else {

...

}

注意判断的条件:packet.markerBit,RTP包的 M 标识位设置为1,才判定为最后一个包,但看后面的条件 IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_) 还不一定只有一个包被设置了 M 标识位。

问题:H264 RTP中是如何规定 M 标志位表示帧的最后一个数据包的?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值