1)前言
- 經(jīng)過前面4篇文章的分析注服,針對WebRtc視頻接收模塊從創(chuàng)建接收模塊韭邓、到對RTP流接收處理、關(guān)鍵幀請求的時機溶弟、丟包判斷以及丟包重傳女淑、frame組幀等已經(jīng)有了一定的概念和認(rèn)識。
- 基于以上本文分析rtp包組包后聚合幀發(fā)送給解碼器前的處理流程辜御,在將一幀完整的幀發(fā)送給解碼模塊之前需要進行一定的預(yù)處理,如需要查找參考幀鸭你,本文著重分析解碼前的參考幀查找原理。
- 承接上文的分析擒权,rtp包組包成功后會將一幀完整的數(shù)據(jù)幀投遞到
RtpVideoStreamReceiver2
模塊由其OnAssembledFrame
函數(shù)來進行接收處理袱巨。 - 其實現(xiàn)如下:
void RtpVideoStreamReceiver2::OnAssembledFrame(
std::unique_ptr<video_coding::RtpFrameObject> frame) {
RTC_DCHECK_RUN_ON(&worker_task_checker_);
RTC_DCHECK(frame);
.....
//該模塊默認(rèn)未開啟,新特性值得研究菜拓,顧名思義為丟包通知控制模塊
// 可通過WebRTC-RtcpLossNotification/Enable開啟瓣窄,但是默認(rèn)只支持VP8
// SDP需要實現(xiàn)goog-lntf feedback
if (loss_notification_controller_ && descriptor) {
loss_notification_controller_->OnAssembledFrame(
frame->first_seq_num(), descriptor->frame_id,
absl::c_linear_search(descriptor->decode_target_indications,
DecodeTargetIndication::kDiscardable),
descriptor->dependencies);
}
// If frames arrive before a key frame, they would not be decodable.
// In that case, request a key frame ASAP.
if (!has_received_frame_) {
if (frame->FrameType() != VideoFrameType::kVideoFrameKey) {
// |loss_notification_controller_|, if present, would have already
// requested a key frame when the first packet for the non-key frame
// had arrived, so no need to replicate the request.
if (!loss_notification_controller_) {
RequestKeyFrame();
}
}
has_received_frame_ = true;
}
// Reset |reference_finder_| if |frame| is new and the codec have changed.
if (current_codec_) {
//每幀之間的時間戳不一樣,當(dāng)前幀的時間戳大于前一幀的時間戳(未環(huán)繞的情況下)
bool frame_is_newer =
AheadOf(frame->Timestamp(), last_assembled_frame_rtp_timestamp_);
if (frame->codec_type() != current_codec_) {
if (frame_is_newer) {
// When we reset the |reference_finder_| we don't want new picture ids
// to overlap with old picture ids. To ensure that doesn't happen we
// start from the |last_completed_picture_id_| and add an offset in case
// of reordering.
reference_finder_ =
std::make_unique<video_coding::RtpFrameReferenceFinder>(
this, last_completed_picture_id_ +
std::numeric_limits<uint16_t>::max());
current_codec_ = frame->codec_type();
} else {
// Old frame from before the codec switch, discard it.
return;
}
}
if (frame_is_newer) {
last_assembled_frame_rtp_timestamp_ = frame->Timestamp();
}
} else {
current_codec_ = frame->codec_type();
last_assembled_frame_rtp_timestamp_ = frame->Timestamp();
}
if (buffered_frame_decryptor_ != nullptr) {
buffered_frame_decryptor_->ManageEncryptedFrame(std::move(frame));
} else if (frame_transformer_delegate_) {
frame_transformer_delegate_->TransformFrame(std::move(frame));
} else {
reference_finder_->ManageFrame(std::move(frame));
}
}
- 首先該函數(shù)第一次接收到一幀數(shù)據(jù)的時候纳鼎,需要判斷是否是在關(guān)鍵幀之前收到俺夕,如果在未收到關(guān)鍵幀之前收到的話是不能解碼的裳凸,所以此時需要發(fā)送
關(guān)鍵幀請求
使用RequestKeyFrame()
函數(shù)發(fā)送關(guān)鍵幀請求。 - 其次劝贸、根據(jù)不同幀之間的時間戳不一樣的原則姨谷,判斷是否為新的一幀,首次接收到一幀之后會實例化
reference_finder_
成員映九,后續(xù)對參考幀的查找處理在未加密的情況下梦湘,都基于該實例完成。 - 如果為新的一幀件甥,每幀數(shù)據(jù)查找參考幀后都會更新
last_assembled_frame_rtp_timestamp_
捌议。 - 最后調(diào)用根據(jù)是否加密選擇
reference_finder_
或者buffered_frame_decryptor_
對視頻幀調(diào)用ManageFrame
或者ManageEncryptedFrame
函數(shù)進行參考幀查找處理。 - 本文的核心就是分析
ManageFrame
函數(shù)引有。
2)ManageFrame工作流程
-
在分析該函數(shù)之前先了解
RtpFrameReferenceFinder
瓣颅,RtpVideoStreamReceiver2
,OnCompleteFrameCallback
之間的關(guān)系譬正。
根據(jù)上圖的關(guān)系圖宫补,在
RtpFrameReferenceFinder
模塊中對video_coding::RtpFrameObject
數(shù)據(jù)幀進行處理,如果處理成功最終會生成video_coding::EncodedFrame
視頻幀曾我,接著回調(diào)OnCompleteFrameCallback
的OnCompleteFrame
函數(shù)將視頻幀返回到RtpVideoStreamReceiver2
模塊粉怕。ManageFrame()
函數(shù)的代碼如下:
void RtpFrameReferenceFinder::ManageFrame(
std::unique_ptr<RtpFrameObject> frame) {
// If we have cleared past this frame, drop it.
if (cleared_to_seq_num_ != -1 &&
AheadOf<uint16_t>(cleared_to_seq_num_, frame->first_seq_num())) {
return;
}
FrameDecision decision = ManageFrameInternal(frame.get());
switch (decision) {
case kStash:
if (stashed_frames_.size() > kMaxStashedFrames)
stashed_frames_.pop_back();
stashed_frames_.push_front(std::move(frame));
break;
case kHandOff:
HandOffFrame(std::move(frame));
RetryStashedFrames();
break;
case kDrop:
break;
}
}
-
cleared_to_seq_num_
變量記錄的是已經(jīng)清除的seq,比如說如果一幀數(shù)據(jù)已經(jīng)發(fā)送到解碼模塊,或解碼完成抒巢,那么需要將對應(yīng)的seq進行清除贫贝,在這里的作用就是判斷當(dāng)前待解碼的數(shù)據(jù)幀的首個包的seq和cleared_to_seq_num_
大小進行比較,在未環(huán)繞的情況下虐秦,如果cleared_to_seq_num_
大于frame->first_seq_num()
則說明該幀數(shù)據(jù)之前的幀已經(jīng)解碼了平酿,此幀應(yīng)該放棄解碼,所以直接返回悦陋。 -
cleared_to_seq_num_
變量的更新通過調(diào)用ClearTo(uint16_t seq_num)
函數(shù)來更新蜈彼,調(diào)用流程后續(xù)會分析到。 - 調(diào)用
ManageFrameInternal
函數(shù)對當(dāng)前幀進行決策處理俺驶,結(jié)果返回三種幸逆,kStash
表示當(dāng)前幀解碼時機未到需要存儲、kHandOff
可以解碼暮现、kDrop
表示放棄該幀还绘。 - 對于可以解碼的決策直接調(diào)用
HandOffFrame
函數(shù)進行后處理,而kStash
的決策使用stashed_frames_
容器將當(dāng)前幀插入到容器頭部栖袋,該容器的最大容量為100拍顷。 -
ManageFrameInternal
函數(shù)的實現(xiàn)如下:
RtpFrameReferenceFinder::FrameDecision
RtpFrameReferenceFinder::ManageFrameInternal(RtpFrameObject* frame) {
........
switch (frame->codec_type()) {
case kVideoCodecVP8:
return ManageFrameVp8(frame);
case kVideoCodecVP9:
return ManageFrameVp9(frame);
case kVideoCodecGeneric:
if (auto* generic_header = absl::get_if<RTPVideoHeaderLegacyGeneric>(
&frame->GetRtpVideoHeader().video_type_header)) {
return ManageFramePidOrSeqNum(frame, generic_header->picture_id);
}
ABSL_FALLTHROUGH_INTENDED;
default:
return ManageFramePidOrSeqNum(frame, kNoPictureId);
}
}
- 該函數(shù)根據(jù)當(dāng)前幀數(shù)據(jù)的codec類型使用不同的實現(xiàn)來對當(dāng)前幀進行決策,本文以H264為例進行分析討論塘幅。
-
ManageFrameH264
函數(shù)分成兩部分昔案,一部分可以理解成對方是使用硬編編碼出來的數(shù)據(jù)尿贫,此時tid=0xff,這種情況把任務(wù)交給了ManageFramePidOrSeqNum
函數(shù)。 - 另一種情況針對openh264軟編的數(shù)據(jù)此時tid不為0xff踏揣。
- 首先對tid=0xff的情況進行分析庆亡。
- 如果要支持H265的話需要在這里新增對H265視頻幀的決策處理函數(shù)。
3)ManageFramePidOrSeqNum設(shè)置參考幀
RtpFrameReferenceFinder::FrameDecision RtpFrameReferenceFinder::ManageFrameH264(
RtpFrameObject* frame) {
const FrameMarking& rtp_frame_marking = frame->GetFrameMarking();
uint8_t tid = rtp_frame_marking.temporal_id;
bool blSync = rtp_frame_marking.base_layer_sync;
/*android 硬編的情況收到的tid位0xff,傳入的kNoPictureId=-1捞稿,這是h264的特性*/
if (tid == kNoTemporalIdx)
return ManageFramePidOrSeqNum(std::move(frame), kNoPictureId);
....
}
根據(jù)tid=0xff,直接調(diào)用ManageFramePidOrSeqNum對當(dāng)前幀進行參考幀查找處理又谋。
-
在分析
ManageFramePidOrSeqNum()
函數(shù)之前首先介紹編碼數(shù)據(jù)gop的概念。
以上以h264為例,在H264數(shù)據(jù)中idr幀可以單獨解碼娱局,而P幀需要前向參考彰亥,在一個GOP內(nèi)的幀都需要前向參考幀才能順利解碼。
-
RtpFrameReferenceFinder
通過last_seq_num_gop_
容器來維護最近的GOP表铃辖,收到P幀后剩愧,RtpFrameReferenceFinder
需要找到P幀所屬的GOP,將P幀的參考幀設(shè)置為GOP內(nèi)該幀的上一幀娇斩,之后傳遞給FrameBuffer
模塊。
該容器是以當(dāng)前待解碼的幀所屬的
gop
(由于IDR
關(guān)鍵幀是gop
的開始)關(guān)鍵幀的最后一個包的seq位key,以當(dāng)前幀最后一個包的seq組成的std::pair<seq,seq>為value的容器(當(dāng)前幀也有可能是padding包穴翩。下面開始分析
ManageFramePidOrSeqNum()
函數(shù)原理如下
RtpFrameReferenceFinder::FrameDecision
RtpFrameReferenceFinder::ManageFramePidOrSeqNum(RtpFrameObject* frame,
int picture_id) {
// If |picture_id| is specified then we use that to set the frame references,
// otherwise we use sequence number.
// 1)確保非h264幀gop內(nèi)維護的幀的連續(xù)性
if (picture_id != kNoPictureId) {
frame->id.picture_id = unwrapper_.Unwrap(picture_id);
frame->num_references =
frame->frame_type() == VideoFrameType::kVideoFrameKey ? 0 : 1;
frame->references[0] = frame->id.picture_id - 1;
return kHandOff;
}
//2)判斷是否為關(guān)鍵幀,其中frame_type在組幀的時候進行設(shè)置的
if (frame->frame_type() == VideoFrameType::kVideoFrameKey) {
last_seq_num_gop_.insert(std::make_pair(
frame->last_seq_num(),//當(dāng)前gop最后一個包的seq為key
std::make_pair(frame->last_seq_num(), frame->last_seq_num())));
}
//3)如果到此為止還沒有收到一幀關(guān)鍵幀犬第,則存儲該幀
// We have received a frame but not yet a keyframe, stash this frame.
if (last_seq_num_gop_.empty())
return kStash;
// Clean up info for old keyframes but make sure to keep info
// for the last keyframe.
// 4)清除老的gop frame->last_seq_num() - 100之前的所有都清除掉,但至少確保有一個芒帕。
auto clean_to = last_seq_num_gop_.lower_bound(frame->last_seq_num() - 100);
for (auto it = last_seq_num_gop_.begin();
it != clean_to && last_seq_num_gop_.size() > 1;) {
it = last_seq_num_gop_.erase(it);
}
// Find the last sequence number of the last frame for the keyframe
// that this frame indirectly references.
// 函數(shù)能走到這一步歉嗓,gop 容器中是一定有存值的
//5.1) 如果關(guān)鍵幀的序號是大于該幀的序號的(未環(huán)繞的情況),那么該幀需要丟棄掉背蟆。
// 假設(shè)last_seq_num_gop_中存的是34號包鉴分,而本次來的幀的序號是10~16(非關(guān)鍵幀)。
//5.2) 還有一種情況假設(shè)當(dāng)前幀就是關(guān)鍵幀frame->last_seq_num()=34带膀,假設(shè)事先last_seq_num_gop_存的是56號seq,由last_seq_num_gop_定義的排序規(guī)則志珍,34號包被插入的時候會在頭部,最終下面的條件依然成立垛叨。
auto seq_num_it = last_seq_num_gop_.upper_bound(frame->last_seq_num());
if (seq_num_it == last_seq_num_gop_.begin()) {
RTC_LOG(LS_WARNING) << "Generic frame with packet range ["
<< frame->first_seq_num() << ", "
<< frame->last_seq_num()
<< "] has no GoP, dropping frame.";
return kDrop;
}
//如果上述條件不成立這里則返回last_seq_num_gop_最后一個元素對應(yīng)的迭代器
//如果當(dāng)前幀為關(guān)鍵幀的話那么seq_num_it為last_seq_num_gop_.end(),進行--操作后舊對應(yīng)了最后一個關(guān)鍵幀
seq_num_it--;
// Make sure the packet sequence numbers are continuous, otherwise stash
// this frame.
// 6) 該步用來判斷該幀和上一幀的連續(xù)性
// last_picture_id_gop得到的是當(dāng)前gop所維護的當(dāng)前幀的上一幀(前向參考幀)的最后一個包的seq伦糯。
uint16_t last_picture_id_gop = seq_num_it->second.first;
// last_picture_id_with_padding_gop得到的也是上一幀的最后一個包的seq。
// 當(dāng)前GOP的最新包的序列號嗽元,可能是last_picture_id_gop, 也可能是填充包.
uint16_t last_picture_id_with_padding_gop = seq_num_it->second.second;
// 非關(guān)鍵幀判斷seq連續(xù)性敛纲,
if (frame->frame_type() == VideoFrameType::kVideoFrameDelta) {
//得到上一幀最后一個包的seq,當(dāng)前幀的第一個包的seq -1 得到上一幀的最后一個seq
uint16_t prev_seq_num = frame->first_seq_num() - 1;
// 如果不相等說明不連續(xù),如果正常未丟包的情況下是一定會相等的剂癌。
if (prev_seq_num != last_picture_id_with_padding_gop)
return kStash;
}
//檢查當(dāng)前幀最后一個seq是否大于所屬gop 關(guān)鍵幀的最后一個seq
RTC_DCHECK(AheadOrAt(frame->last_seq_num(), seq_num_it->first));
// Since keyframes can cause reordering we can't simply assign the
// picture id according to some incrementing counter.
//7) 給RtpFrameObject的id.picture_id賦值
// 如果為關(guān)鍵幀num_references為false,否則為true
frame->id.picture_id = frame->last_seq_num();
frame->num_references =
frame->frame_type() == VideoFrameType::kVideoFrameDelta;
//上一幀最后一個包號
frame->references[0] = rtp_seq_num_unwrapper_.Unwrap(last_picture_id_gop);
//這一步確保第6步的邏輯能跑通淤翔,否則第6不邏輯是跑不通的last_picture_id_表示的是當(dāng)前幀的上一個關(guān)鍵幀的最后一個包的seq,frame->id.picture_id為當(dāng)前幀的最后一個包的seq,正常情況AheadOf函數(shù)是會返回true的佩谷。
if (AheadOf<uint16_t>(frame->id.picture_id, last_picture_id_gop)) {
//這里修改了容器last_seq_num_gop_對應(yīng)關(guān)鍵幀的second變量旁壮,將當(dāng)前幀最后一個包號的seq 賦值給他們
//正因為有這個操作辞做,第6步才能順利跑通
seq_num_it->second.first = frame->id.picture_id;
seq_num_it->second.second = frame->id.picture_id;
}
last_picture_id_ = frame->id.picture_id;
//更新填充包狀態(tài)
UpdateLastPictureIdWithPadding(frame->id.picture_id);
frame->id.picture_id = rtp_seq_num_unwrapper_.Unwrap(frame->id.picture_id);
return kHandOff;
}
- 1)確保
gop
內(nèi)幀的連續(xù)性,對于google vpx系列的編碼數(shù)據(jù),只需要判斷picture_id是否連續(xù)即可寡具,num_references
表示參考幀數(shù)目秤茅,對于IDR關(guān)鍵幀可以單獨解碼,不需要參考幀童叠,所以num_references
為0框喳,若gop
內(nèi)任一幀丟失則該gop
內(nèi)的剩余時間都將處于卡頓狀態(tài)。 - 2)判斷當(dāng)前幀是否是關(guān)鍵幀厦坛,如果是則直接將其該關(guān)鍵幀的最后一個包的seq 生成相應(yīng)的鍵值對插入到
gop
容器last_seq_num_gop_
五垮,關(guān)鍵幀是gop
的開始。 - 3)如果
last_seq_num_gop_
為空表示到此目前為止沒收到關(guān)鍵幀杜秸,同時當(dāng)前幀又不是關(guān)鍵幀所以沒有參考幀放仗,不能解碼,需要緩存該幀撬碟。為什么不是直接丟棄诞挨? - 4)將
last_seq_num_gop_
容器維護的太舊的關(guān)鍵幀清除掉,規(guī)則是當(dāng)前幀最后一個包seq即[frame->last_seq_num() - 100]
之前的關(guān)鍵幀都清理掉呢蛤,但是至少保留一個(假設(shè)規(guī)則之前一共就維護了一個gop那么不清除)惶傻。 - 5)以當(dāng)前幀的最后一個包的seq使用
last_seq_num_gop_.upper_bound(frame->last_seq_num())
查詢,該查詢返回last_seq_num_gop_
容器中第一個大于frame->last_seq_num()
的位置其障,假設(shè)查出的位置就是last_seq_num_gop_
的首部银室,則丟棄該幀,為什么呢?來舉個例子励翼,假設(shè)last_seq_num_gop_
此時存在的seq為34而此時傳入的包的seq->first_seq_num() = 10蜈敢,seq->last_seq_num() =16,而且當(dāng)前傳入的幀為非關(guān)鍵幀汽抚,這說明什么意思呢抓狭?在傳輸?shù)倪^程中可能由于10~16號包這一幀數(shù)據(jù)中有幾個包丟了,而又由于丟包重傳發(fā)送了PLI請求殊橙,也或者是對端主動發(fā)送了關(guān)鍵幀浩考,該關(guān)鍵幀的的最后一個包的序號恰好是34啡捶,在上文的分析中提到了組包流程,如果組包過程中出現(xiàn)了關(guān)鍵幀,它是不管該關(guān)鍵幀前面的幀的死活的蜡歹,直接會將該關(guān)鍵幀投遞到RtpVideoStreamReceiver2
進行處理富腊,而當(dāng)該關(guān)鍵幀處理之后10~16號包之間被丟失的包又被恢復(fù)了狮惜,同理會傳遞到該函數(shù)進行處理圾旨,此時上述的假設(shè)條件就成立了,那么對于這種情況下惹谐,該幀應(yīng)該丟棄掉持偏,因為他后面的關(guān)鍵幀已經(jīng)被處理了驼卖。 - 6)根據(jù)
last_seq_num_gop_
來判斷當(dāng)前幀和上一幀的連續(xù)性,如果不連續(xù)(說明沒有前向參考幀鸿秆,不能進行解碼)則返回kStash
酌畜,進行緩存操作。 - 7)設(shè)置
picture_id
卿叽,對于H264數(shù)據(jù)用一幀的最后一個seq來作為picture_id
桥胞,設(shè)置當(dāng)前幀的參考幀數(shù)目,對于關(guān)鍵幀不需要參考幀所以為0考婴,對于P幀贩虾,參考幀數(shù)目為1(前向參考)。 - 更新
gop
容器last_seq_num_gop_
的value值沥阱,它也是一個std::pair<seq,seq>
缎罢,這兩個值被設(shè)置成當(dāng)前幀的最后一個包的seq,同時也更新RtpFrameObject
的id成員考杉,最后返回kHandOff
策精。 - 此處
RtpFrameObject
父類有3個重要的成員變量id、num_references奔则、references[0]被賦值蛮寂,其中num_references表示的意思應(yīng)該為當(dāng)前幀的和上一幀是參考關(guān)系,h264的前向參考易茬。
- 該函數(shù)的決策主要是通過判斷seq的連續(xù)性(是否有參考幀)或者是否是關(guān)鍵幀,來決定當(dāng)前幀是否要發(fā)到解碼模塊及老,或者是進行存儲抽莱,當(dāng)出現(xiàn)丟幀現(xiàn)象的時候,需要緩存當(dāng)前幀然后等待丟失的幀重傳。
- 到此為止骄恶,
gop
容器last_seq_num_gop_
的數(shù)據(jù)成員如下:
4) UpdateLastPictureIdWithPadding更新填充包狀態(tài)
void RtpFrameReferenceFinder::UpdateLastPictureIdWithPadding(uint16_t seq_num) {
//取第一個大于seq_num的對應(yīng)的gop
auto gop_seq_num_it = last_seq_num_gop_.upper_bound(seq_num);
// If this padding packet "belongs" to a group of pictures that we don't track
// anymore, do nothing.
if (gop_seq_num_it == last_seq_num_gop_.begin())
return;
--gop_seq_num_it;
// Calculate the next contiuous sequence number and search for it in
// the padding packets we have stashed.
uint16_t next_seq_num_with_padding = gop_seq_num_it->second.second + 1;
auto padding_seq_num_it =
stashed_padding_.lower_bound(next_seq_num_with_padding);
// While there still are padding packets and those padding packets are
// continuous, then advance the "last-picture-id-with-padding" and remove
// the stashed padding packet.
while (padding_seq_num_it != stashed_padding_.end() &&
*padding_seq_num_it == next_seq_num_with_padding) {
gop_seq_num_it->second.second = next_seq_num_with_padding;
++next_seq_num_with_padding;
padding_seq_num_it = stashed_padding_.erase(padding_seq_num_it);
}
// In the case where the stream has been continuous without any new keyframes
// for a while there is a risk that new frames will appear to be older than
// the keyframe they belong to due to wrapping sequence number. In order
// to prevent this we advance the picture id of the keyframe every so often.
if (ForwardDiff(gop_seq_num_it->first, seq_num) > 10000) {
auto save = gop_seq_num_it->second;
last_seq_num_gop_.clear();
last_seq_num_gop_[seq_num] = save;
}
}
5) ManageFrame函數(shù)業(yè)務(wù)處理
void RtpFrameReferenceFinder::ManageFrame(
std::unique_ptr<RtpFrameObject> frame) {
.....
FrameDecision decision = ManageFrameInternal(frame.get());
switch (decision) {
case kStash:
if (stashed_frames_.size() > kMaxStashedFrames)//最大100
stashed_frames_.pop_back();
stashed_frames_.push_front(std::move(frame));
break;
case kHandOff:
HandOffFrame(std::move(frame));
RetryStashedFrames();
break;
case kDrop:
break;
}
}
- 在2.1中分析了ManageFrameInternal的原理食铐,該函數(shù)會返回三種不同的決策。
- 當(dāng)返回
kStash
的時候會將當(dāng)前待解碼的幀插入到stashed_frames_
容器,等待合適的時機獲取僧鲁,如果容器滿了先將末尾的清除掉虐呻,然后從頭部插入,同時根據(jù)上面的分析我們可以得知寞秃,出現(xiàn)這種情況是要等待前面的幀完整斟叼。所以在kHandOff
的情況下先處理當(dāng)前幀然后再通過RetryStashedFrames獲取stashed_frames_
中存儲的幀進行解碼。 - 當(dāng)返回
kHandOff
的時候調(diào)用HandOffFrame函數(shù)進行再處理春寿。 - 當(dāng)返回
kDrop
的時候直接丟棄該幀數(shù)據(jù)朗涩。 -
stashed_frames_
為一個std::deque<std::unique_ptr<RtpFrameObject>>
隊列。
void RtpFrameReferenceFinder::HandOffFrame(
std::unique_ptr<RtpFrameObject> frame) {
//picture_id_offset_為0
frame->id.picture_id += picture_id_offset_;
for (size_t i = 0; i < frame->num_references; ++i) {
frame->references[i] += picture_id_offset_;
}
frame_callback_->OnCompleteFrame(std::move(frame));
}
- 調(diào)用OnCompleteFrame將RtpFrameObject傳遞到
RtpVideoStreamReceiver
模塊當(dāng)中绑改。
void RtpFrameReferenceFinder::RetryStashedFrames() {
bool complete_frame = false;
do {
complete_frame = false;
for (auto frame_it = stashed_frames_.begin();
frame_it != stashed_frames_.end();) {
FrameDecision decision = ManageFrameInternal(frame_it->get());
switch (decision) {
case kStash:
++frame_it;
break;
case kHandOff:
complete_frame = true;
HandOffFrame(std::move(*frame_it));
RTC_FALLTHROUGH();
case kDrop:
frame_it = stashed_frames_.erase(frame_it);
}
}
} while (complete_frame);
}
- 對
stashed_frames_
容器進行遍歷谢床,重新調(diào)用ManageFrameInternal
進行決策兄一,最后如果決策可解碼的話回調(diào)HandOffFrame
進行處理。 - 如果決策結(jié)果為
kDrop
直接釋放识腿。
6)總結(jié)
-
RtpFrameReferenceFinder
模塊的核心作用就是決策當(dāng)前幀是否要進入到解碼模塊出革。 - 決策的依據(jù)依然是根據(jù)seq的連續(xù)性,以及是否有關(guān)鍵幀等性質(zhì)渡讼。
- 在決策為
kHandOff
的情況下會通過其成員變量frame_callback_
將數(shù)據(jù)重新傳遞到RtpVideoStreamReceiver
模塊的OnCompleteFrame
函數(shù)骂束。 - 接下來的處理就是解碼前的操作了如將數(shù)據(jù)放到
jitterbuffer
模塊等。