WebRTC中的ulp fec实现分析

目录

概述

1.rfc5109关于ulpfec的定义

1.1 fec包结构

1.2 fec header结构

1.3 ulp header 结构

2.webrtc中ulpfec的启用

3.掩码表的作用

4.webrtc中fec包的生成。

5.利用fec包恢复丢失的rtp包

6.Red打包格式

7.fec打包和RTP包加密的先后顺序

8.如何区分音频RTP包和视频RTP包

9.如何区分RTP包和RTCP包


概述

webrtc在90版本使用了ULP FEC来应对网络丢包,ULP是Uneven Level Protection的缩写,意为不均等保护,可以针对数据的重要程度提供不同级别的保护。一般音视频的媒体数据,不同部分的重要程度是不一样的,越靠前的数据越重要,对靠前的数据使用更多的FEC包来保护,靠后的数据使用更少的FEC包来保护,这样就可更充分的利用带宽。

1.rfc5109关于ulpfec的定义

1.1 fec包结构

RTP Header就是标准的RTP头, FEC Header固定为10个字节,FEC Header后面可以跟着多个 Level,每个Level保护着不同的数据。

1.2 fec header结构

E: 保留的扩展标志位,必须设置为0

L:长掩码标志位。当L设置为0时,ulp header的mask长度为16bits,当L设置为1时,ulp header的mask长度为48bits。

P、X、CC、PT的值由此fec包所保护的RTP包的对应值通过XOR运算得到。

SN base: 设置为此fec包所保护的RTP包中的最小序列号。

TS recovery: 由此fec包所保护的RTP包的timestamps通过XOR运算得到。

Length recovery:由此fec包所保护的RTP包的长度通过XOR运算得到。

1.3 ulp header 结构

Protection Length: 此级别所保护的数据的长度。

mask: 保护掩码,长度为2个字节或者6个字节(由fec header 的L标志位决定)。通过mask可以知道此级别保护了哪些RTP包,例如

SN base等于100,mask值等于9b80,对应的二进制为 1001 1011 1000 0000,那么就可以知道第0、3、4、6、7、8个RTP包被此级别所保护,被保护的RTP包的序列号分别是100、103、104、10106、107、108。

2.webrtc中ulpfec的启用

如果采用H264编码,则不会启用ULPFEC功能,见代码call/RtpVideoSender::ShouldDisableRedAndUlpfec

编码生成fec包的个数由保护系数protection_factor和媒体包个数决定。protection_factor是一个由丢包率和有效比率决定的值,计算公式:

protection_factor = kFecRateTable[k];

k = rate_i * 129 + loss_j;

loss_j = 0, 1, .. 128;

rate_i是在某个范围内变化的值。

kFecRateTable是一个静态数组,在modules/video_coding/fec_rate_table.h文件中定义,具体的protection_factor计算过程在modules/video_coding/media_opt_util.cc文件的VCMFecMethod::ProtectionFactor函数。

一般网络畅通的时候不会生成fec包,只有在丢包的情况下protection_factor才不会为0,接下来才会生成fec编码包。

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

3.掩码表的作用

在ulpfec协议中,一个fec包可以保护多个媒体包,而一个媒体包也可以被多个fec包所保护。假设m个媒体包需要使用k个fec包来保护,那么可以通过一个掩码表来表示媒体包和fec包的关系。ULP Leave Header的mask字段可以是2个字节或者6个字节下面以2个字节为例,假如有12个媒体包需要3个fec包来保护,那么其掩码表packet_masks为: 9b 80 4f 10 3c 60,其中9b 80表示第一个fec包所保护的媒体包,4f 10和 3c 60分别表示第二个、第三个fec包所保护的媒体包。以9b 80为例进行分析,9b 80的二进制表示为:1001 1011 1000 0000,表明此fec包保护了第0、3、4、6、7、8个媒体包,假设基准序列号为27176,那么此fec包所保护的RTP包的序号就分别是27176、27179、27180、27182、27183、27184.

webrtc在modules/rtp_rtcp_source/fec_private_tables_bursty和fec_private_tables_random文件中预定义了两个掩码表kPacketMaskBurstyTbl和kPacketMaskRandomTbl,其中kPacketMaskBurstyTbl用于阵发性或者连续性的网络丢包环境,而kPacketMaskRandomTbl用于随机性的丢包环境。上面举例的掩码表packet_masks就是由kPacketMaskBurstyTbl或者kPacketMaskRandomTbl而得到。

4.webrtc中fec包的生成。

编码生成fec包的流程如下:

下面是EncodeFec函数的实现代码:

int ForwardErrorCorrection::EncodeFec(const PacketList& media_packets,
                                      uint8_t protection_factor,
                                      int num_important_packets,
                                      bool use_unequal_protection,
                                      FecMaskType fec_mask_type,
                                      std::list<Packet*>* fec_packets) {
  const size_t num_media_packets = media_packets.size();
 
  // Sanity check arguments.
  RTC_DCHECK_GT(num_media_packets, 0);
  RTC_DCHECK_GE(num_important_packets, 0);
  RTC_DCHECK_LE(num_important_packets, num_media_packets);
  RTC_DCHECK(fec_packets->empty());
  const size_t max_media_packets = fec_header_writer_->MaxMediaPackets();
  if (num_media_packets > max_media_packets) {
    RTC_LOG(LS_WARNING) << "Can't protect " << num_media_packets
                        << " media packets per frame. Max is "
                        << max_media_packets << ".";
    return -1;
  }
 
  // Error check the media packets.
  for (const auto& media_packet : media_packets) {
    RTC_DCHECK(media_packet);
    if (media_packet->data.size() < kRtpHeaderSize) {
      RTC_LOG(LS_WARNING) << "Media packet " << media_packet->data.size()
                          << " bytes "
                             "is smaller than RTP header.";
      return -1;
    }
    // Ensure the FEC packets will fit in a typical MTU.
    if (media_packet->data.size() + MaxPacketOverhead() + kTransportOverhead >
        IP_PACKET_SIZE) {
      RTC_LOG(LS_WARNING) << "Media packet " << media_packet->data.size()
                          << " bytes "
                             "with overhead is larger than "
                          << IP_PACKET_SIZE << " bytes.";
    }
  }
 
  // Prepare generated FEC packets.
  int num_fec_packets = NumFecPackets(num_media_packets, protection_factor);
  if (num_fec_packets == 0) {
    return 0;
  }
  for (int i = 0; i < num_fec_packets; ++i) {
    generated_fec_packets_[i].data.EnsureCapacity(IP_PACKET_SIZE);
    memset(generated_fec_packets_[i].data.data(), 0, IP_PACKET_SIZE);
    // Use this as a marker for untouched packets.
    generated_fec_packets_[i].data.SetSize(0);
    fec_packets->push_back(&generated_fec_packets_[i]);
  }
 
  internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets);
  packet_mask_size_ = internal::PacketMaskSize(num_media_packets);
  memset(packet_masks_, 0, num_fec_packets * packet_mask_size_);
  internal::GeneratePacketMasks(num_media_packets, num_fec_packets,
                                num_important_packets, use_unequal_protection,
                                &mask_table, packet_masks_);
 
  // Adapt packet masks to missing media packets.
  int num_mask_bits = InsertZerosInPacketMasks(media_packets, num_fec_packets);
  if (num_mask_bits < 0) {
    RTC_LOG(LS_INFO) << "Due to sequence number gaps, cannot protect media "
                        "packets with a single block of FEC packets.";
    fec_packets->clear();
    return -1;
  }
  packet_mask_size_ = internal::PacketMaskSize(num_mask_bits);
 
  // Write FEC packets to |generated_fec_packets_|.
  GenerateFecPayloads(media_packets, num_fec_packets);
  // TODO(brandtr): Generalize this when multistream protection support is
  // added.
  const uint32_t media_ssrc = ParseSsrc(media_packets.front()->data.data());
  const uint16_t seq_num_base =
      ParseSequenceNumber(media_packets.front()->data.data());
  FinalizeFecHeaders(num_fec_packets, media_ssrc, seq_num_base);
 
  return 0;
}

EncodeFec函数的大概流程就是根据媒体包个数和保护系数protection_factor确定需要需要生成的fec包的个数num_fec_packets,如果num_fec_packets为0则函数返回,否则获取生成fec包所需要的掩码表并存放在packet_masks_变量中,然后调用GenerateFecPayloads生成所有的fec包,这个时候得到的fec包并不是最终的状态,还需要调用FinalizeFecHeaders来调整fec包的头部。

下面来看下GenerateFecPayload函数的实现:

void ForwardErrorCorrection::GenerateFecPayloads(
    const PacketList& media_packets,
    size_t num_fec_packets) {
  RTC_DCHECK(!media_packets.empty());
  for (size_t i = 0; i < num_fec_packets; ++i) {
    Packet* const fec_packet = &generated_fec_packets_[i];
    size_t pkt_mask_idx = i * packet_mask_size_;
    const size_t min_packet_mask_size = fec_header_writer_->MinPacketMaskSize(
        &packet_masks_[pkt_mask_idx], packet_mask_size_);
    const size_t fec_header_size =
        fec_header_writer_->FecHeaderSize(min_packet_mask_size);
 
    size_t media_pkt_idx = 0;
    auto media_packets_it = media_packets.cbegin();
    uint16_t prev_seq_num =
        ParseSequenceNumber((*media_packets_it)->data.data());
    while (media_packets_it != media_packets.end()) {
      Packet* const media_packet = media_packets_it->get();
      const uint8_t* media_packet_data = media_packet->data.cdata();
      // Should |media_packet| be protected by |fec_packet|?
      if (packet_masks_[pkt_mask_idx] & (1 << (7 - media_pkt_idx))) {
        size_t media_payload_length =
            media_packet->data.size() - kRtpHeaderSize;
 
        bool first_protected_packet = (fec_packet->data.size() == 0);
        size_t fec_packet_length = fec_header_size + media_payload_length;
        if (fec_packet_length > fec_packet->data.size()) {
          // Recall that XORing with zero (which the FEC packets are prefilled
          // with) is the identity operator, thus all prior XORs are
          // still correct even though we expand the packet length here.
          fec_packet->data.SetSize(fec_packet_length);
        }
        if (first_protected_packet) {
          uint8_t* data = fec_packet->data.data();
          // Write P, X, CC, M, and PT recovery fields.
          // Note that bits 0, 1, and 16 are overwritten in FinalizeFecHeaders.
          memcpy(&data[0], &media_packet_data[0], 2);
          // Write length recovery field. (This is a temporary location for
          // ULPFEC.)
          ByteWriter<uint16_t>::WriteBigEndian(&data[2], media_payload_length);
          // Write timestamp recovery field.
          memcpy(&data[4], &media_packet_data[4], 4);
          // Write payload.
          if (media_payload_length > 0) {
            memcpy(&data[fec_header_size], &media_packet_data[kRtpHeaderSize],
                   media_payload_length);
          }
        } else {
          XorHeaders(*media_packet, fec_packet);
          XorPayloads(*media_packet, media_payload_length, fec_header_size,
                      fec_packet);
        }
      }
      media_packets_it++;
      if (media_packets_it != media_packets.end()) {
        uint16_t seq_num =
            ParseSequenceNumber((*media_packets_it)->data.data());
        media_pkt_idx += static_cast<uint16_t>(seq_num - prev_seq_num);
        prev_seq_num = seq_num;
      }
      pkt_mask_idx += media_pkt_idx / 8;
      media_pkt_idx %= 8;
    }
    RTC_DCHECK_GT(fec_packet->data.size(), 0)
        << "Packet mask is wrong or poorly designed.";
  }
}

GenerateFecPayloads函数的处理流程如下:

(1)取fec包列表generated_fec_packets_中的一个包fec_packet,计算此fec包所对应的掩码表的索引 pkt_mask_idx,计算此fec包的头部大小fec_header_size,计算媒体包列表media_packets的第一个包的序号prev_seq_num。

(2)取media_packets_的一个包media_packet进行处理,通过掩码值和media_packet的序列号来判断此media_packet是否是被fec_packet所保护。如果不受保护则转到(5)处理。如果受保护,则判断此media_packet是否此fec_packet保护的第一个包,如果是则转到(3)处理,否则转到(4)处理。

(3)a.将media_packet的头两个字节拷贝fec_packet的头两个字节,因为RTP头部的第一个字节的开始两位是版本事情,而FEC头部第一个字节的开始两位是是E和L,所以可以使用memcpy直接复制,后面调用FinazlizeFecHeaders函数再对E、L位进行修正。b.将media_packet的负载长度临时写到fec_packet的第3、4个字节,后面调用FinazlizeFecHeaders函数再对长度进行修正。c.将media_packet的时间戳写到fec_packet。d.将media_packet的负载数据写到fec_packet。转到(5)处理。

(4)将media_packet与fec_packet的头部进行xor运算,运算结果写到fec_packet的头部。将media_packet与fec_packet的负载数据进行xor运算,运算结果写到fec_packet的负载部分。

(5)取media_packets_的下一个包,转(2)继续处理。遍历media_packets_后结束循环。

(6 )取generated_fec_packets_的下一个包,转(1)继续处理。遍历generated_fec_packets后结束循环,至此所有的fec包基本都构建好了。

接下来再看FinalizeFecHeaders函数的代码:

void ForwardErrorCorrection::FinalizeFecHeaders(size_t num_fec_packets,
                                                uint32_t media_ssrc,
                                                uint16_t seq_num_base) {
  for (size_t i = 0; i < num_fec_packets; ++i) {
    fec_header_writer_->FinalizeFecHeader(
        media_ssrc, seq_num_base, &packet_masks_[i * packet_mask_size_],
        packet_mask_size_, &generated_fec_packets_[i]);
  }
}
 
void UlpfecHeaderWriter::FinalizeFecHeader(
    uint32_t /* media_ssrc */,
    uint16_t seq_num_base,
    const uint8_t* packet_mask,
    size_t packet_mask_size,
    ForwardErrorCorrection::Packet* fec_packet) const {
  uint8_t* data = fec_packet->data.data();
  // Set E bit to zero.
  data[0] &= 0x7f;
  // Set L bit based on packet mask size. (Note that the packet mask
  // can only take on two discrete values.)
  bool l_bit = (packet_mask_size == kUlpfecPacketMaskSizeLBitSet);
  if (l_bit) {
    data[0] |= 0x40;  // Set the L bit.
  } else {
    RTC_DCHECK_EQ(packet_mask_size, kUlpfecPacketMaskSizeLBitClear);
    data[0] &= 0xbf;  // Clear the L bit.
  }
  // Copy length recovery field from temporary location.
  memcpy(&data[8], &data[2], 2);
  // Write sequence number base.
  ByteWriter<uint16_t>::WriteBigEndian(&data[2], seq_num_base);
  // Protection length is set to entire packet. (This is not
  // required in general.)
  const size_t fec_header_size = FecHeaderSize(packet_mask_size);
  ByteWriter<uint16_t>::WriteBigEndian(
      &data[10], fec_packet->data.size() - fec_header_size);
  // Copy the packet mask.
  memcpy(&data[12], packet_mask, packet_mask_size);
}

FinalizeFecHeader函数修正了fec头的E和L标志位,同时写入了基准序列号,更正了length recovery字段的值,最后写入ulp level header。ulp level header由保护长度(2个字节)和掩码(2个或者6个字节)组成。由FinilizeFecHeader函数可以看出,虽然ulp fec协议支持在一个fec包里面封装多个保护级别的数据,但webrtc实际上只用到了一个级别。

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

5.利用fec包恢复丢失的rtp包

fec包的解析就是fec包封装的逆过程,代码就不贴了,可以参见modules/rtc_rtcp/source/forward_error_correction.cc::DecodeFec以及相关函数,下面简要下收到RTP包后恢复丢失的RTP包的处理流程。

(1)判断此RTP包是否是fec包,如果是那么把这个包放到队列received_fec_packets_里面,并通过fec包里面的mask字段解析出此fec包所保护的所有RTP包,把这些被保护的RTP包存放到此fec包下面。

(2)如果此RTP包是媒体包,那么更新received_fec_packets中对应的fec包的保护包接收情况。

(3)尝试恢复丢失的包。遍历received_fec_packets_,如果其中的一个fec包的所保护的RTP包的缺失数packets_missing刚好是1,那么就利用此fec包恢复缺失的RTP包。如果packets_missing为0,证明此fec包所保护的RTP包均已收到,丢弃此fec包。如果packets_missing大于1则处理下一个fec包。

6.Red打包格式

Red(Redundant coding)编码是webrtc中采用的一种编码方式,虽然modules/rtp_rtcp/source/ulpfec_receiver_impl.cc文件中存在red格式的定义:

但实际上调试发现webrtc在采用red编码时打包RTP包仅仅是改变了原RTP包的payload类型,同时在原有的payload前增加多一个字节的red_payload。假设原有的RTP的的负载类型是96,而red负载类型是122,那么包结构如下:

原rtp包: RTP Header(PT = 96 ) + payload

red格式rtp包: RTP Header(PT = 122) + red_payload(一字节, 值为96) + payload

payload部分没有任何改变,如果是fec类型的包,那么red_payload = 122, 这样接收端就可以通过red_payload的值来区分此RTP包是普通媒体包还是fec包。

7.fec打包和RTP包加密的先后顺序

fec打包在加密操作之前进行。所有的音视频RTP包、fec类型的RTP包都会被送到pacing模块,再由pacing模块传到pc_network_thread线程进行发送,在调用socket发送数据包之前才调用libsrtp模块进行加密,加密是针对RTP包的payload部分进行。收端接收到RTP包后需要先解密再进行解析。

8.如何区分音频RTP包和视频RTP包

当webrtc使用同一个udp端口来传输音视频数据时,需要能够区分音频、视频RTP包。可以通过RTP包中的SSRC来区分。音频、视频的RTP包的SSRC不同,接收端在得到RTP包的SSRC后,根据SSRC来进行不同的处理。

9.如何区分RTP包和RTCP包

通过负载类型来区分

// For additional details, see http://tools.ietf.org/html/rfc5761.
bool IsRtcpPacket(rtc::ArrayView<const char> packet) {
  if (packet.size() < kMinRtcpPacketLen ||
      !HasCorrectRtpVersion(
          rtc::reinterpret_array_view<const uint8_t>(packet))) {
    return false;
  }
 
  char pt = packet[1] & 0x7F;
  return (63 < pt) && (pt < 96);
}

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值