為什么一開始fps會(huì)降到1吠谢,后來有了正常的兩方通話后又恢復(fù)到30
WebRTC對每一幀調(diào)用 VideoStreamEncoder::OnFrame
,然后調(diào)用VideoStreamEncoder::MaybeEncodeVideoFrame
這個(gè)方法中可能會(huì)執(zhí)行最終的編碼。
定義了一個(gè) posted_frames_waiting_for_encode_
變量表示當(dāng)前等待編碼的幀數(shù),通過它判斷是否應(yīng)該跳過來不及編碼的幀虹统。
每次OnFrame
調(diào)用會(huì)在調(diào)用的線程執(zhí)行 posted_frames_waiting_for_encode_++
,然后在編碼線程中如果判斷posted_frames_waiting_for_encode_ > 1
則跳過編碼宜雀,如果posted_frames_waiting_for_encode_ == 1
則進(jìn)行編碼。不論是否跳過還是執(zhí)行了編碼,這個(gè)值都會(huì)減一posted_frames_waiting_for_encode_--
退子。這樣就保證了如果有多個(gè)幀正在等待編碼腕铸,則會(huì)編碼這些幀中的最晚的幀浓恳。
如果等待編碼的幀有多個(gè)晴圾,說明編碼性能趕不上設(shè)備采集幀率颂砸,編碼器的性能會(huì)最終影響fps,低性能會(huì)導(dǎo)致實(shí)際fps降低死姚。
另一方面人乓,在VideoSender
中也通過改變編碼器的參數(shù)來改變實(shí)際的編碼幀率,該參數(shù)為encoder_params_.input_frame_rate
都毒。
VideoSender
調(diào)用VideoSender::UpdateEncoderParameters
更新幀率參數(shù)色罚,它調(diào)用media_optimization::MediaOptimization
對象_mediaOpt
的
InputFrameRate()
方法得到估算的幀率。
MediaOptimization
是一個(gè)工具類账劲,它的功能之一就是估算input_frame_rate
戳护,它會(huì)記錄每一幀的時(shí)間戳,然后根據(jù)最近兩秒的幀數(shù)來估算幀率瀑焦。
對每一幀腌且,調(diào)用MediaOptimization::DropFrame
,這個(gè)接口是用來判斷是否丟幀的榛瓮,每一幀都會(huì)調(diào)用這個(gè)方法(名字起得不好铺董,害得我查了很久才發(fā)現(xiàn)這個(gè)是每一幀都調(diào)用的)。具體方法:
- 每次
DropFrame
時(shí)記錄一個(gè)時(shí)間戳禀晓,插入到隊(duì)列中柄粹。 - 調(diào)用
InputFrameRate
時(shí)在隊(duì)列中查找一個(gè)區(qū)間,計(jì)算這個(gè)區(qū)間的每幀平均時(shí)間匆绣,即最大時(shí)間戳 減 最小時(shí)間戳除以數(shù)量驻右。 - 區(qū)間的計(jì)算:從隊(duì)列尾部開始,到與尾部時(shí)間戳之差小于2秒的最大值崎淳,即這個(gè)區(qū)間最大長度為2秒堪夭。
- 最后使用這個(gè)每幀平均時(shí)間,計(jì)算
input_frame_rate
拣凹。
為什么后來又升上去了森爽,還沒有研究,大概是因?yàn)榉直媛实慕档蛯?dǎo)致編碼速度加快嚣镜,然后通過MediaOptimization
的估算慢慢提升了幀率爬迟。
為什么分辨率會(huì)由剛開始的 720x1280 經(jīng)過幾秒后降到 360x640
VideoStreamEncoder::OnFrame
↓
VideoStreamEncoder::MaybeEncodeVideoFrame
↓
// 判斷是否應(yīng)該降低視頻能級,如果降級則調(diào)用 AdaptDown 菊匿,并且跳過該幀的解碼
VideoStreamEncoder::DropDueToSize
↓
VideoStreamEncoder::AdaptDown
↓
VideoStreamEncoder::VideoSourceProxy::RequestResolutionLowerThan
↓
VideoSourceInterface::AddOrUpdateSink // 最終改變輸入幀率的方法
DegradationPreference
:猜測是協(xié)議層由對端設(shè)置的付呕,表示使用何種策略降低視頻能級计福。可選值有4種:
- DISABLED
- MAINTAIN_FRAMERATE
- MAINTAIN_RESOLUTION
- BALANCED
根據(jù)log判斷默認(rèn)值是 MAINTAIN_FRAMERATE
徽职,后面只有在結(jié)束通信時(shí)設(shè)置成了 DISABLED
象颖。猜測可能一直保持MAINTAIN_FRAMERATE
不變。
VideoStreamEncoder::DropDueToSize
根據(jù)一個(gè)初始碼率encoder_start_bitrate_bps_
來限制分辨率姆钉。將初始碼率分成3個(gè)檔次對應(yīng)一個(gè)最大分辨率:
- [0, 300kbps]=>320x240
- [300kbps, 500kbps]=>640x480
- [500kbps, )=>沒有限制说订,
如果大于該檔次的最大分辨率就判斷為需要降低分辨率。
DropFrame是如何運(yùn)作的
MediaOptimization
潮瓶,在兩個(gè)類中使用:
vcm::VideoSender
webrtc::VCMEncodedFrameCallback
MediaOptimization
內(nèi)部使用FrameDropper
陶冷,用來計(jì)算什么時(shí)候丟幀。
FEC在WEBRTC是怎么使用的毯辅?
在 rtp_sender_video.cc
文件中處理FEC埂伦、NACK等Qos功能。
payload
悉罕,即SDP中定義的負(fù)載類型id赤屋。在代碼中立镶,payload >= 0
表示啟用壁袄,payload < 0
表示關(guān)閉。
FlexfecSender flexfec_sender
負(fù)責(zé)flexfec的對象媚媒。google的 demo server 都不支持嗜逻,估計(jì)很少有支持的吧。如果有的話缭召,它是比ulpfec優(yōu)先的栈顷,可以在demo的設(shè)置項(xiàng)中打開開關(guān)。
red_payload_type_
: redundant payload嵌巷。red用來持有ulpfec萄凤,沒有red也就沒有ulpfec。
ulpfec_payload_type_
: ulpfec payload搪哪。
RTPSenderVideo::SendVideo()
最終發(fā)送視頻數(shù)RTP包的方法靡努,這個(gè)方法先計(jì)算包的數(shù)量,然后計(jì)算出來詳細(xì)的包大小晓折,最后一個(gè)包一個(gè)包的填充數(shù)據(jù)并發(fā)送惑朦。
對每個(gè)包
- 如果開啟了flexfec,則發(fā)送flexfec包
- 如果開啟了red漓概,則發(fā)送攜帶ulpfec的red包漾月,調(diào)用
SendVideoPacketAsRedMaybeWithUlpfec
- 否則直接發(fā)送video數(shù)據(jù)
為什么H264的時(shí)候沒有啟用
RtpVideoSender::ConfigureProtection
↓
PayloadTypeSupportsSkippingFecPackets
// 它判斷了只有vp8和vp9才開啟FEC,也就是說h264不開啟FEC胃珍。
衡量FEC的效果
NACK with H264 為什么會(huì)導(dǎo)致 FEC 包的重傳梁肿?
一個(gè)完整的幀包含所有的數(shù)據(jù)蜓陌,是不需要重傳的,產(chǎn)生 NACK 的原因一定是判定了一幀中的某些包丟失了栈雳。那么一定有個(gè)機(jī)制來判斷一幀的完整性护奈,而這種機(jī)制對 H264 with FEC 一定是有缺陷的「缛遥可能是因?yàn)槊蛊欤及?+ FEC包都到達(dá)才判定為完整,因此導(dǎo)致了即使所有的原始數(shù)據(jù)包都到達(dá)蛀骇,有FEC包沒到達(dá)厌秒,也會(huì)被判定為幀不完整。
先要搞清楚兩個(gè)問題:一是選擇哪些包發(fā)送NACK擅憔?另一個(gè)是怎么判斷幀的完整性鸵闪?
哪些包需要發(fā)送NACK?
返回 NACK 列表的調(diào)用順序:
ModuleProcessThread
↓
VideoReceiver::Process
↓
VCMReceiver::NackList // 沒有計(jì)算暑诸,直接調(diào)用下面的方法
↓
VCMJitterBuffer::GetNackList(bool* request_key_frame) // 執(zhí)行具體計(jì)算的地方
ModuleProcessThread
周期性調(diào)用 VideoReceiver::Process
方法蚌讼,最后調(diào)用的 VCMJitterBuffer
中的方法獲取 NACK 列表,VCMJitterBuffer
負(fù)責(zé)計(jì)算哪些包是需要發(fā)送 NACK 的个榕,也就是確定 NACK 列表篡石。
VCMJitterBuffer::GetNackList
方法先根據(jù)時(shí)間和緩沖區(qū)大小更新missing_sequence_numbers_
集合,使之不要超出最大限制西采。主要是根據(jù)一些條件刪除 missing_sequence_numbers_
中的數(shù)據(jù)凰萨,但這些判斷與 VCMFrameBuffer
的狀態(tài)沒有太大關(guān)系。更多的是要判斷是否應(yīng)該 request_key_frame
械馆,即請求關(guān)鍵幀胖眷。
然后 VCMJitterBuffer::GetNackList
將 missing_sequence_numbers_
集合中的數(shù)據(jù)轉(zhuǎn)化成一個(gè)列表返回。
另一個(gè)更新 missing_sequence_numbers_
集合的方法是VCMJitterBuffer::UpdateNackList
霹崎,調(diào)用順序:
VCMJitterBuffer::InsertPacket(packet)
↓
VCMJitterBuffer::UpdateNackList(sequence_number)
UpdateNackList
的參數(shù) sequence_number
就是 InsertPacket
的參數(shù) packet
所持有的 sequence_number
珊搀。UpdateNackList
根據(jù)新插入的 sequence_number
更新 missing_sequence_numbers_
集合。主要邏輯如下:
latest_received_sequence_number_
表示最新接收到的 sequence number尾菇,這個(gè)值會(huì)在InsertPacket
和UpdateNackList
中更新境析。sequence number 應(yīng)是連續(xù)的整數(shù),如果傳入的
sequence_number
比latest_received_sequence_number_
要大错沽,也就是時(shí)間順序上要晚簿晓,如果兩者不是連續(xù)的(sequence_number > latest_received_sequence_number_ + 1
),說明它們之間有其他 packet 沒有接收到千埃。那么這個(gè)區(qū)間內(nèi)的所有 sequence number 就要添加到missing_sequence_numbers_
集合中憔儿。如果
sequence_number
比latest_received_sequence_number_
要小,說明這個(gè) packet 是遲到的一個(gè)包放可,應(yīng)該在之前的處理過程中谒臼,已經(jīng)添加到了missing_sequence_numbers_
中朝刊,因此在missing_sequence_numbers_
中刪除這個(gè) sequence number 即可。
如何判斷幀完整
VCMJitterBuffer
↓
VCMFrameBuffer.GetState()
↓
VCMSessionInfo.complete()
判斷 VCMSessionInfo
完整有幾個(gè)條件:
-
VCMSessionInfo
中有第一個(gè) packet -
VCMSessionInfo
中有最后一個(gè) packet - 所有的 packet 的 sequence number 都是連續(xù)的
這個(gè)條件很容易理解蜈缤,也沒有什么特別之處拾氓,關(guān)鍵在于如何判斷是否有最后一個(gè) packet,在代碼中由變量 last_packet_seq_num_
存儲(chǔ)最后一個(gè) packet 的 sequence number底哥。
只有 VCMSessionInfo::InsertPacket
方法會(huì)更新 last_packet_seq_num_
咙鞍,而其中也明確區(qū)分了 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 標(biāo)識(shí)位設(shè)置為1趾徽,才判定為最后一個(gè)包续滋,但看后面的條件 IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_)
還不一定只有一個(gè)包被設(shè)置了 M 標(biāo)識(shí)位。
問題:H264 RTP中是如何規(guī)定 M 標(biāo)志位表示幀的最后一個(gè)數(shù)據(jù)包的孵奶?