本文分析了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)
...
//若在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)
//真正的处理逻辑在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)
//按照不同的编码类型,进行不同的参考帧信息设置,对于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)
//若该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()
//尝试对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);
}