首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

webRTC中timing信息的使用

webRTC解决方案实现了P2P的音视频通信,其中有关timing的几个问题值得归纳总结。开始本文之前建议先行阅读https://xie.infoq.cn/article/738b8293dce86f7c8748e2629 了解视频传输的关键路径。webRTC是一个异步系统,通信的双方无需做时间同步。本文主要探讨webRTC是怎样解决下面两个跟时间有关的问题:1. 音视频同步 2. 基于延时的带宽评估。

音视频同步(Lip-sync)

发送端采集-编码-发送,接收端解码-渲染,音频流和视频流的处理和网络传输是互相独立的,而且各自的采样/播放频率也是不同的。如何在接收端还原采集端的真实场景,从来都不是一件容易的事情。好在人的听觉/视觉系统本来就有一定容忍能力,ITU(国际电信联盟)给了一个建议:音频之于视频在这个范围内[-125ms,45ms],也就是落后125ms或早于45ms,人类感觉上是可以接受的,我们认为这是音视频处于同步状态。

webRTC解决这个问题的原理也比较简单,发送端给音频流和视频流的数据包都打上时间戳,这些时间戳都可以跟同一个时间基准对齐,接收端利用时间戳和缓存就可以调整每个流上音频/视频帧渲染时间,最终达到同步的效果。我们可以进一步了解一下实现的细节,以视频流为例。下图为视频处理的流水线,每个矩形框是一个线程实例。

实现上有三种时间信息:

1.本地系统时间:从操作系统启动计时至当前的时间差值

2.NTP(Network Time Protocol)时间:全局时间信息,从1/1/1900-00:00h计时到当前的时间差值

3.RTP时间:帧时间戳,以视频采样90k频率为例,rtp_timestamp=ntp_timestamp*90

这三种时间坐标都是对时间的度量,只是描述时间的方式不同。比如当前绝对时间2020-08-05T06:08:52+00:00, 它们是这样表达的。

本地时间1919620051:表示开机计数起,过去1919620051ms了,大概22.2days。

NTP时间3805596543795:表示距离1/1/1900-00:00h,过去3805596543795ms了。

RTP时间:RTP时间由NTP时间计算而来,时间单位1/90000s,u32存储,计算过程会发送溢出,((u32)3805596543795)*90=1521922030。

发送端

一帧视频画面在caputer线程就记录下了,这一帧对应的三个时间信息,尤其重要的是RTP时间。这个rtp_timestamp在Packet pacer模块会加一个提前设定的偏移量,作为最终的rtp时间发出去。这个偏移量加在了整个rtp时间坐标系内,所有的对外的RTP时间都加了。

视频流按照自己的RTP时间对每一个包做了标记,音频流也类似的根据自己的RTP时间对每一个音频包做了标记,但这两条流里的时间都是按照自己的步调在走,是独立的。如果要求接收端使这两条流同步渲染,就要想办法让这些时间统一跟同一个时间基准对齐。如下图示,逻辑上举例描述了两条流如何同步,其中的时间数字只做参考,非真实数据。

RTCP SR(sender report)的作用之一就是做时间对齐的,将该流中的RTP时间于NTP时间对齐。所有的流都对齐发送端的NTP时间,这样接收端就有了统一时间基准。

RTCP SR format 如下:

代码语言:javascript
复制
        0                   1                   2                   3
        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
header |V=2|P|    RC   |   PT=SR=200   |             length            |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                         SSRC of sender                        |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
sender |              NTP timestamp, most significant word             |
info   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |             NTP timestamp, least significant word             |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                         RTP timestamp                         |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                     sender's packet count                     |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                      sender's octet count                     |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
report |                 SSRC_1 (SSRC of first source)                 |
block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  1    | fraction lost |       cumulative number of packets lost       |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |           extended highest sequence number received           |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                      interarrival jitter                      |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                         last SR (LSR)                         |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                   delay since last SR (DLSR)                  |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
report |                 SSRC_2 (SSRC of second source)                |
block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  2    :                               ...                             :
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
       |                  profile-specific extensions                  |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   The sender report packet consists of three sections, possibly
   followed by a fourth profile-specific extension section if defined.
   The first section, the header, is 8 octets long.  The fields have the
   following meaning:

   version (V): 2 bits
      Identifies the version of RTP, which is the same in RTCP packets
      as in RTP data packets.  The version defined by this specification
      is two (2).

接收端

上图中可以看到经过网络传输后,到达接收端的帧数据可能经过了jitter(抖动),乱序,比如stream1 的帧2/3/4。接收端通过RTCP SR和buffer的设计,采用pull的模式,以渲染作为终点倒推从frame queue中取帧的延迟。从单条流处理过程中可以看到该延迟包含渲染+解码+抖动延迟,而多流之间的同步还需要考虑流之间的相对传输延迟(参考RtpStreamsSynchronizer),最终得到每条流的取帧延迟。

基于延时的带宽估计

WebRTC的成功之一在于其设计一套拥塞控制算法,基础数据来自于发送端的丢包统计和包接收时间的统计。这里只讲一下有关timing的RTP包接收时间的统计和反馈,不对拥塞算法展开讲述。

拥塞控制的逻辑现在默认都在发送端执行,有关时间延迟的计算包括发送时间T和接收时间t,发送端自己可以保存每个包T,接收端只需要反馈t即可。算法的逻辑是每个包的接收时间都要反馈,这涉及到交互数据和频次就会比较多,webRTC对此也有精心设计。

webRTC默认在发送端做传输带宽的估计,媒体流走的RTP/UDP协议栈,UDP层没有带宽估计的功能,webRTC通过扩展RTP/RTCP的传输格式使得可以在发送端做传输层的带宽估计。

RTP format

代码语言:javascript
复制
    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |V=2|P|X|  CC   |M|     PT      |       sequence number         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                           timestamp                           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           synchronization source (SSRC) identifier            |
   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
   |            contributing source (CSRC) identifiers(if mixed)   |
   |                             ....                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           header extension (optional)                         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           payload header (format depended)                    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           payload data                                        |
   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

在header extension域组织如下类型的扩展内容

https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01

代码语言:javascript
复制
     0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       0xBE    |    0xDE       |           length=1            |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |  ID   | L=1   |transport-wide sequence number | zero padding  |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Tips: 可以注意到RTP包里有两种sequence number。

  • equence number:是RTP层的概念,用于RTP stream的重组解复用。比如多条流复用的场景,每条流有各自的自增序列。
  • transport-wide sequence number:是传输层概念,传输层包的标识,用于传输层的码率统计。该序列自增不受多流复用的影响,因为复用发生在RTP层。

发送端在发送的时候对每一个RTP packet都打上transport-wide sequence number的序号(PacketRouter::SendPacket),比如发送seq=53,54,55。

接收端收到该包后,把该包的到达时间记录下来,记录时间为本地内部时间戳(单位ms),即开机多久了。

代码语言:javascript
复制
packet_arrival_times_[53]=1819746010
packet_arrival_times_[54]=1819746020
packet_arrival_times_[55]=1819746026 

接收端RemoteEstimatorProxy模块负责传输层统计的反馈,周期性的把包接收的时间信息回馈到发送端。transport feedback的格式有详细的规则,定义如下 https://tools.ietf.org/id/draft-dt-rmcat-feedback-message-04.html#rfc.section.3.1

这里有一篇写的不错的注解可以参考 https://blog.jianchihu.net/webrtc-research-transport-cc-rtp-rtcp.html

代码语言:javascript
复制
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |V=2|P| FMT=CCFB |   PT = 205   |          length               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                  SSRC of packet sender                        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                  SSRC of 1st media source                     |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |          begin_seq            |             end_seq           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |L|ECN|  Arrival time offset    | ...                           .
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    .                                                               .
    .                                                               .
    .                                                               .
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                  SSRC of nth media source                     |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |          begin_seq            |             end_seq           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |L|ECN|  Arrival time offset    | ...                           |
    .                                                               .
    .                                                               .
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                 Report Timestamp (32bits)                     |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

RTCP transport feedback一般是RTCP通道上最频繁的传递内容,webRTC对其传输也有特别的设计。关注以下几个参数

代码语言:javascript
复制
max_intervel = 250ms //feedback 最大周期
min_intervel =50ms   //feedback 最小周期
rtcp_ratio = 5%      //feedback占用带宽比例
Avg_feedback_size = 68bytes  //平均一个feedback包的大小

发送RTCP transport feedback的时间周期控制在[50ms,250ms]内,在这个范围内根据当前带宽动态调整,尽量把RTCP transport feedback的传输占用带宽比例控制在5%。可以计算得到边界,单单传输feedback占用的带宽范围[2176bps,10880bps],也是一笔不小的开销了。

总结

本文总结了webRTC中三种timing类型,本地时间、NTP时间、RTP时间,同时分析了音视频同步和基于延迟带宽评估两个专题对时间信息的使用。

附录

附上webRTC工程上有关timing的几个关键数据结构

Capturer

代码语言:javascript
复制
class webrtc::VideoFrame{
...
  uint16_t id_;            //picture id
  uint32_t timestamp_rtp_; //rtp timestamp, (u32)ntp_time_ms_ *90
  int64_t ntp_time_ms_;    //ntp timestamp, capture time since 1/1/1900-00:00h
  int64_t timestamp_us_;   //internal timestamp, capture time since system started, round at 49.71days
}

VideoStreamEncoder::OnFrame // caluclate capture timing

VideoStreamEncoder::OnEncodedImage // fill capture timing

RtpVideoSender::OnEncodedImage // timestamp_rtp_+random value

代码语言:javascript
复制
class webrtc::EncodedImage{
...
  //RTP Video Timing extension
  //https://webrtc.googlesource.com/src/+/refs/heads/master/docs/native-code/rtp-hdrext/video-timing
  struct Timing {
    uint8_t flags = VideoSendTiming::kInvalid;
    int64_t encode_start_ms = 0;                //frame encoding start time, base on ntp_time_ms_
    int64_t encode_finish_ms = 0;               //frame encoding end time, base on ntp_time_ms_
    int64_t packetization_finish_ms = 0;        //encoded frame packetization time, base on ntp_time_ms_
    int64_t pacer_exit_ms = 0;                  //packet sent time when leaving pacer, base on ntp_time_ms_
    int64_t network_timestamp_ms = 0;           //reseved for network node
    int64_t network2_timestamp_ms = 0;          //reseved for network node
    int64_t receive_start_ms = 0;
    int64_t receive_finish_ms = 0;
  } timing_;
  uint32_t timestamp_rtp_;											//same as caputrer.timestamp_rtp_
  int64_t ntp_time_ms_;										      //same as caputrer.ntp_time_ms_
  int64_t capture_time_ms_;										  //same as caputrer.capture_time_ms_
}

RTPSenderVideo::SendVideo

代码语言:javascript
复制
class webrtc::RtpPacketToSend{
...
  // RTP Header.
  bool marker_;                //frame end marker
  uint16_t sequence_number_;   //RTP sequence number, start at random(1,32767)
  uint32_t timestamp_;         //capturer timestamp_rtp_ + u32.random()
  uint32_t ssrc_;              //Synchronization Source, specify media source
  
  int64_t capture_time_ms_;    //same as capturer.capture_time_ms_
}

===

receiver side

RtpTransport::DemuxPacket

代码语言:javascript
复制
class webrtc::RtpPacketReceived{
...
  NtpTime capture_time_;
  int64_t arrival_time_ms_; //RTP packet arrival time, local internal timestamp
  // RTP Header.
  bool marker_;                //frame end marker
  uint16_t sequence_number_;   //RTP sequence number, start at random(1,32767)
  uint32_t timestamp_;         //sender's rtp timestamp maintained by RTPSenderVideo 
  uint32_t ssrc_;              //Synchronization Source, specify media source
}

RtpVideoStreamReceiver::ReceivePacket /OnReceivedPayloadData

代码语言:javascript
复制
struct webrtc::RTPHeader{
  ...
  bool markerBit;
  uint16_t sequenceNumber;                //RTP sequence, set by sender per RTP packet
  uint32_t timestamp;                     //sender's RTP timestamp  
  uint32_t ssrc;
  RTPHeaderExtension extension;           //contains PlayoutDelay&VideoSendTiming if has
}
class webrtc::RtpDepacketizer::ParsedPayload{
    RTPVideoHeader video;

    const uint8_t* payload;
    size_t payload_length;
}

class webrtc::RTPVideoHeader{
  ...
  bool is_first_packet_in_frame;
  bool is_last_packet_in_frame;
  PlayoutDelay playout_delay;     //playout delay extension
  VideoSendTiming video_timing;   //Video Timing extension, align with sender's webrtc::EncodedImage::timing
}

class webrtc::VCMPacket{
...
  uint32_t timestamp;            //sender's RTP timestamp
  int64_t ntp_time_ms_;
  uint16_t seqNum;
  RTPVideoHeader video_header;
  RtpPacketInfo packet_info;
}
class webrtc::RtpPacketInfo{
  ...
  uint32_t ssrc_;
  uint32_t rtp_timestamp_;        //sender's rtp timestamp
  //https://webrtc.googlesource.com/src/+/refs/heads/master/docs/native-code/rtp-hdrext/abs-capture-time
  absl::optional<AbsoluteCaptureTime> absolute_capture_time_; //
  int64_t receive_time_ms_;       //packet receive time, local internal timestamp 
}

PacketBuffer::InsertPacket

代码语言:javascript
复制
class webrtc::video_coding::RtpFrameObject: public EncodedImage{
...
  RTPVideoHeader rtp_video_header_;
  uint16_t first_seq_num_;
  uint16_t last_seq_num_;
  int64_t last_packet_received_time_;
  int64_t _renderTimeMs;
  //inherit from webrtc::EncodedImage
  uint32_t timestamp_rtp_;											
  int64_t ntp_time_ms_;										     
  int64_t capture_time_ms_;										 
}
  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/b6e68fe96ab39e3444e30c439
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券