webrtc qos模块源码走读 一

视频

接收数据

  • 入口
    rtp_stream_receiver.cc 文件分析
bool RtpStreamReceiver::DeliverRtp(const uint8_t* rtp_packet,
                                   size_t rtp_packet_length,
                                   const PacketTime& packet_time)

rtp_packet :rtp包数据
rtp_packet _length: rtp包数据长度
packet_time: rtp包时间

  • DeliverRtp() 方法过程分析
1. 接收控制开关
  {
    rtc::CritScope lock(&receive_cs_);
    if (!receiving_) {
      return false;
    }
  }

receiving_ 是接收rtp数据的开关,由两个方法控制:void RtpStreamReceiver::StartReceive() {..} void RtpStreamReceiver::StopReceive() {..}

2. rtp头解析
  RTPHeader header;
  if (!rtp_header_parser_->Parse(rtp_packet, rtp_packet_length,
                                 &header)) {
    return false;
  }

rtp_header_parser_ 指针是在 RtpStreamReceiver 构造函数里面创建的,实例是class RtpHeaderParserImpl

RTPHeader 结构内容描述:

struct RTPHeader {
  RTPHeader(); //初始化
 // 1 bit  该位的功能依赖于profile的定义。profile可以改变该位的长度,但是要保持marker和payload type总长度不变(一共是8 bit)。
  bool markerBit;  
  // 7 bits 标记着RTP packet所携带信息的类型,标准类型列出在RFC3551 中。如果接收方不能识别该类型,必须忽略该packet。 
  uint8_t payloadType; 
 // 16 bits 序列号,每个RTP packet发送后该序列号加1,接收方可以根据该序列号重新排列数据包顺序。 
  uint16_t sequenceNumber;
 //  32 bits 时间戳。反映RTP packet所携带信息包中第一个字节的采样时间。 
  uint32_t timestamp;
 // 32 bits 标识数据源。在一个RTP Session其间每个数据流都应该有一个不同的SSRC。 
  uint32_t ssrc;
 // 4 bits  在固定头部后存在多少个CSRC标记。
  uint8_t numCSRCs;
 // 0 to 15 items, 32 bits each 标识贡献的数据源。只有存在Mixer的时候才有效。如一个将多声道的语音流合并成一个单声道的语音流,在这里就列出原来每个声道的SSRC。
  uint32_t arrOfCSRCs[kRtpCsrcSize];
 // 1 bit 如果该位被设置,则在该packet末尾包含了额外的附加信息,附加信息的最后一个字节表示额外附加信息的长度(包含该字节本身)。该字段之所以存在是因为一些加密机制需要固定长度的数据块,或者为了在一个底层协议数据单元中传输多个RTP packets。 
  size_t paddingLength;
 // 头部长度
  size_t headerLength;
// 时钟频率,视频给的是 const int kVideoPayloadTypeFrequency = 90000;
  int payload_type_frequency;
  RTPHeaderExtension extension;
};

里面有部分信息是不明白意思的

3. 时间错控制
  int64_t arrival_time_ms;
  int64_t now_ms = clock_->TimeInMilliseconds();
  if (packet_time.timestamp != -1)
    arrival_time_ms = (packet_time.timestamp + 500) / 1000;
  else
    arrival_time_ms = now_ms;

  {
    // Periodically log the RTP header of incoming packets.
    rtc::CritScope lock(&receive_cs_);
    if (now_ms - last_packet_log_ms_ > kPacketLogIntervalMs) {
      std::stringstream ss;
      ss << "Packet received on SSRC: " << header.ssrc << " with payload type: "
         << static_cast<int>(header.payloadType) << ", timestamp: "
         << header.timestamp << ", sequence number: " << header.sequenceNumber
         << ", arrival time: " << arrival_time_ms;
      if (header.extension.hasTransmissionTimeOffset)
        ss << ", toffset: " << header.extension.transmissionTimeOffset;
      if (header.extension.hasAbsoluteSendTime)
        ss << ", abs send time: " << header.extension.absoluteSendTime;
      LOG(LS_INFO) << ss.str();
      last_packet_log_ms_ = now_ms;
    }
  }

  header.payload_type_frequency = kVideoPayloadTypeFrequency;

如果方法带了输入时间错,那么就是用参数带的,否则就用重新计算的,单位毫秒。
设置视频的时钟频率为 90000

4. 顺序判断
  bool in_order = IsPacketInOrder(header);

调用过程

  1. RtpStreamReceiver::IsPacketInOrder(const RTPHeader& header)
    通过header.ssrc 找到流统计信息的实例
  2. StreamStatisticianImpl::IsPacketInOrder(uint16_t sequence_number)
    调用内部私有方法
  3. bool StreamStatisticianImpl::InOrderPacketInternal( uint16_t sequence_number)
    调用seq比较方法,输入参数为当前sequence_number,收到的最大 received_seq_max_
  4. inline bool IsNewerSequenceNumber(uint16_t sequence_number, uint16_t prev_sequence_number)
    如果sequence_number 与 prev_sequence_number 相减等于32768,或者且不相等且 相减值小于32768 且不相等。那么认为seq是新的,也就是新的rtp包

这个地方的逻辑不太理解,不是65535吗

5. 注册接收的rtp负载类型

rtp_payload_registry_.SetIncomingPayloadType(header);

  • ReceivePacket () 方法过程分析
1. red 和 rtx 负载类型独立流程处理
 if (rtp_payload_registry_.IsEncapsulated(header)) {
    return ParseAndHandleEncapsulatingHeader(packet, packet_length, header);
  }

待分析,不懂这一块

2. 根据负载类型获得负载其他信息
  PayloadUnion payload_specific;
  if (!rtp_payload_registry_.GetPayloadSpecifics(header.payloadType,
                                                 &payload_specific)) {
    return false;
  }

比如视频的编码类型,音频的采样率通道,码率.下面是获得的结构体信息

struct AudioPayload {
    uint32_t    frequency;
    size_t      channels;
    uint32_t    rate;
};

struct VideoPayload {
    RtpVideoCodecTypes   videoCodecType;
};

union PayloadUnion {
    AudioPayload Audio;
    VideoPayload Video;
};
2. 调用 bool RtpReceiverImpl::IncomingRtpPacket()

rtp 接收的包 处理

rtp_receiver_impl.cc方法 文件分析

bool RtpReceiverImpl::IncomingRtpPacket(
  const RTPHeader& rtp_header,
  const uint8_t* payload,
  size_t payload_length,
  PayloadUnion payload_specific,
  bool in_order)

rtp_header: rtp 头
payload : 去掉头后的rtp数据指针
payload_length: 负载长度
payload_specific : 负载信息
in_order : 是否新seq

1. 调用 CheckSSRCChanged(rtp_header);

做了一下几件事情:

 if (ssrc_ != rtp_header.ssrc ||
        (last_received_payload_type == -1 && ssrc_ == 0)) {

如果保存ssrc 和如如ssrc不同,或者之前没有收到过负载类型和ssrc。那么读取一下playload的信息(PayloadUnion 结构体的信息),记录上次的一些信息,重新ssrc赋值 。new_ssrc 赋值为TRUE.重置一些参数(last_received_timestamp_,last_received_sequence_number_,last_received_frame_time_ms_)。
new_ssrc 为ture的时候,回调,通知rtcp模块,有ssrc的修改

  // We need to get this to our RTCP sender and receiver.
    // We need to do this outside critical section.
    cb_rtp_feedback_->OnIncomingSSRCChanged(rtp_header.ssrc);
  }

重建创建解码器

  if (re_initialize_decoder) {
    if (-1 ==
        cb_rtp_feedback_->OnInitializeDecoder(
            rtp_header.payloadType, payload_name,
            rtp_header.payload_type_frequency, channels, rate)) {
      // New stream, same codec.
      LOG(LS_ERROR) << "Failed to create decoder for payload type: "
                    << static_cast<int>(rtp_header.payloadType);
    }
  }
2. 调用 CheckPayloadChanged();

检查payload type 变化,如果变化后,一些信息无法创建,则返回失败返回

  if (CheckPayloadChanged(rtp_header, first_payload_byte, &is_red,
                          &payload_specific) == -1) {
    if (payload_length == 0) {
      // OK, keep-alive packet.
      return true;
    }
    LOG(LS_WARNING) << "Receiving invalid payload type.";
    return false;
  }
3. 构建基于webrtc结构的rtphead

WebRtcRTPHeader 是对rtphead的二次封装,包含了rtp_head

  WebRtcRTPHeader webrtc_rtp_header;
  memset(&webrtc_rtp_header, 0, sizeof(webrtc_rtp_header));
  webrtc_rtp_header.header = rtp_header;
4. 判断rtp包是否是一帧的第一个包
  bool is_first_packet_in_frame = false;
  {
    rtc::CritScope lock(&critical_section_rtp_receiver_);
    if (HaveReceivedFrame()) {
      is_first_packet_in_frame =
          last_received_sequence_number_ + 1 == rtp_header.sequenceNumber &&
          last_received_timestamp_ != rtp_header.timestamp;
    } else {
      is_first_packet_in_frame = true;
    }
  }
4. 调用RTPReceiverVideo 的ParseRtpPacket()
int32_t ret_val = rtp_media_receiver_->ParseRtpPacket(
      &webrtc_rtp_header, payload_specific, is_red, payload, payload_length,
      clock_->TimeInMilliseconds(), is_first_packet_in_frame);

rtp 包视频解析处理

  • rtp_receiver_video.cc 文件分析
int32_t RTPReceiverVideo::ParseRtpPacket(WebRtcRTPHeader* rtp_header,
                                         const PayloadUnion& specific_payload,
                                         bool is_red,
                                         const uint8_t* payload,
                                         size_t payload_length,
                                         int64_t timestamp_ms,
                                         bool is_first_packet) {
  • ParseRtpPacket() 方法过程分析
1. 赋值视频编码信息
rtp_header->type.Video.codec = specific_payload.Video.videoCodecType;
2. 判空和判断是否收个rtp包
if (payload == NULL || payload_data_length == 0) {
    return data_callback_->OnReceivedPayloadData(NULL, 0, rtp_header) == 0 ? 0
                                                                           : -1;
  }

  if (first_packet_received_()) {
    LOG(LS_INFO) << "Received first video RTP packet";
  }
3. 解析

使用创建指向RtpDepacketizerH264的指针。并且把相信息赋值给rtp_header

// We are not allowed to hold a critical section when calling below functions.
  std::unique_ptr<RtpDepacketizer> depacketizer(
      RtpDepacketizer::Create(rtp_header->type.Video.codec));
  if (depacketizer.get() == NULL) {
    LOG(LS_ERROR) << "Failed to create depacketizer.";
    return -1;
  }

  rtp_header->type.Video.isFirstPacket = is_first_packet;
  RtpDepacketizer::ParsedPayload parsed_payload;
  if (!depacketizer->Parse(&parsed_payload, payload, payload_data_length))
      return -1;

  rtp_header->frameType = parsed_payload.frame_type;
  rtp_header->type = parsed_payload.type;
  rtp_header->type.Video.rotation = kVideoRotation_0;

  // Retrieve the video rotation information.
  if (rtp_header->header.extension.hasVideoRotation) {
    rtp_header->type.Video.rotation = ConvertCVOByteToVideoRotation(
        rtp_header->header.extension.videoRotation);
  }

  rtp_header->type.Video.playout_delay =
      rtp_header->header.extension.playout_delay;
4. 回调到rtp_stream_receiver.ccRtpStreamReceiver::OnReceivedPayloadData()方法
 return data_callback_->OnReceivedPayloadData(parsed_payload.payload,
                                               parsed_payload.payload_length,
                                               rtp_header) == 0
             ? 0
             : -1;

转入 rtp_stream_receiver.cc 文件

  • OnReceivedPayloadData()方法分析
1. 计算ntptime

通过rtptime 计算得到ntptime 毫秒

  WebRtcRTPHeader rtp_header_with_ntp = *rtp_header;
  rtp_header_with_ntp.ntp_time_ms =
      ntp_estimator_.Estimate(rtp_header->header.timestamp);
## 转入 `rtp_stream_receiver.cc` 文件 

rtp时间的理解,参考地址

2. 回调到`video_receiver.cc' 的方法
if (video_receiver_->IncomingPacket(payload_data, payload_size,
                                      rtp_header_with_ntp) != 0) {
    // Check this...
    return -1;
  }

转入 `video_receiver.cc' 文件

  • IncomingPacket() 方法分析
1. 构造VCMPacket

如果负载数据指针为空,负载长度也设置为0

  if (incomingPayload == nullptr) {
    // The jitter buffer doesn't handle non-zero payload lengths for packets
    // without payload.
    // TODO(holmer): We should fix this in the jitter buffer.
    payloadLength = 0;
  }
  const VCMPacket packet(incomingPayload, payloadLength, rtpInfo);
  int32_t ret = _receiver.InsertPacket(packet, rtpInfo.type.Video.width,
                                       rtpInfo.type.Video.height);
  1. 调用到receiver.ccVCMReceiver::InsertPacket() 方法
    `

转入 `receiver.cc' 文件

  • VCMReceiver::InsertPacket() 方法分析
1. 调用JitterBuffer

调用Jitter_Buffer.cc 的 ` VCMJitterBuffer::InsertPacket()方法

const VCMFrameBufferEnum ret =
      jitter_buffer_.InsertPacket(packet, &retransmitted);

Jitter_buffer.ccVCMJitterBuffer 详解

1. 把packet引用传入重传模块
 if (nack_module_)
    nack_module_->OnReceivedPacket(packet);
  • nack_module.cc OnReceivedPacket()的逻辑
  1. 如果未初始化,那么初始化最行收到的newest_seq_num。并且如果是关键帧。那么记录下这关键帧的seq_num。然后return 0。
 bool is_retransmitted = true;
 bool is_keyframe = packet.isFirstPacket && packet.frameType == kVideoFrameKey;

 if (!initialized_) {
   newest_seq_num_ = seq_num;
   if (is_keyframe)
     keyframe_list_.insert(seq_num);
   initialized_ = true;
   return 0;
 }
  1. newest_seq_num_ 是收到过的。所以seq_num 不是会被重传过(怎么理解???)
  // Since the |newest_seq_num_| is a packet we have actually received we know
  // that packet has never been Nacked.
  if (seq_num == newest_seq_num_)
    return 0;
  1. 乱序
    AheadOf 判断是否乱序的seq_num,如果是乱序进入这个if里面
    nack_list_ 记录的是之前向外请求过的seq。这里查找当前收到的seq。
    如果找到了 if (nack_list_it != nack_list_.end()) 那么打印一下重传成功记录,包括重传次数。并且从重传需求列表nack_list_ 里面删除这个seq_num。
    if (!is_retransmitted) 里面的实际上部会执行
  if (AheadOf(newest_seq_num_, seq_num)) {
    // An out of order packet has been received.
    auto nack_list_it = nack_list_.find(seq_num);
    int nacks_sent_for_packet = 0;
    if (nack_list_it != nack_list_.end()) {
      nacks_sent_for_packet = nack_list_it->second.retries;
      LOG(LS_INFO) << "Sequence number " << seq_num
          << " removed from NACK list due to has been received by try " << nacks_sent_for_packet;
      nack_list_.erase(nack_list_it);
    }
    if (!is_retransmitted)
      UpdateReorderingStatistics(seq_num);
    return nacks_sent_for_packet;
  }
  1. 如果不是重传的包。那就是有丢包了或者正常的。
    吧newest_seq_num_ + 1 到 seq_seq 之间的seq都放入nack_list_ 里面。当然AddPacketsToNack () 方法里面还有一些丢弃的逻辑。后面分析
AddPacketsToNack(newest_seq_num_ + 1, seq_num);
 newest_seq_num_ = seq_num;
  1. 记录关键帧rtp的seq。并且控制keyframe_list_ 的大小
  // Keep track of new keyframes.
  if (is_keyframe)
    keyframe_list_.insert(seq_num);

  // And remove old ones so we don't accumulate keyframes.
  auto it = keyframe_list_.lower_bound(seq_num - kMaxPacketAge);
  if (it != keyframe_list_.begin())
    keyframe_list_.erase(keyframe_list_.begin(), it);
  1. 发送需要重传的seq 给nack_sender,其实就是发送rtcp出去。所以这里的rtcp重传请求发送,还是很及时的。
// Are there any nacks that are waiting for this seq_num.
  std::vector<uint16_t> nack_batch = GetNackBatch(kSeqNumOnly);
  if (!nack_batch.empty())
    nack_sender_->SendNack(nack_batch);
  • AddPacketsToNack () 方法详解
  1. 控制nack_list 里面seq_num 的跨度,不超过kMaxPacketAge

lower_bound 的理解是从左边开始找位置。找到某个两个数据的中间或者等于某个数。如果是两个中间,就返回后面的位置。如果是等于。那么就是返回等于的这个位置。比如 0,2,4,6,710。 输入3返回 key为4的位置。比如 10,7,6,4,2.0。 输入3返回 可以key为2的位置的位置。

假如kMaxPacketAge = 100; 那么如果 seq_num_end <= 100 那么都不闪,如果seq_num_end > 100 。那么相当于seq_num_end 往前的seq。那么删除后。第一个元素到 seq_num_end 之间的差就不会超过100。
这里如果从65535 跨度到 0

  // Remove old packets.
  auto it = nack_list_.lower_bound(seq_num_end - kMaxPacketAge);
  nack_list_.erase(nack_list_.begin(), it);
  1. 控制nack_list 里面数据长度
    先获得需要增加的nack seq长度num_new_nacks 。
    num_new_nacks 加上原有的nack_list 长度 如果大于最大限制的长度
    kMaxNackPackets。那么进入到if里面
    RemovePacketsUntilKeyFrame 删除nack_list里面的元素,从坐到右,遇到一个是I帧开头的seq,且长度满足kMaxNackPackets的为止。
    如果一直没有I帧,那么也不会删除。就会导致。长度还是不满足要求。所以要宠幸请求一次I帧,并且把nack_list 里面的数据清空掉
  // If the nack list is too large, remove packets from the nack list until
  // the latest first packet of a keyframe. If the list is still too large,
  // clear it and request a keyframe.
  uint16_t num_new_nacks = ForwardDiff(seq_num_start, seq_num_end);
  if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
    while (RemovePacketsUntilKeyFrame() &&
           nack_list_.size() + num_new_nacks > kMaxNackPackets) {
    }

    if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
      nack_list_.clear();
      LOG(LS_WARNING) << "NACK list full, clearing NACK"
                         " list and requesting keyframe.";
      keyframe_request_sender_->RequestKeyFrame();
      return;
    }
  }
  1. 添加新增的NackInfo ,关于 reordering_histogram_什么还不太理解
  for (uint16_t seq_num = seq_num_start; seq_num != seq_num_end; ++seq_num) {
    NackInfo nack_info(seq_num, seq_num + WaitNumberOfPackets(0.5));
    RTC_DCHECK(nack_list_.find(seq_num) == nack_list_.end());
    nack_list_[seq_num] = nack_info;
  }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350