视频采集帧率是发送端能够发送的最大帧率,但是当码率太高冲击上行带宽时,或者编码性能跟不上采集帧率,发送端都会通过降帧率方式缓解这些问题。
当码率太高冲击上行带宽时,通过MediaOptimization类调节帧率进而平滑码率;另外若使用webrtc自带VPX、openh264编码器,在编码器码控模块有接口配置是否掉帧调节码率功能。参见VideoCodecVP8/VideoCodecVP9/VideoCodecH264的frameDroppingOn参数定义。
当编码性能跟不上采集帧率时,webrtc是通过直接掉帧方式解决,参见下面第三节《采集速度大于编码速度掉帧处理》描述。
一、MediaOptimization作用
MediaOptimization类作用是调节发送端码率,使用两种方式调节:
1、配置编码器输入帧率调整码率:编码器根据输入帧率和码率计算分配给每一帧的字节数。所以实时调节帧率,会调整码率。
2、编码前判断是否掉帧调整码率:实时监控编码后数据量,当检测到码率过高,通过主动掉帧方式降低码率,缓解网络冲击。
函数调用关系图如下:
二、MediaOptimization实现
1)计算编码帧率
- 计算帧率
每次编码前,调用MediaOptimization::DropFrame->MediaOptimization::ProcessIncomingFrameRate函数,将当前系统时间加入incoming_frame_times_队列。然后根据一段时间接收到的帧数和持续时间,计算输入视频的帧率。
- 获取帧率
每次编码前,调用VideoSender::UpdateEncoderParameters->MediaOptimization::InputFrameRate函数,更新encoder_params_,获取当前编码帧率,配置到编码器中。
2)判断是否掉帧
判断是否掉帧在FrameDropper类里面实现。
- 核心参数
accumulator_max_:漏桶容积,其值为targetbps*kLeakyBucketSizeSeconds,随目标码率改变而改变;
accumulator_:漏桶累积,漏桶累积字节数,Fill增加,Leak减少,最大值为targetbps*kAccumulatorCapBufferSizeSecs;
drop_ratio_:丢帧率,指数滤波器,使丢帧率保持一个平滑的变化过程,每次Leak()后更新丢帧率;
key_frame_ratio_:关键帧率,指数滤波器,使关键帧率保持一个平滑的变化过程,每次Fill()后更新;
delta_frame_size_avg_kbits_:差分帧码率,指数滤波器,使关键帧率保持一个平滑的变化过程,每次Fill()后更新。
- 核心函数
FrameDropper::Fill
H264EncoderImpl::Encode
->VCMEncodedFrameCallback::OnEncodedImage
->MediaOptimization::UpdateWithEncodedData
->FrameDropper::Fill
当视频帧被编码后,MediaOptimization类会调用Fill()方法来填充漏桶。
1、将大帧拆分为large_frame_accumulation_count_个小块,并不累加accumulator_;
2、将小帧直接累计accumulator_。
Fill()方法同时更新key_frame_ratio_和delta_frame_size_avg_kbits_,用以计算大帧拆分块数和大帧判断。
FrameDropper::Leak
VideoStreamEncoder::EncodeVideoFrame
->VideoSender::AddVideoFrame
->MediaOptimization::DropFrame
->FrameDropper::Leak
Leak()操作按照编码器输入帧率的频率来执行,每次Leak的大小为targetbps/input_fps,每次Leak时需要判断是否需要累计Fill()方法拆分的块,进而更新drop_ratio_。drop_ratio_的更新遵循下列原则:
当accumulator_ > 1.3f*accumulator_max_,drop_ratio_基数调整为0.8f*,提高丢帧率调整加速度;
当accumulator_ < 1.3f*accumulator_max_,drop_ratio_基数调整为0.9f*,降低丢帧率调整加速度。
FrameDropper::DropFrame
VideoStreamEncoder::EncodeVideoFrame
->VideoSender::AddVideoFrame
->MediaOptimization::DropFrame
->FrameDropper::DropFrame
DropFrame()操作用来判断是否需要将输入到编码器的这一帧丢弃,其利用drop_ratio_来使丢帧率保持一个平滑的变化过程。
当drop_ratio_.filtered() >= 0.5f时,表明连续丢弃多个帧(至少一个帧)
当0.0f < drop_ratio_.filtered() < 0.5f时,表明多个帧才会丢弃一个帧。
三、采集速度大于编码速度掉帧处理
发送端若出现采集帧率大于编码帧率也会主动掉帧,这个掉帧在VideoStreamEncoder::EncodeTask::Run函数处理。
没有使用平滑算法,仅仅判断当前编码器是否空闲,空闲可以正常编码,不会丢帧,否则就会丢弃当前帧。
四、参考
https://www.jianshu.com/p/e2a9740b9877
http://www.enkichen.com/2017/07/29/webrtc-drop-frame/
https://www.freehacker.cn/media/webrtc-frame/