JCHub

  • Home
  • Category
    • A/V
    • WebRTC
    • Beauty of Programming
    • Linux
    • Windows
    • Moments of Life
    • Campus Life
  • Reference
    • API Reference
    • Utilities
    • AV Test
    • Doc
  • Message Board
  • About
JCHub
Code as My Sword, Lost in Obsession
  1. Main page
  2. WebRTC
  3. Main content

WebRTC研究:RTP时间戳的产生

2022年5月13日 2148hotness 1likes 0comments

RTP时间戳

RTP固定报头中,带有4字节大小的时间戳。

1
2
3
4
5
6
7
8
9
10
11
12
    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             |
   |                             ....                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

RTP时间戳一般以采样频率为单位。相邻两帧的时间戳增量为:
\[时间差(ms)* 采样率 / 1000 \tag{1}\]
RTP时间戳分为绝对时间戳跟相对时间戳。本文主要介绍这个时间戳是如何产生的。

视频RTP时间戳产生

由于视频每帧间隔也就是时间差不是稳定的,无法使用固定的时间戳增量计算时间戳,所以视频使用当前帧采集的NTP时间作为时间戳计算基础,也就是使用绝对时间戳表示。

采集阶段

调用set_timestamp_ms(rtc::TimeMillis())设置当前时间。

C++
1
2
3
4
5
6
7
8
9
int32_t VideoCaptureImpl::IncomingFrame() {
  VideoFrame captureFrame =
      VideoFrame::Builder()
          .set_video_frame_buffer(buffer)
          .set_timestamp_rtp(0)
          .set_timestamp_ms(rtc::TimeMillis())
          .set_rotation(!apply_rotation ? _rotateFrame : kVideoRotation_0)
          .build();
}

编码阶段

这里将NTP时间戳转换为RTP时间戳,计算方法为:
RTP时间:NTP时间(ms) * 90000(采样率) / 1000,对应代码如下:

C++
1
2
3
4
5
6
7
8
9
10
11
void VideoStreamEncoder::OnFrame() {
  // delta_ntp_internal_ms_ = clock_->CurrentNtpInMilliseconds() - clock_->TimeInMilliseconds()
  // 用来进行内部时间跟NTP时间的转换,从而得到采集的NTP时间
  // video_frame.render_time_ms()就是前一步设置的时间
  capture_ntp_time_ms = video_frame.render_time_ms() + delta_ntp_internal_ms_;
  incoming_frame.set_ntp_time_ms(capture_ntp_time_ms);
  // Convert NTP time, in ms, to RTP timestamp.
  const int kMsToRtpTimestamp = 90;
  incoming_frame.set_timestamp(
      kMsToRtpTimestamp * static_cast<uint32_t>(incoming_frame.ntp_time_ms()));
}

根据rfc3551,5.Video章节,之所以使用90kHz作为采样频率是因为,90kHz跟MPEG呈现时间戳频率相同。同时90kHz差不多是24(HDTV),25(PAL),29.97(NTSC)和30Hz(HDTV)这些电视信号制式帧率以及50,59.94和60帧率的整数倍。同时使用90kHz能够满足一些场景的时间精度需要。例如:

  • 音视频同步,需要将RTP时间戳转为NTP时间戳
  • 抖动计算,参考:WebRTC研究:统计参数之抖动

RTP打包发送阶段

这里会在之前设置的RTP时间戳基础上加上一个随机偏移,加上这个偏移是为了防止时间戳冲突,假如同一个时刻有好几个视频源同时产生视频,它们就可能有完全一样的时间戳,对应代码如下:

C++
1
2
3
4
5
EncodedImageCallback::Result RtpVideoSender::OnEncodedImage() {
  uint32_t rtp_timestamp =
      encoded_image.Timestamp() +
      rtp_streams_[stream_index].rtp_rtcp->StartTimestamp();
}

至此得到了最后视频RTP包中的时间戳。

音频RTP时间戳产生

RTC中音频默认是10ms采集一次。由于采样间隔也就是时间差是稳定的,根据公式(1),相邻采集帧时间戳增量为:
10ms * 采样率 / 1000 = 采样率 / 100,也就是每帧单声道的样本数。所以音频时间戳计算无需获取当前NTP时间,从0开始,按固定时间戳增量不断增加即可,也就是使用相对时间戳表示。

采集阶段

直接取每帧单道样本数作为时间戳增量,计算代码如下:

C++
1
2
3
void ChannelSend::ProcessAndEncodeAudio() {
  _timeStamp += static_cast<uint32_t>(audio_frame->samples_per_channel_);
}

这样得到了每一个采集帧的时间戳。

编码阶段

Opus编码器默认设置20ms编码帧长,一个Opus编码帧包含20ms音频数据。也就是每两个10ms音频采集帧编码为一个opus帧,喂给编码器两帧10ms数据,最后编码出一帧,这时编码帧时间戳增量为:2 * 采样率 / 100,Opus编码器内部处理代码如下:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
AudioEncoder::EncodedInfo AudioEncoderOpusImpl::EncodeImpl() {
  // 记录input_buffer_第一帧的时间戳
  if (input_buffer_.empty())
    first_timestamp_in_buffer_ = rtp_timestamp;
 
  input_buffer_.insert(input_buffer_.end(), audio.cbegin(), audio.cend());
  // 如果in_buffer大小不满足 2 * 10ms样本数
  if (input_buffer_.size() <
      (Num10msFramesPerPacket() * SamplesPer10msFrame())) {
    return EncodedInfo();
  }
  const size_t max_encoded_bytes = SufficientOutputBufferSize();
  EncodedInfo info;
  info.encoded_bytes = encoded->AppendData(
      max_encoded_bytes, [&](rtc::ArrayView<uint8_t> encoded) {
        int status = WebRtcOpus_Encode(
            inst_, &input_buffer_[0],
            rtc::CheckedDivExact(input_buffer_.size(), config_.num_channels),
            rtc::saturated_cast<int16_t>(max_encoded_bytes), encoded.data());
        return static_cast<size_t>(status);
      });
  input_buffer_.clear();
  // 送到RTP打包发送阶段的RTP时间戳
  info.encoded_timestamp = first_timestamp_in_buffer_;
}

Opus编码完一帧后,会将此时的RTP时间戳通过EncodedInfo返回给上层,然后送到RTP打包发送模块,对应处理代码如下:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int32_t AudioCodingModuleImpl::Encode() {
  AudioEncoder::EncodedInfo encoded_info;
  encoded_info = encoder_stack_->Encode(
      rtp_timestamp,
      rtc::ArrayView<const int16_t>(
          input_data.audio,
          input_data.audio_channel * input_data.length_per_channel),
      &encode_buffer_);
  if (packetization_callback_) {
    // 送到打包发送模块的RTP时间戳:encoded_info.encoded_timestamp
    packetization_callback_->SendData(
        frame_type, encoded_info.payload_type, encoded_info.encoded_timestamp,
        encode_buffer_.data(), encode_buffer_.size(),
        absolute_capture_timestamp_ms.value_or(-1));
  }
}

RTP打包发送阶段

跟视频RTP时间戳处理一样,也需要加上一个随机偏移:

C++
1
2
3
4
5
6
7
8
9
10
int32_t ChannelSend::SendRtpAudio() {
  // 最后RTP包的时间戳:rtp_timestamp + rtp_rtcp_->StartTimestamp()
  if (!rtp_sender_audio_->SendAudio(
          frameType, payloadType, rtp_timestamp + rtp_rtcp_->StartTimestamp(),
          payload.data(), payload.size(), absolute_capture_timestamp_ms)) {
    RTC_DLOG(LS_ERROR)
        << "ChannelSend::SendData() failed to send data to RTP/RTCP module";
    return -1;
  }
}

总结

RTP时间戳在组帧,抖动计算,音视频同步等起到了重要作用,通过本文相信对RTP时间戳有了更深刻认识。

参考

[1]RFC3550.https://www.rfc-editor.org/rfc/rfc3550.
[2]RFC3551.https://www.rfc-editor.org/rfc/rfc3551.

This article is licensed with Creative Commons Attribution-NonCommercial-No Derivatives 4.0 International License
Tag: WebRTC
Last updated:2023年3月15日

Jeff

管理员——代码为剑,如痴如醉

Tip the author Like
< Last article
Next article >

Comments

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
Cancel

This site uses Akismet to reduce spam. Learn how your comment data is processed.

文章目录
  • RTP时间戳
  • 视频RTP时间戳产生
    • 采集阶段
    • 编码阶段
    • RTP打包发送阶段
  • 音频RTP时间戳产生
    • 采集阶段
    • 编码阶段
    • RTP打包发送阶段
  • 总结
  • 参考
Related Posts
  • 浅谈基于SFU实现一对一效果
  • WebRTC资讯:H265支持进展
  • Protected: WebRTC硬件编解码器出错无缝切换软编软解
  • WebRTC研究:Audio level
  • Mac平台WebRTC编译
Categories

COPYRIGHT © 2026 jianchihu.net. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang