WebRtc Video Receiver(五)-設(shè)置參考幀

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瓣颅,RtpVideoStreamReceiver2OnCompleteFrameCallback之間的關(guān)系譬正。

    WebRtc_Video_Stream_Receiver_05_01.png

  • 根據(jù)上圖的關(guān)系圖宫补,在RtpFrameReferenceFinder模塊中對video_coding::RtpFrameObject數(shù)據(jù)幀進行處理,如果處理成功最終會生成video_coding::EncodedFrame視頻幀曾我,接著回調(diào)OnCompleteFrameCallbackOnCompleteFrame函數(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的概念。

    WebRtc_Video_Stream_Receiver_05_02.png

  • 以上以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模塊。

    WebRtc_Video_Stream_Receiver_05_03.png

  • 該容器是以當(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的前向參考易茬。
    WebRtc_Video_Stream_Receiver_05_04.png
  • 該函數(shù)的決策主要是通過判斷seq的連續(xù)性(是否有參考幀)或者是否是關(guān)鍵幀,來決定當(dāng)前幀是否要發(fā)到解碼模塊及老,或者是進行存儲抽莱,當(dāng)出現(xiàn)丟幀現(xiàn)象的時候,需要緩存當(dāng)前幀然后等待丟失的幀重傳。
  • 到此為止骄恶,gop容器last_seq_num_gop_的數(shù)據(jù)成員如下:
    WebRtc_Video_Stream_Receiver_05_05.png

    WebRtc_Video_Stream_Receiver_05_06.png

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模塊等。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載硝全,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者栖雾。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市伟众,隨后出現(xiàn)的幾起案子析藕,更是在濱河造成了極大的恐慌,老刑警劉巖凳厢,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件账胧,死亡現(xiàn)場離奇詭異,居然都是意外死亡先紫,警方通過查閱死者的電腦和手機治泥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遮精,“玉大人居夹,你說我怎么就攤上這事”境澹” “怎么了准脂?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長檬洞。 經(jīng)常有香客問我狸膏,道長,這世上最難降的妖魔是什么添怔? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任湾戳,我火速辦了婚禮,結(jié)果婚禮上广料,老公的妹妹穿的比我還像新娘砾脑。我一直安慰自己,他們只是感情好性昭,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布拦止。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汹族。 梳的紋絲不亂的頭發(fā)上萧求,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音顶瞒,去河邊找鬼夸政。 笑死,一個胖子當(dāng)著我的面吹牛榴徐,可吹牛的內(nèi)容都是我干的守问。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼坑资,長吁一口氣:“原來是場噩夢啊……” “哼耗帕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起袱贮,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤仿便,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后攒巍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嗽仪,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年柒莉,在試婚紗的時候發(fā)現(xiàn)自己被綠了闻坚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡兢孝,死狀恐怖窿凤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跨蟹,我是刑警寧澤卷玉,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站喷市,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏威恼。R本人自食惡果不足惜品姓,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箫措。 院中可真熱鬧腹备,春花似錦、人聲如沸斤蔓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至友驮,卻和暖如春漂羊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卸留。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工走越, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耻瑟。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓旨指,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喳整。 傳聞我的和親對象是個殘疾皇子谆构,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345