前言
上一篇文章的丟幀是依據(jù)編碼后的碼率和目標(biāo)碼率來決定丟幀,
而本文介紹的丟幀依據(jù)是目標(biāo)幀率酿箭。
由此可對丟幀策略分類如下:
- 編碼后的碼率和目標(biāo)碼率來決定丟幀
- 目標(biāo)幀率決定丟幀
調(diào)用流程
前處理模塊調(diào)用丟幀處理
ViEEncoder::DeliverFrame
->int32_t VideoProcessingModuleImpl::PreprocessFrame
->int32_t VPMFramePreprocessor::PreprocessFrame
PreprocessFrame前處理先進行丟幀處理,然后進行重采樣處理葡兑。丟幀處理中没咙,每來一幀先進行輸入幀率統(tǒng)計,然后進行丟幀判斷以蕴。輸入幀率統(tǒng)計和上一篇碼率決定丟幀輸入丟幀統(tǒng)計算法一樣糙麦。
int32_t VPMFramePreprocessor::PreprocessFrame(const I420VideoFrame& frame,
I420VideoFrame** processed_frame) {
if (frame.IsZeroSize()) {
return VPM_PARAMETER_ERROR;
}
vd_->UpdateIncomingframe_rate(); //更新輸入幀率
if (vd_->DropFrame()) {//判斷是否丟幀
return 1; // drop 1 frame
}
// Resizing incoming frame if needed. Otherwise, remains NULL.
// We are not allowed to resample the input frame (must make a copy of it).
*processed_frame = NULL;
if (spatial_resampler_->ApplyResample(frame.width(), frame.height())) {
int32_t ret = spatial_resampler_->ResampleFrame(frame, &resampled_frame_);
if (ret != VPM_OK) return ret;
*processed_frame = &resampled_frame_;
}
// Perform content analysis on the frame to be encoded.
if (enable_ca_) {
// Compute new metrics every |kSkipFramesCA| frames, starting with
// the first frame.
if (frame_cnt_ % kSkipFrameCA == 0) {
if (*processed_frame == NULL) {
content_metrics_ = ca_->ComputeContentMetrics(frame);
} else {
content_metrics_ = ca_->ComputeContentMetrics(resampled_frame_);
}
}
++frame_cnt_;
}
return VPM_OK;
}```
##輸入幀率統(tǒng)計
每來一幀都將此時的時間作為樣本,記錄在滑動窗口為kFrameCountHistory_size的incoming_frame_times_數(shù)組中丛肮。并進行ProcessIncomingframe_rate輸入幀率的計算赡磅。
void VPMVideoDecimator::UpdateIncomingframe_rate() {
int64_t now = TickTime::MillisecondTimestamp();
if (incoming_frame_times_[0] == 0) {
// First no shift.
} else {
// Shift.
for (int i = kFrameCountHistory_size - 2; i >= 0; i--) {
incoming_frame_times_[i+1] = incoming_frame_times_[i];
}
}
incoming_frame_times_[0] = now;
ProcessIncomingframe_rate(now);
}```
統(tǒng)計最多不超過2秒鐘的樣本,計算輸入幀率腾供。
void VPMVideoDecimator::ProcessIncomingframe_rate(int64_t now) {
int32_t num = 0;
int32_t nrOfFrames = 0;
for (num = 1; num < (kFrameCountHistory_size - 1); num++) {
// Don't use data older than 2sec.
if (incoming_frame_times_[num] <= 0 ||
now - incoming_frame_times_[num] > kFrameHistoryWindowMs) {
break;
} else {
nrOfFrames++;
}
}
if (num > 1) {
int64_t diff = now - incoming_frame_times_[num-1];
incoming_frame_rate_ = 1.0;
if (diff > 0) {
incoming_frame_rate_ = nrOfFrames * 1000.0f / static_cast<float>(diff);
}
} else {
incoming_frame_rate_ = static_cast<float>(nrOfFrames);
}
}```
##目標(biāo)幀率丟幀核心
一仆邓、當(dāng)2 * overshoot < (int32_t) incomingframe_rate,即輸入幀率大于目標(biāo)幀率伴鳖,小于2倍目標(biāo)幀率的情況下节值。具體細節(jié)看注釋,這里假設(shè)incomingframe_rate=20榜聂,target_frame_rate_=15搞疗。具體就是實現(xiàn)均勻丟幀。
bool VPMVideoDecimator::DropFrame() {
if (!enable_temporal_decimation_) return false;
if (incoming_frame_rate_ <= 0) return false;
const uint32_t incomingframe_rate =
static_cast<uint32_t>(incoming_frame_rate_ + 0.5f);
if (target_frame_rate_ == 0) return true;
bool drop = false;
if (incomingframe_rate > target_frame_rate_) {//輸入幀率大于目標(biāo)幀率
int32_t overshoot =
overshoot_modifier_ + (incomingframe_rate - target_frame_rate_);//20-15=5须肆,超出幀率
if (overshoot < 0) {
overshoot = 0;
overshoot_modifier_ = 0;
}
if (overshoot && 2 * overshoot < (int32_t) incomingframe_rate) {//2*5<20,即20<2*15匿乃,輸入幀率小于2倍目標(biāo)幀率
if (drop_count_) { // Just got here so drop to be sure.
drop_count_ = 0;
return true;
}
const uint32_t dropVar = incomingframe_rate / overshoot;//20/5=4,丟幀比率
if (keep_count_ >= dropVar) {//均勻丟幀
drop = true;
overshoot_modifier_ = -((int32_t) incomingframe_rate % overshoot) / 3;//-(20%5)/3=0豌汇,修正overshoot
keep_count_ = 1;//重置保留幀數(shù)
} else {
keep_count_++;//保留幀數(shù)
}
} else {
keep_count_ = 0;
const uint32_t dropVar = overshoot / target_frame_rate_;
if (drop_count_ < dropVar) {
drop = true;
drop_count_++;
} else {
overshoot_modifier_ = overshoot % target_frame_rate_;
drop = false;
drop_count_ = 0;
}
}
}
return drop;
}```
二幢炸、當(dāng)2 * overshoot >=(int32_t) incomingframe_rate時,即輸入幀率大于等于2倍目標(biāo)幀率拒贱,此時宛徊,均勻丟幀每次丟1幀以上,具體丟幀方法和上一部分略有區(qū)別逻澳。
bool VPMVideoDecimator::DropFrame() {
if (!enable_temporal_decimation_) return false;
if (incoming_frame_rate_ <= 0) return false;
const uint32_t incomingframe_rate =
static_cast<uint32_t>(incoming_frame_rate_ + 0.5f);
if (target_frame_rate_ == 0) return true;
bool drop = false;
if (incomingframe_rate > target_frame_rate_) {//輸入幀率大于目標(biāo)幀率
int32_t overshoot =
overshoot_modifier_ + (incomingframe_rate - target_frame_rate_); //30-10=20闸天,超出幀率
if (overshoot < 0) {
overshoot = 0;
overshoot_modifier_ = 0;
}
if (overshoot && 2 * overshoot < (int32_t) incomingframe_rate) { //2*20>30,即30>2*10斜做,輸入幀率大于2倍目標(biāo)幀率
if (drop_count_) { // Just got here so drop to be sure.
drop_count_ = 0;
return true;
}
const uint32_t dropVar = incomingframe_rate / overshoot;
if (keep_count_ >= dropVar) {
drop = true;
overshoot_modifier_ = -((int32_t) incomingframe_rate % overshoot) / 3;
keep_count_ = 1;
} else {
keep_count_++;
}
} else {
keep_count_ = 0;
const uint32_t dropVar = overshoot / target_frame_rate_; //20/10=2苞氮,丟幀比率
if (drop_count_ < dropVar) {//一次丟1幀以上
drop = true;
drop_count_++;
} else {
overshoot_modifier_ = overshoot % target_frame_rate_; //20%10=0,overshoot修正
drop = false;
drop_count_ = 0;
}
}
}
return drop;
}```