FEC系列(一)webrtc中的媒体保护策略

src/modules/video_coding/media_opt_util.cc

src/modules/video_coding/fec_controller_default.cc

可以参考:https://www.jianshu.com/p/7fb9d640a998

对于webrtc中使用什么样的保护方法,具体的策略是在FecControllerDefault::SetProtectionMethod中。

因此首先考虑FecControllerDefault的基本功能。

在Call中创建视频发送流的时候会创建fec_controller。这里分析采用自上而下的方法。

 

webrtc::VideoSendStream* Call::CreateVideoSendStream(

webrtc::VideoSendStream::Config config,

VideoEncoderConfig encoder_config) {

if (config_.fec_controller_factory) {

RTC_LOG(LS_INFO) << "External FEC Controller will be used.";

}

std::unique_ptr<FecController> fec_controller =

config_.fec_controller_factory

? config_.fec_controller_factory->CreateFecController()

: absl::make_unique<FecControllerDefault>(Clock::GetRealTimeClock());

return CreateVideoSendStream(std::move(config), std::move(encoder_config),

std::move(fec_controller));

}

对SetProtectionMethod的调用是在RtpVideoSender的构造函数中调用的:

 

// Currently, both ULPFEC and FlexFEC use the same FEC rate calculation logic,

// so enable that logic if either of those FEC schemes are enabled.

fec_controller_->SetProtectionMethod(FecEnabled(), NackEnabled());

fec_controller_->SetProtectionCallback(this);

// Signal congestion controller this object is ready for OnPacket* callbacks.

if (fec_controller_->UseLossVectorMask()) {

transport_->RegisterPacketFeedbackObserver(this);

}

从中可以看出,fec controller中的:

VCMProtectionCallback* protection_callback_; 是RtpVideoSender。

 

// RtpVideoSender routes outgoing data to the correct sending RTP module, based

// on the simulcast layer in RTPVideoHeader.

class RtpVideoSender : public RtpVideoSenderInterface,

public OverheadObserver,

public VCMProtectionCallback,

public PacketFeedbackObserver {

根据RtpVideoSender的功能描述,其是要把outgoing data定向到发送RTP的模块,因此在这里进行fec控制也就不足为奇了。那么和RtpSenderVideo的区别在哪里呢?

区别参见:

https://blog.csdn.net/qq_16135205/article/details/101114575

https://blog.csdn.net/qq_16135205/article/details/101605185

https://blog.csdn.net/qq_16135205/article/details/102295738

也就是说具体的功能是RtpSenderVideo来完成的。rtp_video_sender管理各个stream的发送。要使用各个stream的sender video...。

https://blog.csdn.net/lidec/article/details/106717545?spm=1001.2014.3001.5501(要对webrtc整个的编码打包流程有个总体的概览,这个blog是目前为止最好的诠释)

1 FecControllerDefault

在FecControllerDefault中,要依赖于丢包逻辑来进行处理,因此其有一个成员是:

loss_prot_logic_: prot是protection的意思。

在SetEncodingData函数中,主要是设置编码参数,包括宽、高、层数和payload size。主要是丢包保护逻辑要使用这些参数。

 

void FecControllerDefault::SetEncodingData(size_t width,

size_t height,

size_t num_temporal_layers,

size_t max_payload_size) {

CritScope lock(&crit_sect_);

loss_prot_logic_->UpdateFrameSize(width, height);

loss_prot_logic_->UpdateNumLayers(num_temporal_layers);

max_payload_size_ = max_payload_size;

}

函数SetProtectionMethod根据是否启用fec和nack,确定保护的策略:

 

void FecControllerDefault::SetProtectionMethod(bool enable_fec,

bool enable_nack) {

media_optimization::VCMProtectionMethodEnum method(media_optimization::kNone);

if (enable_fec && enable_nack) {

method = media_optimization::kNackFec;

} else if (enable_nack) {

method = media_optimization::kNack;

} else if (enable_fec) {

method = media_optimization::kFec;

}

CritScope lock(&crit_sect_);

loss_prot_logic_->SetMethod(method);

}

注意,各种保护策略之间是有关联性的。

其主要函数有下列两个:

1.1 UpdateWithEncodedData

该函数根据编码的图像的大小和帧的类型,进行参数的更新。在RtpVideoSender::OnEncodedImage中被用到。

 

EncodedImageCallback::Result RtpVideoSender::OnEncodedImage(

const EncodedImage& encoded_image,

const CodecSpecificInfo* codec_specific_info,

const RTPFragmentationHeader* fragmentation) {

fec_controller_->UpdateWithEncodedData(encoded_image.size(),

encoded_image._frameType);

Fec controller是一个接口,把要更新的信息传递到loss protection logic,最终通过loss protection logic完成最终的计算。

其主要功能如下:

 

void FecControllerDefault::UpdateWithEncodedData(

const size_t encoded_image_length,

const FrameType encoded_image_frametype) {

const size_t encoded_length = encoded_image_length;

CritScope lock(&crit_sect_);

if (encoded_length > 0) {

//帧类型,delta_frame表示P帧

const bool delta_frame = encoded_image_frametype != kVideoFrameKey;

if (max_payload_size_ > 0 && encoded_length > 0) {

//因为使用的max_payload_size_(1460字节),因此计算出的该帧的packets数是所有可能性中最小的

const float min_packets_per_frame =

encoded_length / static_cast<float>(max_payload_size_);

if (delta_frame) {

loss_prot_logic_->UpdatePacketsPerFrame(min_packets_per_frame,

clock_->TimeInMilliseconds());

} else {

loss_prot_logic_->UpdatePacketsPerFrameKey(

min_packets_per_frame, clock_->TimeInMilliseconds());

}

}

if (!delta_frame && encoded_length > 0) {

loss_prot_logic_->UpdateKeyFrameSize(static_cast<float>(encoded_length));

}

}

}

从以上代码可以看出,首先判断帧的类型,并计算出该帧的packets数量(最小数量)。然后根据帧类型调用loss_prot_logic_的对应函数(更新了loss_prot_logic_的某些参数)。因此,这里主要就是更新。

1.1 UpdateFecRates

该函数在RtpVideoSender的OnBitrateUpdated中被用到。

 

// Get the encoder target rate. It is the estimated network rate -

// protection overhead.

encoder_target_rate_bps_ = fec_controller_->UpdateFecRates(

payload_bitrate_bps, framerate, fraction_loss, loss_mask_vector_, rtt);

UpdateFecRates的主要工作包括:

(1)传递参数(估计出来的网络带宽,实际帧率,丢包率,rtt)到丢包保护逻辑(FEC和NACK都属于丢包保护逻辑)。

(2)丢包保护逻辑更新参数和内部的方法(由实际的如nack或者fec对象来进行处理),计算出保护的参数和开销;

(3)把保护参数key_frame_params_和delta_frame_params_以回调的形式传递给RtpVideoSender,并得到实际的video,fec和nack速率(基于统计);

(4)计算实际的保护开销(fec+nack/total),并限制开销不高于阈值;

(5)根据开销设定视频编码的码率:网络传输带宽 (1 - 开销)。这样会反馈到编码器。

 

uint32_t FecControllerDefault::UpdateFecRates(

uint32_t estimated_bitrate_bps, //网络目标比特率

int actual_framerate_fps, //实际的帧率

uint8_t fraction_lost, //丢包率

std::vector<bool> loss_mask_vector, //丢包掩码向量

int64_t round_trip_time_ms) { //rtt

float target_bitrate_kbps = //目标比特率

static_cast<float>(estimated_bitrate_bps) / 1000.0f;

// Sanity check.

if (actual_framerate_fps < 1.0) {

actual_framerate_fps = 1.0;

}

FecProtectionParams delta_fec_params;

FecProtectionParams key_fec_params;

{

CritScope lock(&crit_sect_);

//更新信息

loss_prot_logic_->UpdateBitRate(target_bitrate_kbps); //估计出的网络目标比特率

loss_prot_logic_->UpdateRtt(round_trip_time_ms); //rtt

// Update frame rate for the loss protection logic class: frame rate should

// be the actual/sent rate.

loss_prot_logic_->UpdateFrameRate(actual_framerate_fps); //帧率

// Returns the filtered packet loss, used for the protection setting.

// The filtered loss may be the received loss (no filter), or some

// filtered value (average or max window filter).

// Use max window filter for now.

//计算模式

media_optimization::FilterPacketLossMode filter_mode =

media_optimization::kMaxFilter;

//丢包率预测(滤波的方法)Q8表示法

uint8_t packet_loss_enc = loss_prot_logic_->FilteredLoss(

clock_->TimeInMilliseconds(), filter_mode, fraction_lost);

// For now use the filtered loss for computing the robustness settings.

//计算丢包概率 /255

loss_prot_logic_->UpdateFilteredLossPr(packet_loss_enc);

//没有开销,直接返回用作目标编码码率

if (loss_prot_logic_->SelectedType() == media_optimization::kNone) {

return estimated_bitrate_bps;

}

// Update method will compute the robustness settings for the given

// protection method and the overhead cost

// the protection method is set by the user via SetVideoProtection.

//内置的保护方法会进行更新,计算出新的保护参数和开销

loss_prot_logic_->UpdateMethod();

// Get the bit cost of protection method, based on the amount of

// overhead data actually transmitted (including headers) the last

// second.

// Get the FEC code rate for Key frames (set to 0 when NA).

//设置关键帧的FEC保护参数和P帧的保护参数

key_fec_params.fec_rate =

loss_prot_logic_->SelectedMethod()->RequiredProtectionFactorK();

// Get the FEC code rate for Delta frames (set to 0 when NA).

delta_fec_params.fec_rate =

loss_prot_logic_->SelectedMethod()->RequiredProtectionFactorD();

// The RTP module currently requires the same |max_fec_frames| for both

// key and delta frames.

delta_fec_params.max_fec_frames =

loss_prot_logic_->SelectedMethod()->MaxFramesFec();

key_fec_params.max_fec_frames =

loss_prot_logic_->SelectedMethod()->MaxFramesFec();

}

// Set the FEC packet mask type. |kFecMaskBursty| is more effective for

// consecutive losses and little/no packet re-ordering. As we currently

// do not have feedback data on the degree of correlated losses and packet

// re-ordering, we keep default setting to |kFecMaskRandom| for now.

delta_fec_params.fec_mask_type = kFecMaskRandom;

key_fec_params.fec_mask_type = kFecMaskRandom;

// Update protection callback with protection settings.

uint32_t sent_video_rate_bps = 0;

uint32_t sent_nack_rate_bps = 0;

uint32_t sent_fec_rate_bps = 0;

// Rate cost of the protection methods.

float protection_overhead_rate = 0.0f;

// TODO(Marco): Pass FEC protection values per layer.

protection_callback_->ProtectionRequest(

&delta_fec_params, &key_fec_params, &sent_video_rate_bps,

&sent_nack_rate_bps, &sent_fec_rate_bps);

uint32_t sent_total_rate_bps = //当前实际的发送总的速率

sent_video_rate_bps + sent_nack_rate_bps + sent_fec_rate_bps;

// Estimate the overhead costs of the next second as staying the same

// wrt the source bitrate.

if (sent_total_rate_bps > 0) {

protection_overhead_rate = //当前实际的保护开销

static_cast<float>(sent_nack_rate_bps + sent_fec_rate_bps) /

sent_total_rate_bps;

}

// Cap the overhead estimate to a threshold, default is 50%.

protection_overhead_rate =

std::min(protection_overhead_rate, overhead_threshold_);

// Source coding rate: total rate - protection overhead.

return estimated_bitrate_bps * (1.0 - protection_overhead_rate);

}

因此从以上的过程可以看出,核心的功能在Loss protection logic中实现。

2 VCMLossProtectionLogic

在VCMLossProtectionLogic中,的基本的数据类型如下:

2.1 数据成员

 

//当前选择的保护方法

//VCMProtectionMethod为抽象类,有FEC,NACK和NACKFEC方法继承

std::unique_ptr<VCMProtectionMethod> _selectedMethod;

/*

struct VCMProtectionParameters {

VCMProtectionParameters();

int64_t rtt; //rtt

float lossPr; //丢包率(浮点数表示 0.0~1.0)

float bitRate; //比特率

float packetsPerFrame; //每帧的包数

float packetsPerFrameKey; //每个关键帧的包数

float frameRate; //帧率

float keyFrameSize; //关键帧大小

uint8_t fecRateDelta; //P帧的fec rate

uint8_t fecRateKey; //关键帧的fec rate

uint16_t codecWidth; //编码宽度

uint16_t codecHeight; //编码高度

int numLayers; //层数

};

*/

//要计算出这样的一个参数,传递给上层RtpVideoSender

VCMProtectionParameters _currentParameters;

int64_t _rtt;

float _lossPr;

float _bitRate;

float _frameRate;

float _keyFrameSize;

uint8_t _fecRateKey; //关键帧fec rate Q8格式

uint8_t _fecRateDelta; //P帧fec rate Q8格式

int64_t _lastPrUpdateT; //上次的丢包率更新时间?

int64_t _lastPacketPerFrameUpdateT; //上次的每帧包数更新时间?

int64_t _lastPacketPerFrameUpdateTKey; //上次每关键帧包数更新时间?

rtc::ExpFilter _lossPr255; //丢包率计算滤波器

/*

class VCMLossProbabilitySample {

public:

VCMLossProbabilitySample() : lossPr255(0), timeMs(-1) {}

uint8_t lossPr255;

int64_t timeMs;

}; 丢包率样本(时间和丢包率)

*/

//丢包率样本数组 kLossPrHistorySize = 10

VCMLossProbabilitySample _lossPrHistory[kLossPrHistorySize];

uint8_t _shortMaxLossPr255; //丢包率中的最大值?(用于最大预测模式?)

rtc::ExpFilter _packetsPerFrame; //帧包数滤波器

rtc::ExpFilter _packetsPerFrameKey; //关键帧包数滤波器

size_t _codecWidth; //编码宽度

size_t _codecHeight; //编码高度

int _numLayers;

2.2 FilterLoss(丢包率预测)

这里提供了三种预估未来时间窗丢包率的方法,分别是使用当前的丢包率;使用一阶指数平滑算法和使用一段窗口期内的最大丢包率的方法。[思考:根据不同的目的选择不同的方法]

核心函数为FilteredLoss,该函数的主要功能是返回滑动平均和最大窗口滤波器来得到滤波后的丢包率,范围为[0,255]。返回的值依赖于参数filter_mode。输入lossPr255是接收到的丢包率。

 

uint8_t VCMLossProtectionLogic::FilteredLoss(int64_t nowMs,

FilterPacketLossMode filter_mode,

uint8_t lossPr255) {

// Update the max window filter.

UpdateMaxLossHistory(lossPr255, nowMs);

// Update the recursive average filter.

_lossPr255.Apply(rtc::saturated_cast<float>(nowMs - _lastPrUpdateT),

rtc::saturated_cast<float>(lossPr255));

_lastPrUpdateT = nowMs;

// Filtered loss: default is received loss (no filtering).

//缺省为接收到的丢包率

uint8_t filtered_loss = lossPr255;

switch (filter_mode) {

case kNoFilter: //使用当前丢包率

break;

case kAvgFilter:

//滑动平均滤波器的结果

filtered_loss = rtc::saturated_cast<uint8_t>(_lossPr255.filtered() + 0.5);

break;

case kMaxFilter:

//最大窗口滤波器的结果

filtered_loss = MaxFilteredLossPr(nowMs);

break;

}

return filtered_loss;

}

2.2.1 一阶指数平滑算法

$$S_{t+1} = a \cdot x_{t} + (1-a) \cdot S{t}$$

其中$$S_{t+1}$$表示将要预测的趋势;$$a$$表示平滑系数;$$S{t}$$表示上一个时刻预测的值。

具体的实现代码:

可以看得出来,平滑系数可以为定值(当exp=1.0的时候),或者根据求幂的方法来计算。这个和新样本距离上次的间隔时间有关系,可以更为合理的得到权重。

 

float ExpFilter::Apply(float exp, float sample) {

if (filtered_ == kValueUndefined) {

// Initialize filtered value.

filtered_ = sample;

} else if (exp == 1.0) {

filtered_ = alpha_ * filtered_ + (1 - alpha_) * sample;

} else {

float alpha = pow(alpha_, exp);

filtered_ = alpha * filtered_ + (1 - alpha) * sample;

}

if (max_ != kValueUndefined && filtered_ > max_) {

filtered_ = max_;

}

return filtered_;

}

2.2.2 最大丢包率滤波法

主要包括两个步骤,分别是丢包率入队和选取窗口期内的最大值。

 

void VCMLossProtectionLogic::UpdateMaxLossHistory(uint8_t lossPr255,

int64_t now) {

if (_lossPrHistory[0].timeMs >= 0 &&

now - _lossPrHistory[0].timeMs < kLossPrShortFilterWinMs) {

if (lossPr255 > _shortMaxLossPr255) {

_shortMaxLossPr255 = lossPr255; //最大丢包率

}

} else {

// Only add a new value to the history once a second

if (_lossPrHistory[0].timeMs == -1) {

// First, no shift

_shortMaxLossPr255 = lossPr255;

} else {

// Shift

for (int32_t i = (kLossPrHistorySize - 2); i >= 0; i--) {

_lossPrHistory[i + 1].lossPr255 = _lossPrHistory[i].lossPr255;

_lossPrHistory[i + 1].timeMs = _lossPrHistory[i].timeMs;

}

}

if (_shortMaxLossPr255 == 0) {

_shortMaxLossPr255 = lossPr255;

}

_lossPrHistory[0].lossPr255 = _shortMaxLossPr255;

_lossPrHistory[0].timeMs = now;

_shortMaxLossPr255 = 0;

}

}

基本的思路是:把时间分为一个一个的时间窗口(1000ms)长度。

第一个时间窗口中比如有10个值,其中有一个最大值,则把该样本存入lossPrHistory[0]

然后依次类推...。因此这就决定了更新历史的方法如下:

样本数组_lossPrHistory的长度为10。这里共分为三种情况:

(1)样本数组中没有数据,也就是_lossPrHistory[0].timeMs == -1的情况

此时会设置_shortMaxLossPr255 = lossPr255。并设置样本数组中[0]的内容为:

 

_lossPrHistory[0].lossPr255 = _shortMaxLossPr255;

_lossPrHistory[0].timeMs = now;

(2)新样本和第0个数组中的时间之差在时间窗口范围之内

这说明第0个的值最后还没有最后确定,这样如果新样本>_shortMaxLossPr255。则更新_shortMaxLossPr255 = lossPr255;

同样设置:

 

_lossPrHistory[0].lossPr255 = _shortMaxLossPr255;

_lossPrHistory[0].timeMs = now;

注意这里可以发现,样本的timeMs的设置总是最新收到的样本的时间。而lossPr255则是这个时间窗口内的最大值。也就是timeMs和lossPr255不一定是和输入的样本一一对应的。

(3)否则进行移动操作,保持第0个是最新的

 

for (int32_t i = (kLossPrHistorySize - 2); i >= 0; i--) {

_lossPrHistory[i + 1].lossPr255 = _lossPrHistory[i].lossPr255;

_lossPrHistory[i + 1].timeMs = _lossPrHistory[i].timeMs;

}

 

_lossPrHistory[0].lossPr255 = _shortMaxLossPr255;

_lossPrHistory[0].timeMs = now;

在这种情况下,当前样本是最新的时间窗口的第一个样本。而上面的情况,则是已经存在样本了,但对值进行更新。

具体的最大滤波器计算方法则是:

 

uint8_t VCMLossProtectionLogic::MaxFilteredLossPr(int64_t nowMs) const {

uint8_t maxFound = _shortMaxLossPr255;

if (_lossPrHistory[0].timeMs == -1) {

return maxFound;

}

for (int32_t i = 0; i < kLossPrHistorySize; i++) {

if (_lossPrHistory[i].timeMs == -1) {

break;

}

//样本无效,过期了

if (nowMs - _lossPrHistory[i].timeMs >

kLossPrHistorySize * kLossPrShortFilterWinMs) {

// This sample (and all samples after this) is too old

break;

}

//判断是否是更大者

if (_lossPrHistory[i].lossPr255 > maxFound) {

// This sample is the largest one this far into the history

maxFound = _lossPrHistory[i].lossPr255;

}

}

return maxFound;

}

2.3 UpdateMethod

本逻辑更新计算的所有信息会传递到具体的方法策略中,用来计算具体的保护参数。这里currentParameters既是输入,也是输出,里面某些参数会被更新。

 

bool VCMLossProtectionLogic::UpdateMethod() {

if (!_selectedMethod)

return false;

_currentParameters.rtt = _rtt;

_currentParameters.lossPr = _lossPr;

_currentParameters.bitRate = _bitRate;

_currentParameters.frameRate = _frameRate; // rename actual frame rate?

_currentParameters.keyFrameSize = _keyFrameSize;

_currentParameters.fecRateDelta = _fecRateDelta;

_currentParameters.fecRateKey = _fecRateKey;

_currentParameters.packetsPerFrame = _packetsPerFrame.filtered();

_currentParameters.packetsPerFrameKey = _packetsPerFrameKey.filtered();

_currentParameters.codecWidth = _codecWidth;

_currentParameters.codecHeight = _codecHeight;

_currentParameters.numLayers = _numLayers;

return _selectedMethod->UpdateParameters(&_currentParameters);

}

因此需要知道具体的保护方法。保护方法有三种分别是:VCMNackFecMethod,VCMNackMethod和VCMFecMethod。核心的方法是UpdateParameters。

3 VCMNackMethod

 

bool VCMNackMethod::UpdateParameters(

const VCMProtectionParameters* parameters) {

// Compute the effective packet loss

EffectivePacketLoss(parameters);

// nackCost = (bitRate - nackCost) * (lossPr)

return true;

}

 

bool VCMNackMethod::EffectivePacketLoss(

const VCMProtectionParameters* parameter) {

// Effective Packet Loss, NA in current version.

_effectivePacketLoss = 0;

return true;

}

可以看出来,这里对parameter没有做任何计算。因为这里本身就是只启用Nack。

4 VCMFecMethod

 

bool VCMFecMethod::UpdateParameters(const VCMProtectionParameters* parameters) {

// Compute the protection factor

ProtectionFactor(parameters);

// Compute the effective packet loss

EffectivePacketLoss(parameters);

// Protection/fec rates obtained above is defined relative to total number

// of packets (total rate: source+fec) FEC in RTP module assumes protection

// factor is defined relative to source number of packets so we should

// convert the factor to reduce mismatch between mediaOpt suggested rate and

// the actual rate

_protectionFactorK = ConvertFECRate(_protectionFactorK);

_protectionFactorD = ConvertFECRate(_protectionFactorD);

return true;

}

这里是计算保护因子以及对有效丢包率进行更新,然后对保护因子进行一个转换。因为前面计算出来的保护因子是相对于总的packets的数量的(也就是media packets + fec packets)。而在RTP中的保护因子是相对于media packets的,因此需要进行转换。

注意在M74版本中,有效丢包率没有使用。

 

bool VCMFecMethod::EffectivePacketLoss(

const VCMProtectionParameters* parameters) {

// Effective packet loss to encoder is based on RPL (residual packet loss)

// this is a soft setting based on degree of FEC protection

// RPL = received/input packet loss - average_FEC_recovery

// note: received/input packet loss may be filtered based on FilteredLoss

// Effective Packet Loss, NA in current version.

_effectivePacketLoss = 0;

return true;

}

4.1 ProtectionFactor(保护因子计算)

保护因子的计算过程很是复杂。其基本的方式是借助于表查找。

4.1.1 FecRateTable

位于src/modules/video_coding/fec_rate_table.h

该表示用于Protection factor(code rate) of delta frames。也就是说仅用于P帧的,在进行XOR FEC的时候使用。

输入为丢包率和有效rate(bits/frame)。也就是说:除了丢包率外,需要计算出每帧的比特率。

输出为一个数组kFecRateTable[k]。其中$$k=rate_i*129 + loss_j$$,$$loss_j=0,1,...128$$而$$rate_i$$则是在某个范围内变化。在考虑把该静态表用一个闭合形式的表达式替代。

 

static const int kFecRateTableSize = 6450;

static const unsigned char kFecRateTable[kFecRateTableSize] = {

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,

11, 11, 11, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,

39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,

39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,

因此剩下的问题就是要找到对应的loss和rate了。

首先如果丢包率为0,则不需要进行任何保护。

 

uint8_t packetLoss = rtc::saturated_cast<uint8_t>(255 * parameters->lossPr);

if (packetLoss == 0) {

_protectionFactorK = 0;

_protectionFactorD = 0;

return true;

}

4.1.2 DeltaFrame保护因子计算

 

// Parameters for FEC setting:

// first partition size, thresholds, table pars, spatial resoln fac.

// First partition protection: ~ 20%

uint8_t firstPartitionProt = rtc::saturated_cast<uint8_t>(255 * 0.20);

// Minimum protection level needed to generate one FEC packet for one

// source packet/frame (in RTP sender)

uint8_t minProtLevelFec = 85;

// Threshold on packetLoss and bitRrate/frameRate (=average #packets),

// above which we allocate protection to cover at least first partition.

//当丢包率>lossThr, 并且bitrate/framerate = 平均包数

//当丢包率和平均包数>下列阈值的时候,至少为第一个划分分配保护

uint8_t lossThr = 0;

uint8_t packetNumThr = 1;

定义了第一个划分的保护因子为20%,如果丢包率>lossThr,平均包数>packetNumThr,则至少需要提供第一划分保护能力。

 

// Parameters for range of rate index of table.

const uint8_t ratePar1 = 5;

const uint8_t ratePar2 = 49;

索引表中查找时的参数。

下面计算bitrate/frame

(1) 计算分辨率参考比值

 

//首先,计算一个分辨率参考比这个参考比是基于一个固定的分辨率704*576

//宽度和高度相乘之后的比值

float spatialSizeToRef = rtc::saturated_cast<float>(parameters->codecWidth *

parameters->codecHeight) /

(rtc::saturated_cast<float>(704 * 576));

(2)计算码率增益系数

 

//然后根据分辨率参考比计算一个估算码率增益系数

const float resolnFac = 1.0 / powf(spatialSizeToRef, 0.3f);

(3)计算单帧码率 $$rate_i$$计算

 

//估算单帧码率和单帧包数,单帧码率就是用来查表的,但是不是直接用,下面还要再变换一下。

const int bitRatePerFrame = BitsPerFrame(parameters);

// Average number of packets per frame (source and fec):

//每帧平均的packets数量,计算方法为 每帧的比特数/最大负载size

const uint8_t avgTotPackets = rtc::saturated_cast<uint8_t>(

1.5f + rtc::saturated_cast<float>(bitRatePerFrame) * 1000.0f /

rtc::saturated_cast<float>(8.0 * _maxPayloadSize));

// FEC rate parameters: for P and I frame

uint8_t codeRateDelta = 0;

uint8_t codeRateKey = 0;

// Get index for table: the FEC protection depends on an effective rate.

// The range on the rate index corresponds to rates (bps)

// from ~200k to ~8000k, for 30fps

//计算增益的单帧码率,增益的单帧码率的计算用到了前面计算的估算码率增益系数,

//这个码率除以5以后就是上面FEC冗余度表中提到rate_i,再结合丢包率, 就可以查表了。

//乘以增益系数之后的单帧比特率

const uint16_t effRateFecTable =

rtc::saturated_cast<uint16_t>(resolnFac * bitRatePerFrame);

//得到查找表索引(需要先说明表结构) ratePar1 = 5, ratePar2 = 49 得到了rate(i)

uint8_t rateIndexTable = rtc::saturated_cast<uint8_t>(

VCM_MAX(VCM_MIN((effRateFecTable - ratePar1) / ratePar1, ratePar2), 0));

(4)丢包率计算$$loss_j$$

 

// Restrict packet loss range to 50:

// current tables defined only up to 50%

//限制丢包率在最大为128 < 50%的丢包率

if (packetLoss >= kPacketLossMax) {

packetLoss = kPacketLossMax - 1;

}

(5)k值计算并查表,得到保护因子

 

//计算k

uint16_t indexTable = rateIndexTable * kPacketLossMax + packetLoss;

// Check on table index

RTC_DCHECK_LT(indexTable, kFecRateTableSize);

// Protection factor for P frame

//表查询

codeRateDelta = kFecRateTable[indexTable];

(6) 限制保护因子范围

 

if (packetLoss > lossThr && avgTotPackets > packetNumThr) {

// Set a minimum based on first partition size.

if (codeRateDelta < firstPartitionProt) {

codeRateDelta = firstPartitionProt; //至少是20%的保护率?

}

}

// Check limit on amount of protection for P frame; 50% is max.

//保护率不能超过50%

if (codeRateDelta >= kPacketLossMax) {

codeRateDelta = kPacketLossMax - 1;

}

4.1.3 KeyFrame保护因子计算

上面算的codeRateDelta是P帧的冗余度, I帧的冗余度计算方式略有不同,主要在于计算增益的单帧码率的时候,是在P帧的增益的单帧码率的基础上再次增益,增益系数为I帧的单帧包数除以P帧的单帧报数,但最小增益为2,在BoostCodeRateKey()函数中完成计算

(1)增益系数计算

 

const uint8_t packetFrameDelta =

rtc::saturated_cast<uint8_t>(0.5 + parameters->packetsPerFrame);

const uint8_t packetFrameKey =

rtc::saturated_cast<uint8_t>(0.5 + parameters->packetsPerFrameKey);

const uint8_t boostKey = BoostCodeRateKey(packetFrameDelta, packetFrameKey);

 

uint8_t VCMFecMethod::BoostCodeRateKey(uint8_t packetFrameDelta,

uint8_t packetFrameKey) const {

uint8_t boostRateKey = 2;

// Default: ratio scales the FEC protection up for I frames

uint8_t ratio = 1;

//计算比率

if (packetFrameDelta > 0) {

ratio = (int8_t)(packetFrameKey / packetFrameDelta);

}

//不小于2

ratio = VCM_MAX(boostRateKey, ratio);

return ratio;

}

(2)k值计算,查表

 

rateIndexTable = rtc::saturated_cast<uint8_t>(VCM_MAX(

VCM_MIN(1 + (boostKey * effRateFecTable - ratePar1) / ratePar1, ratePar2),

0));

//K值计算

uint16_t indexTableKey = rateIndexTable * kPacketLossMax + packetLoss;

indexTableKey = VCM_MIN(indexTableKey, kFecRateTableSize);

// Check on table index

assert(indexTableKey < kFecRateTableSize);

// Protection factor for I frame

codeRateKey = kFecRateTable[indexTableKey];

(3)范围检查

 

// Boosting for Key frame.

int boostKeyProt = _scaleProtKey * codeRateDelta;

if (boostKeyProt >= kPacketLossMax) {

boostKeyProt = kPacketLossMax - 1;

}

// Make sure I frame protection is at least larger than P frame protection,

// and at least as high as filtered packet loss.

codeRateKey = rtc::saturated_cast<uint8_t>(

VCM_MAX(packetLoss, VCM_MAX(boostKeyProt, codeRateKey)));

// Check limit on amount of protection for I frame: 50% is max.

if (codeRateKey >= kPacketLossMax) {

codeRateKey = kPacketLossMax - 1;

}

4.1.4 保护因子校正

一般来说,FEC cost的估计值和在RTP模块中的实际值很难匹配。尤其是在low rates(#source packets很小的)的情况下。可能会产生非零保护因子得到0个FEC packets的情况。因此使用校正因子_corrFecCost来尝试校正,至少在low rates(small #packets)和low protection levels的时候来校正。

首先,根据当前计算出来的保护因子来估计产生的FEC packets数量:

 

float numPacketsFl =

1.0f + (rtc::saturated_cast<float>(bitRatePerFrame) * 1000.0 /

rtc::saturated_cast<float>(8.0 * _maxPayloadSize) +

0.5);

//对产生的fec packet数量进行估计

const float estNumFecGen =

0.5f +

rtc::saturated_cast<float>(_protectionFactorD * numPacketsFl / 255.0f);

减少cost factor(对FEC和混合方法会导致降低FEC开销)而不是protectionFactor。[只是一个思路,好像没有真正的启用]

 

_corrFecCost = 1.0f;

if (estNumFecGen < 1.1f && _protectionFactorD < minProtLevelFec) {

_corrFecCost = 0.5f;

}

if (estNumFecGen < 0.9f && _protectionFactorD < minProtLevelFec) {

_corrFecCost = 0.0f;

}

4.2 保护因子的转换

 

uint8_t VCMFecMethod::ConvertFECRate(uint8_t codeRateRTP) const {

return rtc::saturated_cast<uint8_t>(

VCM_MIN(255, (0.5 + 255.0 * codeRateRTP /

rtc::saturated_cast<float>(255 - codeRateRTP))));

}

5 VCMNackFecMethod

其UpdateParameters主要工作包括:

 

bool VCMNackFecMethod::UpdateParameters(

const VCMProtectionParameters* parameters) {

ProtectionFactor(parameters);

EffectivePacketLoss(parameters);

_maxFramesFec = ComputeMaxFramesFec(parameters);

if (BitRateTooLowForFec(parameters)) {

_protectionFactorK = 0;

_protectionFactorD = 0;

}

// Protection/fec rates obtained above are defined relative to total number

// of packets (total rate: source + fec) FEC in RTP module assumes

// protection factor is defined relative to source number of packets so we

// should convert the factor to reduce mismatch between mediaOpt's rate and

// the actual one

_protectionFactorK = VCMFecMethod::ConvertFECRate(_protectionFactorK);

_protectionFactorD = VCMFecMethod::ConvertFECRate(_protectionFactorD);

return true;

}

5.1 ProtectionFactor(保护因子计算)

分为几种情况:

(1)如果rtt < kLowRttNackMs = 20ms 仅使用Nack(设置FEC rate = 0)

(2)如果rtt > _highRttNackMs = 500 则仅使用FEC

(3)中间rtt,FEC delta保护因子基于RTT动态调整,仅nack在FEC恢复之后(认为丢失的packet),FEC delta protection factor基于RTT进行调整(好像这个取消了)。

实际上除了rtt < 20ms的情况,其余和VCMFecMethod一模一样。

 

bool VCMNackFecMethod::ProtectionFactor(

const VCMProtectionParameters* parameters) {

// Hybrid Nack FEC has three operational modes:

// 1. Low RTT (below kLowRttNackMs) - Nack only: Set FEC rate

// (_protectionFactorD) to zero. -1 means no FEC.

// 2. High RTT (above _highRttNackMs) - FEC Only: Keep FEC factors.

// -1 means always allow NACK.

// 3. Medium RTT values - Hybrid mode: We will only nack the

// residual following the decoding of the FEC (refer to JB logic). FEC

// delta protection factor will be adjusted based on the RTT.

// Otherwise: we count on FEC; if the RTT is below a threshold, then we

// nack the residual, based on a decision made in the JB.

// Compute the protection factors

//首先使用VCMFecMethod的方法计算出保护因子

VCMFecMethod::ProtectionFactor(parameters);

//rtt太小,不使用FEC保护

if (_lowRttNackMs == -1 || parameters->rtt < _lowRttNackMs) {

_protectionFactorD = 0;

VCMFecMethod::UpdateProtectionFactorD(_protectionFactorD);

// When in Hybrid mode (RTT range), adjust FEC rates based on the

// RTT (NACK effectiveness) - adjustment factor is in the range [0,1].

} else if (_highRttNackMs == -1 || parameters->rtt < _highRttNackMs) {

// TODO(mikhal): Disabling adjustment temporarily.

// uint16_t rttIndex = (uint16_t) parameters->rtt;

float adjustRtt = 1.0f; // (float)VCMNackFecTable[rttIndex] / 100.0f;

// Adjust FEC with NACK on (for delta frame only)

// table depends on RTT relative to rttMax (NACK Threshold)

//不再基于rtt对保护因子进行调整(是不是测试后发现不行?)

_protectionFactorD = rtc::saturated_cast<uint8_t>(

adjustRtt * rtc::saturated_cast<float>(_protectionFactorD));

// update FEC rates after applying adjustment

VCMFecMethod::UpdateProtectionFactorD(_protectionFactorD);

}

return true;

}

5.2 ComputeMaxFramesFec(最大保护帧数量计算)

 

int VCMNackFecMethod::ComputeMaxFramesFec(

const VCMProtectionParameters* parameters) {

if (parameters->numLayers > 2) {

// For more than 2 temporal layers we will only have FEC on the base layer,

// and the base layers will be pretty far apart. Therefore we force one

// frame FEC.

return 1;

}

// We set the max number of frames to base the FEC on so that on average

// we will have complete frames in one RTT. Note that this is an upper

// bound, and that the actual number of frames used for FEC is decided by the

// RTP module based on the actual number of packets and the protection factor.

//主要目的 一个RTT一个complete frames

//计算的是一个上界

float base_layer_framerate =

parameters->frameRate /

rtc::saturated_cast<float>(1 << (parameters->numLayers - 1));

//一个rtt完成的帧数,为什么要乘以2呢?

int max_frames_fec = std::max(

rtc::saturated_cast<int>(

2.0f * base_layer_framerate * parameters->rtt / 1000.0f + 0.5f),

1);

// |kUpperLimitFramesFec| is the upper limit on how many frames we

// allow any FEC to be based on.

//不能超过6帧

if (max_frames_fec > kUpperLimitFramesFec) {

max_frames_fec = kUpperLimitFramesFec;

}

return max_frames_fec;

}

5.3 BitRateTooLowForFec(低码率处理)

使用每帧的字节数的阈值,以及其他因素来决定是否关闭FEC。

 

//如果bitrate太低,则关闭fec,不管丢包率有多大,依赖于分辨率

bool VCMNackFecMethod::BitRateTooLowForFec(

const VCMProtectionParameters* parameters) {

// Bitrate below which we turn off FEC, regardless of reported packet loss.

// The condition should depend on resolution and content. For now, use

// threshold on bytes per frame, with some effect for the frame size.

// The condition for turning off FEC is also based on other factors,

// such as |_numLayers|, |_maxFramesFec|, and |_rtt|.

int estimate_bytes_per_frame = 1000 * BitsPerFrame(parameters) / 8;

int max_bytes_per_frame = kMaxBytesPerFrameForFec; //700

int num_pixels = parameters->codecWidth * parameters->codecHeight;

if (num_pixels <= 352 * 288) {

max_bytes_per_frame = kMaxBytesPerFrameForFecLow;

} else if (num_pixels > 640 * 480) {

max_bytes_per_frame = kMaxBytesPerFrameForFecHigh;

}

// TODO(marpan): add condition based on maximum frames used for FEC,

// and expand condition based on frame size.

// Max round trip time threshold in ms.

const int64_t kMaxRttTurnOffFec = 200;

//estimate_bytes_per_frame小, rtt小

if (estimate_bytes_per_frame < max_bytes_per_frame &&

parameters->numLayers < 3 && parameters->rtt < kMaxRttTurnOffFec) {

return true;

}

return false;

}

 

// Thresholds values for the bytes/frame and round trip time, below which we

// may turn off FEC, depending on |_numLayers| and |_maxFramesFec|.

// Max bytes/frame for VGA, corresponds to ~140k at 25fps.

enum { kMaxBytesPerFrameForFec = 700 };

// Max bytes/frame for CIF and lower: corresponds to ~80k at 25fps.

enum { kMaxBytesPerFrameForFecLow = 400 };

// Max bytes/frame for frame size larger than VGA, ~200k at 25fps.

enum { kMaxBytesPerFrameForFecHigh = 1000 };

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值