WebRTC中的pacing模块主要负责拥塞控制,其中RoundRobinPacketQueue是模块里的核心数据结构,项目开发中也要做类似的拥塞控制模块,WebRTC中的RoundRobinPacketQueue设计思路也很值得借鉴。
1、模块定义
RoundRobinPacketQueue名字中已经给出了答案,这是一个特殊的队列,用于管理数据包。Pacing大概的调用关系如图所示,上游模块把数据包扔给Pacing模块,Pacing模块最终会将数据包放入自己维护的RoundRobinPacketQueue对象中,然后Pacing内部启动了一个定时器,每个5ms会根据带宽情况合理的从RoundRobinPacketQueue中pop数据,然后发送出去。
2、RoundRobinPacketQueue接口定义
RoundRobinPacketQueue的对外接口定义如下,从对外接口看,关键是实现push和pop,RoundRobinPacketQueue内部实现对数据包的有效管理,对外就是一个queue的接口调用。
class RoundRobinPacketQueue {
public:
//构造函数
RoundRobinPacketQueue(Timestamp start_time,
const WebRtcKeyValueConfig* field_trials);
~RoundRobinPacketQueue();
// push具有不同优先级的数据
void Push(int priority,
Timestamp enqueue_time,
uint64_t enqueue_order,
std::unique_ptr<RtpPacketToSend> packet);
// 弹出一个最高优先级的数据包
std::unique_ptr<RtpPacketToSend> Pop();
// 是否为空
bool Empty() const;
// 数据包的数量
size_t SizeInPackets() const;
// 数据包的总字节数
DataSize Size() const;
// 当前最高优先级的数据包如果是音频包,则返回该数据包的入队时间戳,
// 如果不是音频包,则返回nullopt
// If the next packet, that would be returned by Pop() if called
// now, is an audio packet this method returns the enqueue time
// of that packet. If queue is empty or top packet is not audio,
// returns nullopt.
absl::optional<Timestamp> LeadingAudioPacketEnqueueTime() const;
// 队列中的最早的入队时间
Timestamp OldestEnqueueTime() const;
TimeDelta AverageQueueTime() const;
void UpdateQueueTime(Timestamp now);
void SetPauseState(bool paused, Timestamp now);
void SetIncludeOverhead();
void SetTransportOverhead(DataSize overhead_per_packet);
...
};
3、QueuedPacket对象
Pacing模块通过push接口将数据包压入到RoundRobinPacketQueue中,RoundRobinPacketQueue定义了一个QueuePacket对象用于包裹外部传进来的数据包数据。
struct QueuedPacket {
public:
QueuedPacket(int priority,
Timestamp enqueue_time,
uint64_t enqueue_order,
std::multiset<Timestamp>::iterator enqueue_time_it,
std::unique_ptr<RtpPacketToSend> packet);
QueuedPacket(const QueuedPacket& rhs);
~QueuedPacket();
// 此函数很关键,用于进行优先级队列的比较
bool operator<(const QueuedPacket& other) const;
int Priority() const;
RtpPacketMediaType Type() const;
uint32_t Ssrc() const;
Timestamp EnqueueTime() const;
bool IsRetransmission() const;
uint64_t EnqueueOrder() const;
RtpPacketToSend* RtpPacket() const;
std::multiset<Timestamp>::iterator EnqueueTimeIterator() const;
void UpdateEnqueueTimeIterator(std::multiset<Timestamp>::iterator it);
void SubtractPauseTime(TimeDelta pause_time_sum);
private:
int priority_;
Timestamp enqueue_time_; // Absolute time of pacer queue entry.
uint64_t enqueue_order_;
bool is_retransmission_; // Cached for performance.
// 此字段汉关键,用于记录当前对象的时间戳插入到enqueue_times_中的位置
// 当改数据包pop时,该字段用于将enqueue_times_中的相应时间戳删除
std::multiset<Timestamp>::iterator enqueue_time_it_;
// 原始的数据包
// Raw pointer since priority_queue doesn't allow for moving
// out of the container.
RtpPacketToSend* owned_packet_;
};
4、基于Stream的优先级队列
RoundRobinPacketQueue内部是以Stream为单位进行数据包管理的,每个Stream对应一路独立的SSRC数据流,相当于每路数据流,有一个Stream与之对应。Stream的类成员变量有一个SSRC用于标识流的SSRC数值。每个Stream对象都维护了一个基于C++标准库的优先级队列。
<