WebRTC源码阅读——视频参考帧info设置

2021-08-02 15:24:17 浏览数 (1)

本文分析了Google WebRTC h264编码的视频参考帧info设置的相关源码,给出了参考帧info的处理流程分析,为避免文章内容过多,文中对于关键函数的分析仅给出关键内容的说明,没有贴完整的源代码。文中所分析内容均基于WebRTC M86版本。

视频参考帧info设置

1.概括

视频帧在编码的时候是有参考帧概念存在的,对于I帧可独立解码,不需要参考,但对于P帧或B帧(webrtc iOS端采用h264硬编码时,并没有设置B帧)在解码时必须保证其所参考的帧可以被解码。故在组帧结束以后,还需要对视频帧的参考帧信息进行设置,否则在取帧送入到解码器之前无法判断其是否可以被解码。

2.关键函数说明

本文内容着重分析webrtc源码中的rtp_video_stream_receiver2.cc和rtp_frame_reference_finder.cc文件的参考帧信息设置部分。

RtpVideoStreamReceiver2::OnAssembledFrame函数拿到组帧后的RtpFrameObject对象frame,按需重置reference_finder_,随后将frame送入到buffered_frame_decryptor_frame_transformer_delegate_reference_finder_进行不同的处理,本文主要分析送入到reference_finder_的参考帧信息处理过程。由于未接触过vp8、vp9编码,故本文涉及编码类型的分析均针对h264编码。h264编码的参考帧信息包含id.picture_id(该帧的参考id,由包序号计算得到)、num_references(参考帧的数量,该值为0或1)、references0(参考帧的参考id)。

rtp_video_stream_receiver2.cc
  • void RtpVideoStreamReceiver2::OnAssembledFrame( std::unique_ptr<video_coding::RtpFrameObject> frame)
代码语言:txt复制
...
//若在frame之前未接收到任何帧,且该帧不是关键帧,则需要重新请求关键帧,否则后面是无法进行解码的。这里存在一个loss_notification_controller_,查了一下源码当编码类型有设置kRtcpFbParamLntf类型的FeedbackParam时才会存在,目前是只在vp8编码下且打开"WebRTC-RtcpLossNotification"才会存在。
 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;
  }
  ...
  //这里主要是用于重置reference_finder_,若当前帧编码类型与已记录的编码类型不一致,并且当前帧比已记录的帧时间戳要新,则重置reference_finder_,注意重置的时候为避免picture id重叠,picture_id_offset_是有加一个偏移量的。
  if (current_codec_) {
    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();
  }
// 按需将frame送入到不同的处理中
  if (buffered_frame_decryptor_ != nullptr) {
    //对加密帧进行解密处理,解密完成后依旧是送入reference_finder_->ManageFrame进行处理
    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));
  }
rtp_frame_reference_finder.cc

RtpFrameReferenceFinder中利用last_seq_num_gop_存储最近的gop表,last_seq_num_gop_是一个map,其key值为关键帧的结束packet包的序列号,value值是一个pair,其第一个值为key关键帧所在gop内的最后一个完整帧的结束packet包的序列号,value的第二个值为key关键帧所在gop内接收到的最后一个packet包的序号。注意这里value的第一个值与第二个值的区别,这里主要是因为接收到的包存在payload.size为0的情况,即属于padding包(存储于RtpFrameReferenceFinder中的stashed_padding_),导致二者的值不一定相等。

  • void RtpFrameReferenceFinder::ManageFrame( std::unique_ptr<RtpFrameObject> frame)
代码语言:txt复制
//真正的处理逻辑在ManageFrameInternal函数中,依据不同的处理结果,对这一帧数据进行暂存、继续传递(插入到frame_buffer)、丢弃处理。若结果为kHandOff,则暂存在stashed_frames_中的帧,若参考frame,则可能被正确处理,故调用RetryStashedFrames()尝试处理暂存的帧的参考帧信息。
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:
      //调用frame->id.picture_id  = picture_id_offset_;更新frame的参考id,并将frame交回到RtpVideoStreamReceiver2:: OnCompleteFrame函数中。
      HandOffFrame(std::move(frame));
      RetryStashedFrames();
      break;
    case kDrop:
      break;
  }
  • RtpFrameReferenceFinder::FrameDecision RtpFrameReferenceFinder::ManageFrameInternal(RtpFrameObject* frame)
代码语言:txt复制
//按照不同的编码类型,进行不同的参考帧信息设置,对于h264走默认ManageFramePidOrSeqNum逻辑
 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);
  }

H264处理

  • RtpFrameReferenceFinder::FrameDecision RtpFrameReferenceFinder::ManageFramePidOrSeqNum(RtpFrameObject* frame, int picture_id)
代码语言:txt复制
//若该frame是关键帧,则在last_seq_num_gop_中插入该frame的最后一个packet的序列号,注意其value值将在后面处理中变化。

last_seq_num_gop_.insert(std::make_pair(
        frame->last_seq_num(),
        std::make_pair(frame->last_seq_num(), frame->last_seq_num())));`
 //若当前last_seq_num_gop_为空,说明没有关键帧,那么这个frame也是无法正确设置参考信息的,所以先暂存。      
 if (last_seq_num_gop_.empty())
    return kStash;  

//若last_seq_num_gop_中未找到该frame间接参考的关键帧,则丢弃该frame,返回kDrop
 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;
  }
//令seq_num_it指向frame间接参考的关键帧
  seq_num_it--;          

//若该frame不是关键帧,并且不满足连续性,说明其前面存在丢包,所以暂存该帧。
uint16_t last_picture_id_gop = seq_num_it->second.first;
uint16_t last_picture_id_with_padding_gop = seq_num_it->second.second;
if (frame->frame_type() == VideoFrameType::kVideoFrameDelta) {
uint16_t prev_seq_num = frame->first_seq_num() - 1;

if (prev_seq_num != last_picture_id_with_padding_gop)
  return kStash;
}

//设置frame的参考帧信息
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);
if (AheadOf<uint16_t>(frame->id.picture_id, last_picture_id_gop)) {
seq_num_it->second.first = frame->id.picture_id;
seq_num_it->second.second = frame->id.picture_id;

//更新frame在last_seq_num_gop_中value的第二个值
UpdateLastPictureIdWithPadding(frame->id.picture_id);
frame->id.picture_id = rtp_seq_num_unwrapper_.Unwrap(frame->id.picture_id);
}
  • void RtpFrameReferenceFinder::RetryStashedFrames()
代码语言:txt复制
//尝试对stashed_frames_中的frame再次处理参考帧信息
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));
          ABSL_FALLTHROUGH_INTENDED;
        case kDrop:
          frame_it = stashed_frames_.erase(frame_it);
      }
    }
  } while (complete_frame);
}

0 人点赞