為什么一開始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è)置的,表示使用何種策略降低視頻能級??蛇x值有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::VideoSenderwebrtc::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è)置為1,才判定為最后一個(gè)包,但看后面的條件 IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_) 還不一定只有一個(gè)包被設(shè)置了 M 標(biāo)識位。
問題:H264 RTP中是如何規(guī)定 M 標(biāo)志位表示幀的最后一個(gè)數(shù)據(jù)包的?