## 前言
上一篇文章的丟幀是依據編碼后的碼率和目標碼率來決定丟幀,
> [http://www.jianshu.com/p/e2a9740b9877](https://www.jianshu.com/p/e2a9740b9877)
而本文介紹的丟幀依據是目標幀率。
##### 由此可對丟幀策略分類如下:
* 編碼后的碼率和目標碼率來決定丟幀
* 目標幀率決定丟幀
## 調用流程
前處理模塊調用丟幀處理
~~~cpp
ViEEncoder::DeliverFrame
->int32_t VideoProcessingModuleImpl::PreprocessFrame
->int32_t VPMFramePreprocessor::PreprocessFrame
~~~
PreprocessFrame前處理先進行丟幀處理,然后進行重采樣處理。丟幀處理中,每來一幀先進行輸入幀率統計,然后進行丟幀判斷。輸入幀率統計和上一篇碼率決定丟幀輸入丟幀統計算法一樣。
~~~php
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;
}```
##輸入幀率統計
每來一幀都將此時的時間作為樣本,記錄在滑動窗口為kFrameCountHistory_size的incoming_frame_times_數組中。并進行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);
}```
統計最多不超過2秒鐘的樣本,計算輸入幀率。
~~~cpp
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);
}
}```
##目標幀率丟幀核心
一、當2 * overshoot < (int32_t) incomingframe_rate,即輸入幀率大于目標幀率,小于2倍目標幀率的情況下。具體細節看注釋,這里假設incomingframe_rate=20,target_frame_rate_=15。具體就是實現均勻丟幀。
~~~
bool VPMVideoDecimator::DropFrame() {
if (!enable\_temporal\_decimation\_) return false;
if (incoming\_frame\_rate\_ <= 0) return false;
const uint32\_t incomingframe\_rate =
static\_cast(incoming\_frame\_rate\_ + 0.5f);
if (target\_frame\_rate\_ == 0) return true;
bool drop = false;
if (incomingframe\_rate > target\_frame\_rate\_) {//輸入幀率大于目標幀率
int32\_t overshoot =
overshoot\_modifier\_ + (incomingframe\_rate - target\_frame\_rate\_);//20-15=5,超出幀率
if (overshoot < 0) {
overshoot = 0;
overshoot\_modifier\_ = 0;
}
~~~cpp
if (overshoot && 2 * overshoot < (int32_t) incomingframe_rate) {//2*5<20,即20<2*15,輸入幀率小于2倍目標幀率
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;//重置保留幀數
} else {
keep_count_++;//保留幀數
}
} 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;
}```
二、當2 \* overshoot >=(int32\_t) incomingframe\_rate時,即輸入幀率大于等于2倍目標幀率,此時,均勻丟幀每次丟1幀以上,具體丟幀方法和上一部分略有區別。
~~~cpp
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_) {//輸入幀率大于目標幀率
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倍目標幀率
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;
}
~~~
- 序言
- 編解碼
- H264
- HEVC碼流解析
- H264編碼原理
- 多媒體封裝
- MP4
- 學好 MP4,讓直播更給力
- AAC
- FLV
- 流媒體協議
- RTSP
- RTCP
- RTP
- H265 RTP封包筆記
- SDP
- RTMP
- RTMP URL
- rtmp url基礎
- webrtc
- 編譯
- 最簡單的編譯webrtc方案
- Webrtc音視頻會議之Webrtc“不求甚解”
- Webrtc音視頻會議之Mesh/MCU/SFU三種架構
- 音頻傳輸之Jitter Buffer設計與實現
- Janus
- Webrtc音視頻會議之Janus編譯
- Webrtc音視頻會議之Janus源碼架構設計
- webrtc服務器-janus房間管理
- 源碼分析
- WebRTC視頻JitterBuffer詳解
- 走讀Webrtc 中的視頻JitterBuffer(一)
- 走讀webrtc 中的視頻JitterBuffer(二)
- webrtc視頻幀率控制算法機制
- 目標碼率丟幀-1
- 目標幀率丟幀-2
- 29 如何使用Medooze 實現多方視頻會議
- FFmpeg
- FFmpeg編譯
- Window10下編譯最新版FFmpeg的方法步驟
- FFMPEG靜態庫編譯
- ffmpeg實現畫中畫
- FFmpeg推流器
- ffmpeg-aac
- OpenCV
- OpenCV學習筆記——視頻的邊緣檢測
- 圖像特征點匹配(視頻質量診斷、畫面抖動檢測)
- 圖像質量診斷