目录
webrtc之精读audio jitterbuffer
前言:
先看从网上摘来的2张图片,webrtc核心之一的技术就是其neteq,从图中可以看出其所处位置,及它的模块构成这里网上参考文章很多,不详细介绍,下面文献主要是阅读过程中的一些参考。
- 抗抖动:增加buffer来抵抗网络造成的抖动,让buffer中的数据平稳输出
- 去重:将收到的重复包丢弃。
- 触发重传:jitter buffer中每个packet(包)都有sequence number, 即包的序列号,序列号有间隔,则说明有丢包;有丢包则需要重传。
如何计算jitter
两个连续的包,收到的时间点之差,减去,发送的时间点之差,即是当前的抖动,计算公式如下
jitter = (packet[i].recvTime - packet[i-1].recvTime) - (packet[i].sendTime - packet[i-1].sendTime);
jitter > 0 说明需要加速播放
jitter < 0 说明需要延迟播放
一般音频数据不止一个包,连续的包,则会产生一系列的抖动值。
一 NetEq之Inserpacket
1.1 InsertPacketInternal
insertpacket主体流程代码主要是InsertPacketInternal,总体流程如下:
- 将数据放入局部变量 PacketList 中
- 处理 RTP 包逻辑
- 转换内外部时间戳
- NACK(否定应答) 处理
- 判断冗余大包(RED)并解为小包
- 检查包类型
- 判断并处理 DTMF(双音多频) 包
- 带宽估算
- 分析包
- 去除噪音包
- 解包头获取包的信息
- 计算除纠错包和冗余包以外的正常语音包数量
- 将语音包插入 PacketBuffer(抖动缓冲区)
int NetEqImpl::InsertPacketInternal(const RTPHeader& rtp_header,
rtc::ArrayView<const uint8_t> payload,
uint32_t receive_timestamp) {
if (payload.empty()) { //判断是否payload为空
RTC_LOG_F(LS_ERROR) << "payload is empty";
return kInvalidPointer;
}
PacketList packet_list; //局部变量packet_list,存放生成的语音包
// Insert packet in a packet list.
packet_list.push_back([&rtp_header, &payload] {
// Convert to Packet.
Packet packet;
packet.payload_type = rtp_header.payloadType;
packet.sequence_number = rtp_header.sequenceNumber;
packet.timestamp = rtp_header.timestamp;
packet.payload.SetData(payload.data(), payload.size());
// Waiting time will be set upon inserting the packet in the buffer.
RTC_DCHECK(!packet.waiting_time);
return packet;
}()); //将语音包通过lamda表达式调用生成push_back至packet_list
bool update_sample_rate_and_channels = //是否是首包或者发现ssrc_发生变化
first_packet_ || (rtp_header.ssrc != ssrc_);
if (update_sample_rate_and_channels) { //首次 曾重置源码时间戳缩放类
/*
WebRTC中的时间戳缩放类用于将外部时间戳转换为内部时间戳,或者将内部时间戳转换为外部时间戳。
内部 / 外部时间戳概念
外部时间戳即为RTP携带的时间戳字段,它表示RTP报文发送的时钟频率,在语音中通常等于pcm语音的采样
率(RTP携带Opus编码时 时钟频率设置为固定的48kHz,而采样率可以有很多值),在视频中无论是哪种是
视频编码,外部时间戳(时钟频率)都设置为固定的90kHz。
外部时间戳转换为内部时间戳就是将外部时间戳按照采样率缩放。设初始内部时间戳为0,则
内部时间戳 += 外部时间戳间隔 * ( 采样率 / 外部时间戳 )
相反,用内部时间戳转换为外部时间戳就是按照采样率扩大。设初始外部时间戳为0,则
外部时间戳 += 内部时间戳间隔 * ( 外部时间戳 / 采样率 )
*/
// Reset timestamp scaling.
timestamp_scaler_->Reset();
}
if (!decoder_database_->IsRed(rtp_header.payloadType)) { //非red冗余包则转换为内部时间戳
// Scale timestamp to internal domain (only for some codecs).
timestamp_scaler_->ToInternal(&packet_list);
}
// Store these for later use, since the first packet may very well disappear
// before we need these values.
uint32_t main_timestamp = packet_list.front().timestamp; //主时间戳
uint8_t main_payload_type = packet_list.front().payload_type;
uint16_t main_sequence_number = packet_list.front().sequence_number;
// Reinitialize NetEq if it's needed (changed SSRC or first call).
if (update_sample_rate_and_channels) { //首次则初始话相关数据结构
// Note: |first_packet_| will be cleared further down in this method, once
// the packet has been successfully inserted into the packet buffer.
rtcp_.Init(rtp_header.sequenceNumber); //初始话rtcp_模块
// Flush the packet buffer and DTMF buffer.
packet_buffer_->Flush(); //包缓存
dtmf_buffer_->Flush(); //看rfc这个用来保存来电呼叫铃声? 本地调试未看见
// Store new SSRC.
ssrc_ = rtp_header.ssrc; //初始化ssrc_
// Update audio