從jitterbuffer取出frame声畏,解碼
在ViEChannel類中創(chuàng)建解碼線程,在VCMReceiver類中調(diào)用jitterbuffer取出frame姻成。
bool ViEChannel::ChannelDecodeProcess()
->int32_t Decode(uint16_t maxWaitTimeMs)
->int32_t VideoReceiver::Decode(uint16_t maxWaitTimeMs)
->VCMEncodedFrame* VCMReceiver::FrameForDecoding
```
先檢查是否存在完整的幀插龄,存在則取出,若不存在科展,則檢查不完整的幀均牢,滿足條件則取出,不滿足則不取出才睹。
```
bool found_frame = jitter_buffer_.NextCompleteTimestamp(
max_wait_time_ms, &frame_timestamp);
if (!found_frame)
found_frame = jitter_buffer_.NextMaybeIncompleteTimestamp(&frame_timestamp);
```
在上一步取出frame_timestamp徘跪,再用frame_timestamp從jitterbuffer中取出幀數(shù)據(jù)
```
VCMEncodedFrame* frame = jitter_buffer_.ExtractAndSetDecode(frame_timestamp);
```
##接收到包,將包插入jitterbuffer
接收到包砂竖,將包插入jitterbuffer代碼流程
```
void UdpTransportImpl::IncomingRTPCallback
->void UdpTransportImpl::IncomingRTPFunction
->void VideoChannelTransport::IncomingRTPPacket
->int ViENetworkImpl::ReceivedRTPPacket
->int32_t ViEChannel::ReceivedRTPPacket
->int ViEReceiver::ReceivedRTPPacket
->int ViEReceiver::InsertRTPPacket
->bool ViEReceiver::ReceivePacket
->bool RtpReceiverImpl::IncomingRtpPacket
->int32_t RTPReceiverVideo::ParseRtpPacket
->int32_t ViEReceiver::OnReceivedPayloadData
->int32_t IncomingPacket
->int32_t VideoReceiver::IncomingPacket
->int32_t VCMReceiver::InsertPacket
->VCMFrameBufferEnum VCMJitterBuffer::InsertPacket
```
##decodable_frames_真椿,incomplete_frames_,free_frames_
decodable_frames_乎澄,incomplete_frames_突硝,free_frames_的處理,主要在VCMFrameBuffer類中置济,其中包含的VCMSessionInfo _sessionInfo;為主要處理成員解恰。
對于每一次收到的包,根據(jù)時(shí)間戳找到浙于,當(dāng)前幀在哪個(gè)隊(duì)列中护盈,在decodable_frames_或incomplete_frames_隊(duì)列中,若不存在羞酗,則從free_frames_隊(duì)列中給出一個(gè)空幀腐宋。
```
VCMFrameBuffer* frame;
FrameList* frame_list;
const VCMFrameBufferEnum error = GetFrame(packet, &frame, &frame_list);```
```
// Gets frame to use for this timestamp. If no match, get empty frame.
VCMFrameBufferEnum VCMJitterBuffer::GetFrame(const VCMPacket& packet,
VCMFrameBuffer** frame,
FrameList** frame_list) {
*frame = incomplete_frames_.PopFrame(packet.timestamp);
if (*frame != NULL) {
*frame_list = &incomplete_frames_;
return kNoError;
}
*frame = decodable_frames_.PopFrame(packet.timestamp);
if (*frame != NULL) {
*frame_list = &decodable_frames_;
return kNoError;
}
*frame_list = NULL;
// No match, return empty frame.
*frame = GetEmptyFrame();
if (*frame == NULL) {
// No free frame! Try to reclaim some...
LOG(LS_WARNING) << "Unable to get empty frame; Recycling.";
bool found_key_frame = RecycleFramesUntilKeyFrame();
*frame = GetEmptyFrame();
assert(*frame);
if (!found_key_frame) {
free_frames_.push_back(*frame);
return kFlushIndicator;
}
}
(*frame)->Reset();
return kNoError;
}```
對于VCMSessionInfo中 complete_和decodable_的判定,每一次插入一個(gè)包,都要進(jìn)行UpdateCompleteSession();對于幀的完整性進(jìn)行檢查胸竞。
```
size_t returnLength = InsertBuffer(frame_buffer, packet_list_it);
UpdateCompleteSession();
if (decode_error_mode == kWithErrors)
decodable_ = true;
else if (decode_error_mode == kSelectiveErrors)
UpdateDecodableSession(frame_data);
return static_cast<int>(returnLength);
```
UpdateCompleteSession();檢查是否有第一個(gè)包和最后一個(gè)包欺嗤,并且都是按序的,即沒有丟包卫枝,若滿足這些條件煎饼,則判定為complete_ = true;
```
void VCMSessionInfo::UpdateCompleteSession() {
if (HaveFirstPacket() && HaveLastPacket()) {
// Do we have all the packets in this session?
bool complete_session = true;
PacketIterator it = packets_.begin();
PacketIterator prev_it = it;
++it;
for (; it != packets_.end(); ++it) {
if (!InSequence(it, prev_it)) {
complete_session = false;
break;
}
prev_it = it;
}
complete_ = complete_session;
}
}
```
下面則是對于decodable_ 的判定,如果decode_error_mode 為kWithErrors模式校赤,則有一個(gè)包decodable_ 即可以為ture吆玖。如果decode_error_mode 為kSelectiveErrors,則根據(jù)rtt,幀類型马篮,已經(jīng)有的包數(shù)等條件來綜合得出decodable_ 沾乘。
具體條件則為:rtt<100,或者為關(guān)鍵幀浑测,或者已經(jīng)收到的包數(shù)在0.2\*rolling_average_packets_per_frame和0.8\*rolling_average_packets_per_frame之間時(shí)意鲸,則decodable_ 不去改變,否則為true尽爆。rolling_average_packets_per_frame為平均每幀所含的包數(shù)待笑。
```
void VCMSessionInfo::UpdateDecodableSession(const FrameData& frame_data) {
// Irrelevant if session is already complete or decodable
if (complete_ || decodable_)
return;
// TODO(agalusza): Account for bursty loss.
// TODO(agalusza): Refine these values to better approximate optimal ones.
// Do not decode frames if the RTT is lower than this.
const int64_t kRttThreshold = 100;
// Do not decode frames if the number of packets is between these two
// thresholds.
const float kLowPacketPercentageThreshold = 0.2f;
const float kHighPacketPercentageThreshold = 0.8f;
if (frame_data.rtt_ms < kRttThreshold
|| frame_type_ == kVideoFrameKey
|| !HaveFirstPacket()
|| (NumPackets() <= kHighPacketPercentageThreshold
* frame_data.rolling_average_packets_per_frame
&& NumPackets() > kLowPacketPercentageThreshold
* frame_data.rolling_average_packets_per_frame))
return;
decodable_ = true;
}
```
```
PS:
1惠呼、如果rolling_average_packets_per_frame>5,
kLowPacketPercentageThreshold * frame_data.rolling_average_packets_per_frame>1
那么來一個(gè)包就判定 decodable_ = true;這個(gè)是否合理?
2妓笙、 rtt很小的時(shí)候夭委,不判定為true幅狮,是希望能夠complete_ ,至于kRttThreshold = 100是否合理株灸,有待驗(yàn)證崇摄。
3、可見慌烧,關(guān)鍵幀都是完整的逐抑,不完整,不會 設(shè)置decodable_ = true;則一直為kIncomplete屹蚊。
```
對于VCMFrameBuffer中的kCompleteSession和kDecodableSession分別對應(yīng)VCMSessionInfo中的complete_ 和decodable_ 厕氨。若既不是complete_ ,也不是decodable_汹粤,則對應(yīng)kIncomplete命斧。
```
if (_sessionInfo.complete()) {
SetState(kStateComplete);
return kCompleteSession;
} else if (_sessionInfo.decodable()) {
SetState(kStateDecodable);
return kDecodableSession;
}
return kIncomplete;
```
對于上面提到的decode_error_mode,通過如下流程進(jìn)行設(shè)置嘱兼。
```
int Conductor::VideoCreateStream
->int ViEBaseImpl::CreateChannel
->int ViEBaseImpl::CreateChannel
->int ViEChannelManager::CreateChannel
->bool ChannelGroup::CreateSendChannel
->bool ChannelGroup::CreateChannel
->int32_t ViEChannel::Init
->int32_t SetVideoProtection
->int32_t VideoReceiver::SetVideoProtection
->void VCMReceiver::SetDecodeErrorMode
->void VCMJitterBuffer::SetDecodeErrorMode
```
這里根據(jù)NACK和FEC的使用情況国葬,來設(shè)置decode_error_mode。
```
// Enable or disable a video protection method.
// Note: This API should be deprecated, as it does not offer a distinction
// between the protection method and decoding with or without errors. If such a
// behavior is desired, use the following API: SetReceiverRobustnessMode.
int32_t VideoReceiver::SetVideoProtection(VCMVideoProtection videoProtection,
bool enable) {
// By default, do not decode with errors.
_receiver.SetDecodeErrorMode(kNoErrors);
switch (videoProtection) {
case kProtectionNack:
case kProtectionNackReceiver: {
CriticalSectionScoped cs(_receiveCritSect);
if (enable) {
// Enable NACK and always wait for retransmits.
_receiver.SetNackMode(kNack, -1, -1);
} else {
_receiver.SetNackMode(kNoNack, -1, -1);
}
break;
}
case kProtectionKeyOnLoss: {
CriticalSectionScoped cs(_receiveCritSect);
if (enable) {
_keyRequestMode = kKeyOnLoss;
_receiver.SetDecodeErrorMode(kWithErrors);
} else if (_keyRequestMode == kKeyOnLoss) {
_keyRequestMode = kKeyOnError; // default mode
} else {
return VCM_PARAMETER_ERROR;
}
break;
}
case kProtectionKeyOnKeyLoss: {
CriticalSectionScoped cs(_receiveCritSect);
if (enable) {
_keyRequestMode = kKeyOnKeyLoss;
} else if (_keyRequestMode == kKeyOnKeyLoss) {
_keyRequestMode = kKeyOnError; // default mode
} else {
return VCM_PARAMETER_ERROR;
}
break;
}
case kProtectionNackFEC: {
CriticalSectionScoped cs(_receiveCritSect);
if (enable) {
// Enable hybrid NACK/FEC. Always wait for retransmissions
// and don't add extra delay when RTT is above
// kLowRttNackMs.
_receiver.SetNackMode(kNack, media_optimization::kLowRttNackMs, -1);
_receiver.SetDecodeErrorMode(kNoErrors);
_receiver.SetDecodeErrorMode(kNoErrors);
} else {
_receiver.SetNackMode(kNoNack, -1, -1);
}
break;
}
case kProtectionNackSender:
case kProtectionFEC:
case kProtectionPeriodicKeyFrames:
// Ignore encoder modes.
return VCM_OK;
}
return VCM_OK;
}
```
下面則介紹本文的核心,decodable_frames_汇四,incomplete_frames_接奈,free_frames_的處理。
```
// Is the frame already in the decodable list?
bool continuous = IsContinuous(*frame);
switch (buffer_state) {
case kGeneralError:
case kTimeStampError:
case kSizeError: {
free_frames_.push_back(frame);
break;
}
case kCompleteSession: {
if (previous_state != kStateDecodable &&
previous_state != kStateComplete) {
CountFrame(*frame);
if (continuous) {
// Signal that we have a complete session.
frame_event_->Set();
}
}
FALLTHROUGH();
}
// Note: There is no break here - continuing to kDecodableSession.
case kDecodableSession: {
*retransmitted = (frame->GetNackCount() > 0);
if (continuous) {
decodable_frames_.InsertFrame(frame);
FindAndInsertContinuousFrames(*frame);
} else {
incomplete_frames_.InsertFrame(frame);
}
break;
}
case kIncomplete: {
if (frame->GetState() == kStateEmpty &&
last_decoded_state_.UpdateEmptyFrame(frame)) {
free_frames_.push_back(frame);
return kNoError;
} else {
incomplete_frames_.InsertFrame(frame);
}
break;
}
case kNoError:
case kOutOfBoundsPacket:
case kDuplicatePacket: {
// Put back the frame where it came from.
if (frame_list != NULL) {
frame_list->InsertFrame(frame);
} else {
free_frames_.push_back(frame);
}
++num_duplicated_packets_;
break;
}
case kFlushIndicator:
free_frames_.push_back(frame);
return kFlushIndicator;
default: assert(false);
}
```
其中第一句 bool continuous = IsContinuous(*frame);主要是判斷當(dāng)前收到幀和上一個(gè)解碼幀是不是連續(xù)的船殉。其中還有一些特殊情況鲫趁,如decode_error_mode_ == kWithErrors,或者frame->FrameType() == kVideoFrameKey等均判斷為連續(xù)的利虫。由于不加FEC和NACK時(shí)挨厚,decode_error_mode_ = kWithErrors,所以糠惫,一直continuous 為true疫剃。
只有kCompleteSession時(shí),才觸發(fā)事件frame_event_->Set();等待事件在
bool VCMJitterBuffer::NextCompleteTimestamp中硼讽,即取幀的函數(shù)中巢价。
注意:kCompleteSession情況,后面沒有break,則將完成的幀也插入decodable_frames_隊(duì)列固阁。
所以壤躲,對于VCMSessionInfo中的complete_ 和decodable_ ,都將插入decodable_frames_隊(duì)列备燃。
##再回頭看從jitterbuffer取出frame
```
// Returns immediately or a |max_wait_time_ms| ms event hang waiting for a
// complete frame, |max_wait_time_ms| decided by caller.
bool VCMJitterBuffer::NextCompleteTimestamp(
uint32_t max_wait_time_ms, uint32_t* timestamp) {
crit_sect_->Enter();
if (!running_) {
crit_sect_->Leave();
return false;
}
CleanUpOldOrEmptyFrames();
if (decodable_frames_.empty() ||
decodable_frames_.Front()->GetState() != kStateComplete)
{
const int64_t end_wait_time_ms = clock_->TimeInMilliseconds() +
max_wait_time_ms;
int64_t wait_time_ms = max_wait_time_ms;
while (wait_time_ms > 0) {
crit_sect_->Leave();
const EventTypeWrapper ret =
frame_event_->Wait(static_cast<uint32_t>(wait_time_ms));
crit_sect_->Enter();
if (ret == kEventSignaled) {
// Are we shutting down the jitter buffer?
if (!running_) {
crit_sect_->Leave();
return false;
}
// Finding oldest frame ready for decoder.
CleanUpOldOrEmptyFrames();
if (decodable_frames_.empty() ||
decodable_frames_.Front()->GetState() != kStateComplete) {
wait_time_ms = end_wait_time_ms - clock_->TimeInMilliseconds();
} else {
break;
}
} else {
break;
}
}
}
if (decodable_frames_.empty() ||
decodable_frames_.Front()->GetState() != kStateComplete) {
crit_sect_->Leave();
return false;
}
*timestamp = decodable_frames_.Front()->TimeStamp();
crit_sect_->Leave();
return true;
}
```
1碉克、其中decodable_frames_.Front()->GetState() 取得的_state,有下列情況賦值:
```
if (packet.frameType != kFrameEmpty) {
// first media packet
SetState(kStateIncomplete);
}
```
```
if (_sessionInfo.complete()) {
SetState(kStateComplete);
return kCompleteSession;
} else if (_sessionInfo.decodable()) {
SetState(kStateDecodable);
return kDecodableSession;
}
```
可見并齐,_state還是可以標(biāo)記這一幀數(shù)據(jù)的完成情況的漏麦,即完成時(shí),為kStateComplete况褪;未完成時(shí)撕贞,為kStateDecodable或kStateIncomplete,只有一個(gè)包時(shí)测垛,為kStateIncomplete捏膨。凡是沒有從kStateIncomplete升為kStateDecodable,則依然為kStateIncomplete食侮。
2脊奋、其中條件decodable_frames_.Front()->GetState() != kStateComplete
可見,必須是完整的幀才能夠從NextCompleteTimestamp函數(shù)中取出來疙描。
再看取出不完整的幀:
```
bool VCMJitterBuffer::NextMaybeIncompleteTimestamp(uint32_t* timestamp) {
CriticalSectionScoped cs(crit_sect_);
if (!running_) {
return false;
}
if (decode_error_mode_ == kNoErrors) {
// No point to continue, as we are not decoding with errors.
return false;
}
CleanUpOldOrEmptyFrames();
VCMFrameBuffer* oldest_frame;
if (decodable_frames_.empty()) {
if (nack_mode_ != kNoNack || incomplete_frames_.size() <= 1) {
return false;
}
oldest_frame = incomplete_frames_.Front();
// Frame will only be removed from buffer if it is complete (or decodable).
if (oldest_frame->GetState() < kStateComplete) {
return false;
}
} else {
oldest_frame = decodable_frames_.Front();
// If we have exactly one frame in the buffer, release it only if it is
// complete. We know decodable_frames_ is not empty due to the previous
// check.
if (decodable_frames_.size() == 1 && incomplete_frames_.empty() &&
oldest_frame->GetState() != kStateComplete) {
return false;
}
}
*timestamp = oldest_frame->TimeStamp();
return true;
}
```
從條件中可以看出:
1诚隙、decodable_frames_為空時(shí)
incomplete_frames_.Front()->GetState()為 kStateEmpty或者kStateIncomplete,則取不出幀起胰,否則可以取出久又。
2巫延、decodable_frames_不空時(shí)
decodable_frames_.size() == 1 && incomplete_frames_.empty() &&
oldest_frame->GetState() != kStateComplete時(shí),取不出幀地消。否則取出decodable_frames_中不完整的數(shù)據(jù)幀炉峰。
##總結(jié):
至此,jitterbuffer對于包脉执、幀的處理疼阔,已經(jīng)比較清晰。
所有組包的幀都存在于decodable_frames_半夷,incomplete_frames_隊(duì)列中婆廊,而decodable_frames_又根據(jù)狀態(tài)分為完整的幀和不完整的幀,incomplete_frames_主要保存狀態(tài)為kIncomplete的幀巫橄,也是不完整的淘邻,但有包數(shù)據(jù)。
而在取幀數(shù)據(jù)的時(shí)候先取完整的幀湘换,取不到宾舅,則取一定條件下不完整的幀數(shù)據(jù),不是有不完整的幀數(shù)據(jù)就去取彩倚。
所以筹我,如果你不想取出丟包的幀,則只調(diào)用NextCompleteTimestamp去取完整的幀即可帆离。因?yàn)槊看蝸淼陌槔#湃霂兄螅际遣迦氲絛ecodable_frames_或者incomplete_frames_的最前面盯质,所以不存在一直取不出來幀數(shù)據(jù)的情況。另外一個(gè)方法概而,就是設(shè)置decode_error_mode_ 為kNoErrors呼巷。
但是,不取出丟包的幀 赎瑰,不等于不存在馬賽克王悍。因?yàn)閬G幀,所以解碼的時(shí)候餐曼,可能會存在參考幀的問題压储。
##追加--如何杜絕馬賽克
由上述對于馬賽克存在的討論中可知,丟包是主要原因源譬,如果只取出完整的幀集惋,也會因?yàn)閰⒖紟瑔栴},導(dǎo)致馬賽克踩娘。
如果刮刑,丟包以后,就把整個(gè)GOP都刪除,直到下一個(gè)關(guān)鍵幀雷绢,同時(shí)泛烙,發(fā)現(xiàn)丟包就去請求關(guān)鍵幀,這樣就完全可以杜絕馬賽克了翘紊。但是這樣會導(dǎo)致在丟包時(shí)蔽氨,流暢度更加不好。