剑痴乎

剑痴乎
代码为剑,如痴如醉
  1. 首页
  2. WebRTC
  3. 正文

WebRTC研究:RTP时间戳的产生

2022年5月13日 1370点热度 1人点赞 0条评论

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.

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
标签: WebRTC
最后更新:2023年3月15日

Jeff

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

打赏 点赞
< 上一篇
下一篇 >

文章评论

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

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理。

版权声明

为支持原创,创作更好的文章,未经许可,禁止任何形式的转载与抄袭,如需转载请邮件私信!本人保留所有法定权利。违者必究!

文章目录
  • RTP时间戳
  • 视频RTP时间戳产生
    • 采集阶段
    • 编码阶段
    • RTP打包发送阶段
  • 音频RTP时间戳产生
    • 采集阶段
    • 编码阶段
    • RTP打包发送阶段
  • 总结
  • 参考
最近评论
ztt 发布于 3 周前(04月05日) 你好,想看里面的视频和图片为什么没有显示呢?需要下flash吗还是什么。
huowa222 发布于 4 周前(03月26日) 同问
邱国禄 发布于 2 个月前(02月17日) Receive Delta以0.25ms为单位,reference time以64ms为单位,kDe...
啊非 发布于 4 个月前(12月30日) 大神,请教一个问题: constexpr int kBaseScaleFactor = Tran...
啊非 发布于 4 个月前(12月30日) reference time:3字节,表示参考时间,以64ms为单位,但是 代码里面是 Trans...
相关文章
  • WebRTC资讯:H265支持进展
  • WebRTC研究:Audio level
  • Mac平台WebRTC编译
  • WebRTC研究:统计参数之丢包率
  • WebRTC研究:基于Transport Feedback的早期丢包检测

COPYRIGHT © 2024 jianchihu.net. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang