天天看點

WebRTC系列之JitterBuffer(1)

在音視訊網絡傳輸過程中,由于存在網路抖動情況,接收端視訊接受不及時導緻播放卡頓,為了消除幀間抖動情況,一個解決手段是JitterBuffer。JitterBuffer包括RTP包的排序,GOP内幀排序以及GOP間排序。(文末注解名詞解釋)。

1、RTP包排序:PacketBuffer

1.1 插入RTP資料包(PacketBuffer::InsertPacket)

這個函數首先判斷是不是首包,是的話就記錄一下,接下來的包開始往後排序,不是的話就調用包序列号比較函數AheadOf。在利用索引計算的包在緩存中的位置如果被占用并且序列号一樣,就是重複包,丢掉。如果被占用但是序列号不相同,就說明緩存滿了,需要擴容,重新計算包的索引值,擴容後還是滿的就要情況緩存了

PacketBuffer::InsertResult PacketBuffer::InsertPacket(
    std::unique_ptr<PacketBuffer::Packet> packet) {
  PacketBuffer::InsertResult result;
  //目前包序号
  uint16_t seq_num = packet->seq_num;
  //目前包在緩存中的索引
  size_t index = seq_num % buffer_.size();

  if (!first_packet_received_) {
    //儲存第一個包
    first_seq_num_ = seq_num;
    //第一個包的序号
    first_packet_received_ = true;
    //收到了第一個包
  } else if (AheadOf(first_seq_num_, seq_num)) {
    // If we have explicitly cleared past this packet then it's old,
    // don't insert it, just silently ignore it.
    // 如果目前包比之前記錄的第一個包first_seq_num_還老
    // 并且之前已經清理過第一個包序列号,說明已經至少成功解碼過一幀,RtpVideoStreamReceiver::FrameDecoded
    // 會調用PacketBuffer::ClearTo(seq_num),清理first_seq_num_之前的所有緩存,這個時候還來一個比first_seq_num_還
    // 老的包,就沒有必要再留着了。
    if (is_cleared_to_first_seq_num_) {
      return result;
    }
    // 相反如果沒有被清理過,則是有必要保留成第一個包的,比如發生了亂序。
    first_seq_num_ = seq_num;
  }
  //如果緩存的槽被占了,而且序号一樣,說明是重複包,丢掉
  if (buffer_[index] != nullptr) {
    // Duplicate packet, just delete the payload.
    if (buffer_[index]->seq_num == packet->seq_num) {
      return result;
    }

    // The packet buffer is full, try to expand the buffer.
    // 如果槽被占,但是輸入包和對應槽的包序列号不等,說明緩存滿了,需要擴容。
    // ExpandBufferSize() 會更新緩存在新的隊列的位置,并不會引起位置錯誤
    while (ExpandBufferSize() && buffer_[seq_num % buffer_.size()] != nullptr) {
    }
    // 重新計算輸入包索引.
    index = seq_num % buffer_.size();

    // Packet buffer is still full since we were unable to expand the buffer.
    // 如果對應的槽還是被占用了,還是滿,已經不行了,緻命錯誤.
    if (buffer_[index] != nullptr) {
      // Clear the buffer, delete payload, and return false to signal that a
      // new keyframe is needed.
      RTC_LOG(LS_WARNING) << "Clear PacketBuffer and request key frame.";
      ClearInternal();
      result.buffer_cleared = true;
      return result;
    }
  }
  //之前的包是否連續,這裡初始為false,在FindFrames中置位
  packet->continuous = false;
  //此處的move移動語義提升了效率
  buffer_[index] = std::move(packet);
  // 更新丢包資訊,檢查收到目前包後是否有丢包導緻的空洞,也就是不連續.
  UpdateMissingPackets(seq_num);

  result.packets = FindFrames(seq_num);
  return result;
}           

1.2 插入填充包(PacketBuffer::InsertPadding)

這裡的填充包類似于濫竽充數,主要是由于發送端為了滿足輸出碼率的情況下進行的Padding,

PacketBuffer::InsertResult PacketBuffer::InsertPadding(uint16_t seq_num) {
  PacketBuffer::InsertResult result;
  // 更新丢包資訊,檢查收到目前包後是否有丢包導緻的空洞,也就是不連續.
  UpdateMissingPackets(seq_num);
  // 分析排序緩存,檢查是否能夠組裝出完整的幀并傳回.
  result.packets = FindFrames(static_cast<uint16_t>(seq_num + 1));
  return result;
}           

1.3 丢包檢測(PacketBuffer::UpdateMissingPackets)

這個函數主要完成的是包是否是連續的,主要靠丢包緩存missing_packets_維護包序列号。

void PacketBuffer::UpdateMissingPackets(uint16_t seq_num) {
  // 如果最新插入的包序列号還未設定過,這裡直接設定一次.
  if (!newest_inserted_seq_num_)
    newest_inserted_seq_num_ = seq_num;

  const int kMaxPaddingAge = 1000;
  // 如果目前包的序列号新于之前的最新包序列号,沒有發生亂序
  if (AheadOf(seq_num, *newest_inserted_seq_num_)) {
    // 丢包緩存missing_packets_最大儲存1000個包,這裡得到目前包1000個包以前的序列号,
    // 也就差不多是丢包緩存裡應該儲存的最老的包.
    uint16_t old_seq_num = seq_num - kMaxPaddingAge;
    // 第一個>= old_seq_num的包的位置
    auto erase_to = missing_packets_.lower_bound(old_seq_num);
    // 删除丢包緩存裡所有1000個包之前的所有包(如果有的話)
    missing_packets_.erase(missing_packets_.begin(), erase_to);

    // Guard against inserting a large amount of missing packets if there is a
    // jump in the sequence number.
    // 如果最老的包的序列号都比目前最新包序列号新,那麼更新一下目前最新包序列号
    if (AheadOf(old_seq_num, *newest_inserted_seq_num_))
      *newest_inserted_seq_num_ = old_seq_num;
    // 因為seq_num >newest_inserted_seq_num_,這裡開始統計(newest_inserted_seq_num_, sum)之間的空洞.
    ++*newest_inserted_seq_num_;
    // 從newest_inserted_seq_num_開始,每個小于目前seq_num的包都進入丢包緩存,直到newest_inserted_seq_num_ ==
    // seq_num,也就是最新包的序列号變成了目前seq_num.
    while (AheadOf(seq_num, *newest_inserted_seq_num_)) {
      missing_packets_.insert(*newest_inserted_seq_num_);
      ++*newest_inserted_seq_num_;
    }
  } else {
    // 如果目前收到的包的序列号小于目前收到的最新包序列号,則從丢包緩存中删除(之前應該已經進入丢包緩存)
    missing_packets_.erase(seq_num);
  }
}           

1.4 連續包檢測(PacketBuffer::PotentialNewFrame)

主要作用就是檢測目前包的前面是否連續,連續的話才會進行完整幀的檢測。

bool PacketBuffer::PotentialNewFrame(uint16_t seq_num) const {
  // 通過序列号擷取緩存索引
  size_t index = seq_num % buffer_.size();
  // 上個包的索引
  int prev_index = index > 0 ? index - 1 : buffer_.size() - 1;
  const auto& entry = buffer_[index];
  const auto& prev_entry = buffer_[prev_index];
  // 如果目前包的槽位沒有被占用,那麼該包之前沒有處理過,不連續
  if (entry == nullptr)
    return false;
  // 如果目前包的槽位的序列号和目前包序列号不一緻,不連續.
  if (entry->seq_num != seq_num)
    return false;
  // 如果目前包的幀開始辨別frame_begin為true,那麼該包是幀第一個包,連續.
  if (entry->is_first_packet_in_frame())
    return true;
  // 如果上個包的槽位沒有被占用,那麼上個包之前沒有處理過,不連續.  
  if (prev_entry == nullptr)
    return false;
  // 如果上個包和目前包的序列号不連續,不連續.
  if (prev_entry->seq_num != static_cast<uint16_t>(entry->seq_num - 1))
    return false;
  // 如果上個包的時間戳和目前包的時間戳不相等,不連續.
  if (prev_entry->timestamp != entry->timestamp)
    return false;
  // 排除掉以上所有錯誤後,如果上個包連續,則可以認為目前包連續.
  if (prev_entry->continuous)
    return true;
  // 如果上個包不連續或者有其他錯誤,就傳回不連續.
  return false;
}           

1.5 幀的完整性檢測(PacketBuffer::FindFrames)

PacketBuffer::FindFrames函數會周遊排序緩存中連續的包,檢查一幀的邊界,但是這裡對VPX和H264的處理做了區分:

對VPX,這個函數認為包的frame_begin可信,這樣VPX的完整一幀就完全依賴于檢測到frame_begin和frame_end這兩個包;

對H264,這個函數認為包的frame_begin不可信,并不依賴frame_begin來判斷幀的開始,但是frame_end仍然是可信的,具體說H264的開始辨別是通過從frame_end辨別的一幀最後一個包向前追溯,直到找到一個時間戳不一樣的斷層,認為找到了完整的一個H264的幀。

另外這裡對H264的P幀做了一些特殊處理,雖然P幀可能已經完整,但是如果該P幀前面仍然有丢包空洞,不會立刻向後傳遞,會等待直到所有空洞被填滿,因為P幀必須有參考幀才能正确解碼。

C++音視訊學習資料免費擷取方法:關注音視訊開發T哥,點選「連結」即可免費擷取2023年最新C++音視訊開發進階獨家免費學習大禮包!

std::vector<std::unique_ptr<PacketBuffer::Packet>> PacketBuffer::FindFrames(
    uint16_t seq_num) {
  std::vector<std::unique_ptr<PacketBuffer::Packet>> found_frames;
  // 基本算法:周遊所有連續包,先找到帶有frame_end辨別的幀最後一個包,然後向前回溯,
  // 找到幀的第一個包(VPX是frame_begin, H264是時間戳不連續),組成完整一幀,
  // PotentialNewFrame(seq_num)檢測seq_num之前的所有包是否連續.
  for (size_t i = 0; i < buffer_.size() && PotentialNewFrame(seq_num); ++i) {
    // 目前包的緩存索引
    size_t index = seq_num % buffer_.size();
    // 如果seq_num之前所有包連續,那麼seq_num自己也連續.
    buffer_[index]->continuous = true;

    // If all packets of the frame is continuous, find the first packet of the
    // frame and add all packets of the frame to the returned packets.
    // 找到了幀的最後一個包.
    if (buffer_[index]->is_last_packet_in_frame()) {
      // 幀開始序列号,從幀尾部開始.
      uint16_t start_seq_num = seq_num;

      // Find the start index by searching backward until the packet with
      // the |frame_begin| flag is set.
      // 開始向前回溯,找幀的第一個包.
      // 幀開始的索引,從幀尾部開始.
      int start_index = index;
      // 已經測試的包數
      size_t tested_packets = 0;
      // 目前包的時間戳. 也就是幀的時間戳
      int64_t frame_timestamp = buffer_[start_index]->timestamp;

      // Identify H.264 keyframes by means of SPS, PPS, and IDR.
      bool is_h264 = buffer_[start_index]->codec() == kVideoCodecH264;
      bool has_h264_sps = false;
      bool has_h264_pps = false;
      bool has_h264_idr = false;
      bool is_h264_keyframe = false;
      int idr_width = -1;
      int idr_height = -1;
      // 從幀尾部的包開始回溯.
      while (true) {
        // 測試包數++
        ++tested_packets;
        // 如果是VPX,并且找到了frame_begin辨別的第一個包,一幀完整,回溯結束.
        if (!is_h264 && buffer_[start_index]->is_first_packet_in_frame())
          break;
        // h264 判斷方式
        if (is_h264) {
          //擷取h264 相關資訊,
          const auto* h264_header = absl::get_if<RTPVideoHeaderH264>(
              &buffer_[start_index]->video_header.video_type_header);
          if (!h264_header || h264_header->nalus_length >= kMaxNalusPerPacket)
            return found_frames;
          // 周遊所有NALU,注意WebRTC所有IDR幀前面都會帶SPS、PPS.
          for (size_t j = 0; j < h264_header->nalus_length; ++j) {
            if (h264_header->nalus[j].type == H264::NaluType::kSps) {
              has_h264_sps = true;
            } else if (h264_header->nalus[j].type == H264::NaluType::kPps) {
              has_h264_pps = true;
            } else if (h264_header->nalus[j].type == H264::NaluType::kIdr) {
              has_h264_idr = true;
            }
          }
          // 預設sps_pps_idr_is_h264_keyframe_為false,也就是說隻需要有IDR幀就認為是關鍵幀,
          // 而不需要等待SPS、PPS完整.
          if ((sps_pps_idr_is_h264_keyframe_ && has_h264_idr && has_h264_sps &&
               has_h264_pps) ||
              (!sps_pps_idr_is_h264_keyframe_ && has_h264_idr)) {
            is_h264_keyframe = true;
            // Store the resolution of key frame which is the packet with
            // smallest index and valid resolution; typically its IDR or SPS
            // packet; there may be packet preceeding this packet, IDR's
            // resolution will be applied to them.
            if (buffer_[start_index]->width() > 0 &&
                buffer_[start_index]->height() > 0) {
              idr_width = buffer_[start_index]->width();
              idr_height = buffer_[start_index]->height();
            }
          }
        }
        // 如果檢測包數已經達到緩存容量,中止.
        if (tested_packets == buffer_.size())
          break;

        start_index = start_index > 0 ? start_index - 1 : buffer_.size() - 1;

        // In the case of H264 we don't have a frame_begin bit (yes,
        // |frame_begin| might be set to true but that is a lie). So instead
        // we traverese backwards as long as we have a previous packet and
        // the timestamp of that packet is the same as this one. This may cause
        // the PacketBuffer to hand out incomplete frames.
        // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=7106
        // 這裡保留了注釋,可以看看H264不使用frame_begin的原因,
        //已timestamp發生變化,認為是一幀結束
        if (is_h264 && (buffer_[start_index] == nullptr ||
                        buffer_[start_index]->timestamp != frame_timestamp)) {
          break;
        }
        // 如果仍然在一幀内,開始包序列号--.
        --start_seq_num;
      }
      //如果沒有sps或者pps 異常警告
      if (is_h264) {
        // Warn if this is an unsafe frame.
        if (has_h264_idr && (!has_h264_sps || !has_h264_pps)) {
          RTC_LOG(LS_WARNING)
              << "Received H.264-IDR frame "
                 "(SPS: "
              << has_h264_sps << ", PPS: " << has_h264_pps << "). Treating as "
              << (sps_pps_idr_is_h264_keyframe_ ? "delta" : "key")
              << " frame since WebRTC-SpsPpsIdrIsH264Keyframe is "
              << (sps_pps_idr_is_h264_keyframe_ ? "enabled." : "disabled");
        }

        // Now that we have decided whether to treat this frame as a key frame
        // or delta frame in the frame buffer, we update the field that
        // determines if the RtpFrameObject is a key frame or delta frame.
        //幀的起始位置
        const size_t first_packet_index = start_seq_num % buffer_.size();
        // 設定資料緩存中的關鍵幀辨別.
        if (is_h264_keyframe) {
          buffer_[first_packet_index]->video_header.frame_type =
              VideoFrameType::kVideoFrameKey;
          if (idr_width > 0 && idr_height > 0) {
            // IDR frame was finalized and we have the correct resolution for
            // IDR; update first packet to have same resolution as IDR.
            buffer_[first_packet_index]->video_header.width = idr_width;
            buffer_[first_packet_index]->video_header.height = idr_height;
          }
        } else {
          buffer_[first_packet_index]->video_header.frame_type =
              VideoFrameType::kVideoFrameDelta;
        }

        // If this is not a keyframe, make sure there are no gaps in the packet
        // sequence numbers up until this point.
        // 這個條件是說在丢包的清單裡搜尋>start_seq_num(幀開始序列号)的第一個位置,
        // 發現其不等于丢包清單的開頭, 有些丢的包序列号小于start_seq_num,
        // 也就是說P幀前面有丢包空洞, 舉例1: missing_packets_ = { 3, 4, 6},
        // start_seq_num = 5, missing_packets_.upper_bound(start_seq_num)==6
        // 作為一幀開始位置的序列号5,前面還有3、4這兩個包還未收到,那麼對P幀來說,雖然完整,但是向後傳遞也可能是沒有意義的,
        // 是以這裡又清除了frame_created狀态,先繼續緩存,等待丢包的空洞填滿.
        // 舉例2:
        // missing_packets_ = { 10, 16, 17}, start_seq_num = 3,
        // missing_packets_.upper_bound(start_seq_num)==10
        // 作為一幀開始位置的序列号3,前面并沒有丢包,并且幀完整,那麼可以向後傳遞.
        if (!is_h264_keyframe && missing_packets_.upper_bound(start_seq_num) !=
                                     missing_packets_.begin()) {
          return found_frames;
        }
      }

      const uint16_t end_seq_num = seq_num + 1;
      // Use uint16_t type to handle sequence number wrap around case.
      uint16_t num_packets = end_seq_num - start_seq_num;
      found_frames.reserve(found_frames.size() + num_packets);
      for (uint16_t i = start_seq_num; i != end_seq_num; ++i) {
        std::unique_ptr<Packet>& packet = buffer_[i % buffer_.size()];
        RTC_DCHECK(packet);
        RTC_DCHECK_EQ(i, packet->seq_num);
        // Ensure frame boundary flags are properly set.
        packet->video_header.is_first_packet_in_frame = (i == start_seq_num);
        packet->video_header.is_last_packet_in_frame = (i == seq_num);
        found_frames.push_back(std::move(packet));
      }
      // 馬上要組幀了,清除丢包清單中到幀開始位置之前的丢包.
      // 對H264 P幀來說,如果P幀前面有空洞不會運作到這裡,在上面已經解釋.
      // 對I幀來說,可以丢棄前面的丢包資訊(?).
      missing_packets_.erase(missing_packets_.begin(),
                             missing_packets_.upper_bound(seq_num));
    }
    // 向後擴大搜尋的範圍,假設丢包、亂序,目前包的seq_num剛好填補了之前的一個空洞,
    // 該包并不能檢測出一個完整幀,需要這裡向後移動指針到frame_end再進行回溯,直到檢測出完整幀,
    // 這裡會繼續檢測之前緩存的因為前面有空洞而沒有向後傳遞的P幀。
    ++seq_num;
  }
  return found_frames;
}           

2、幀的排序

一個GOP内P幀依賴前面的P幀和I關鍵幀。,RtpFrameReferenceFinder就是要找到每個幀的參考幀。I幀是GOP起始幀自參考,後續GOP内每個幀都要參考上一幀。RtpFrameReferenceFinder維護最近的GOP表,收到P幀後,RtpFrameReferenceFinder找到P幀所屬的GOP,将P幀的參考幀設定為GOP内該幀的上一幀,之後傳遞給FrameBuffer。

2.1 設定參考幀(RtpSeqNumOnlyRefFinder::ManageFrameInternal)

這個函數主要是處理GOP内幀的連續性以及設定參考幀。

RtpSeqNumOnlyRefFinder::FrameDecision
RtpSeqNumOnlyRefFinder::ManageFrameInternal(RtpFrameObject* frame) {
  // 如果是關鍵幀,插入GOP表,key是last_seq_num,初始value是{last_seq_num,last_seq_num}
  if (frame->frame_type() == VideoFrameType::kVideoFrameKey) {
    last_seq_num_gop_.insert(std::make_pair(
        frame->last_seq_num(),
        std::make_pair(frame->last_seq_num(), frame->last_seq_num())));
  }

  // We have received a frame but not yet a keyframe, stash this frame.
  // 如果GOP表空,那麼就不可能找到參考幀,先緩存.
  if (last_seq_num_gop_.empty())
    return kStash;

  // Clean up info for old keyframes but make sure to keep info
  // for the last keyframe.
  // 删除較老的關鍵幀(PID小于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.
  // 在GOP表中搜尋第一個比目前幀新的關鍵幀。
  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;
  }
  // 如果搜尋到的關鍵幀不是最老的,那麼搜尋到的關鍵幀的上一個關鍵幀所在的GOP裡應該可以找到參考幀,
  // 如果找不到關鍵幀,seq_num_it為end(), seq_num_it--則為最後一個關鍵幀.
  seq_num_it--;

  // Make sure the packet sequence numbers are continuous, otherwise stash
  // this frame.
  // 保證幀的連續,不連續則先緩存.
  // 目前GOP的最新一個幀的最後一個包的序列号.
  uint16_t last_picture_id_gop = seq_num_it->second.first;
  // 目前GOP的最新包的序列号,可能是last_picture_id_gop, 也可能是填充包.
  uint16_t last_picture_id_with_padding_gop = seq_num_it->second.second;
  // P幀的連續性檢查.
  if (frame->frame_type() == VideoFrameType::kVideoFrameDelta) {
    // 獲得P幀第一個包的上個包的序列号.
    uint16_t prev_seq_num = frame->first_seq_num() - 1;
    // 如果P幀第一個包的上個包的序列号與目前GOP的最新包的序列号不等,說明不連續,先緩存.
    if (prev_seq_num != last_picture_id_with_padding_gop)
      return kStash;
  }
  // 現在這個幀是連續的了
  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.
  // 獲得目前幀的最後一個包的序列号,設定為初始PID,後面還會設定一次Unwrap.
  frame->SetId(frame->last_seq_num());
  // 設定幀的參考幀數,P幀才需要1個參考幀.
  frame->num_references =
      frame->frame_type() == VideoFrameType::kVideoFrameDelta;
  // 設定參考幀為目前GOP的最新一個幀的最後一個包的序列号,
  // 既然該幀是連續的,那麼其參考幀自然也就是上個幀.
  frame->references[0] = rtp_seq_num_unwrapper_.Unwrap(last_picture_id_gop);
  // 如果目前幀比目前GOP的最新一個幀的最後一個包還新,則更新GOP的最新一個幀的最後一個包(first)
  // 以及GOP的最新包(second).
  if (AheadOf<uint16_t>(frame->Id(), last_picture_id_gop)) {
    seq_num_it->second.first = frame->Id();// 更新GOP的最新一個幀的最後一個包
    seq_num_it->second.second = frame->Id();// 更新GOP的最新包,可能被填充包更新.
  }
  // 更新填充包狀态.
  UpdateLastPictureIdWithPadding(frame->Id());
  frame->SetSpatialIndex(0);
  // 設定目前幀的PID為Unwrap形式.
  frame->SetId(rtp_seq_num_unwrapper_.Unwrap(frame->Id()));
  return kHandOff;           

2.2 處理Padding(RtpSeqNumOnlyRefFinder::PaddingReceived)

該函數更新填充包,如果填充包填補了GOP内的序列号空洞,那麼P就可以是連續的,嘗試處理P幀。

RtpFrameReferenceFinder::ReturnVector RtpSeqNumOnlyRefFinder::PaddingReceived(
    uint16_t seq_num) {
  // 隻保留最近100個填充包.
  auto clean_padding_to =
      stashed_padding_.lower_bound(seq_num - kMaxPaddingAge);
  stashed_padding_.erase(stashed_padding_.begin(), clean_padding_to);
  // 緩存填充包.
  stashed_padding_.insert(seq_num);
  // 更新填充包狀态.
  UpdateLastPictureIdWithPadding(seq_num);
  RtpFrameReferenceFinder::ReturnVector res;
  // 嘗試處理一次緩存的P幀,有可能序列号連續了.
  RetryStashedFrames(res);
  return res;
}           

3處理緩存的包(RtpSeqNumOnlyRefFinder::RetryStashedFrames)

最常見的是找到帶有參考幀的連續幀,如果遇到上述說的Padding包序列号剛好滿足的情況時,也會嘗試處理。

void RtpSeqNumOnlyRefFinder::RetryStashedFrames(
    RtpFrameReferenceFinder::ReturnVector& res) {
  bool complete_frame = false;
  // 周遊緩存的幀
  do {
    complete_frame = false;
    for (auto frame_it = stashed_frames_.begin();
         frame_it != stashed_frames_.end();) {
      // 調用ManageFramePidOrSeqNum來處理一個緩存幀,檢查是否可以輸出帶參考幀的連續的幀.
      FrameDecision decision = ManageFrameInternal(frame_it->get());

      switch (decision) {
        case kStash:// 仍然不連續,或者沒有參考幀.
          ++frame_it;// 檢查下一個緩存幀.
          break;
        case kHandOff:// 找到了一個帶參考幀的連續的幀.
          complete_frame = true;
          res.push_back(std::move(*frame_it));
          ABSL_FALLTHROUGH_INTENDED;
        case kDrop:// 無論kHandOff、kDrop都可以從緩存中删除了.
          frame_it = stashed_frames_.erase(frame_it);// 删除并檢查下一個緩存幀.
      }
    }
  } while (complete_frame);// 如果能持續找到帶參考幀的連續的幀則繼續.
}           

今天先寫這麼多,具體參考連結是一位大神寫的部落格,具體連結附上:

http://t.csdn.cn/Sf0Dl

後續内容有時間補上。

原文連結:WebRTC系列之JitterBuffer(1)