SQP拥塞控制算法实现和对比实验

SQP拥塞控制算法介绍

一种为低延迟强交互视频流设计的拥塞控制算法, google paper:https://arxiv.org/pdf/2207.11857.pdf

在交互视频应用场景,需要传输高码率的视频率,并且保持极低的端到端时延,比如AR流和云游戏,SQP就是为这种场景而设计的拥塞控制算法。SQP采用基于帧的整形数据包来采样网络带宽,并用自适应的单向采样时延测量方法从网络队列中恢复,从而维持极低的队列时延。SQP快速适应网络带宽变化,确保高带宽利用率和低帧时延,并且当存在竞争流时也能在保证可接受的时延内拥有合理的带宽份额。SQP有不错的公平性,网络上有影子缓存时也工作得挺好。

低延时交互流媒体应用生成固定帧率的裸帧。视频码率由ABR(Adaptive bitrate algorithm 码率自适应算法)算法决定,而ABR算法又是由CCA(Congestion control algorithm,拥塞控制算法)产生的信号来决定, 从而管理帧时延、网络拥塞、带宽利用率。压缩的帧通过网络传输,然后在客户端设备上解码和显示。

低时延视频流架构:


低时延视频流

SQP内部架构:


SQP内部框架

我是为低时延交互流媒体应用设计的拥塞控制算法, 比如云游戏,云VR等应用。 这一套拥塞控制算法的目标是: 1. 提供实时的带宽估计,以确保尽可能大的带宽利用率和尽可能低的端到端帧时延;2. 与其它基于网络列队的拥塞控制算法共存时,也可以提供有竞争力的吞吐量。

SQP算法实现

Feeback: 包级别的网络反馈 + 帧级别的网络反馈

#include "sqp_feedback_adaptor.h"

#include <algorithm>
#include <cstdlib>

#include "common/logger.h"
#include "common_time.h"
namespace BCC {
#define HISTORY_CACHE_MS 60000
#define SRTT_FACTOR 0.6

SQPFeedbackAdaptor::SQPFeedbackAdaptor() {
    int i;

    for (i = 0; i < FEEDBACK_RTT_WIN_SIZE; ++i) {
        rtts_[i] = -1;
    }

    min_feedback_rtt_ = 10;
    num_ = 0;
    index_ = 0;
    hist_ = new SQPSenderHistory(HISTORY_CACHE_MS);
}

SQPFeedbackAdaptor::~SQPFeedbackAdaptor() {
    if (hist_) {
        delete hist_;
        hist_ = nullptr;
    }

    for (auto const& pair : frame_map_) {
        delete pair.second;
    }
    frame_map_.clear();
}

void SQPFeedbackAdaptor::AddPacket(uint16_t seq, size_t size) {
    PacketFeedBackItem packet;
    packet.arrival_ts = -1;
    packet.create_ts = packet.send_ts = GET_SYS_MS();
    packet.payload_size = size;
    packet.sequence_number = seq;
    hist_->Add(&packet);
}

void SQPFeedbackAdaptor::AddFrame(uint32_t frame_idx, size_t packet_size, bool first_packet, bool last_packet) {
    FrameFeedBackItem* frame_ptr;
    int64_t now_ts = GET_SYS_MS();
    if (frame_map_.find(frame_idx) != frame_map_.end()) {
        frame_ptr = frame_map_[frame_idx];
    }
    else {
        frame_ptr = new FrameFeedBackItem();
        frame_ptr->frame_index = frame_idx;
        frame_map_[frame_idx] = frame_ptr;
    }

    frame_ptr->frame_size += packet_size;
    if (first_packet) {
        frame_ptr->send_start_ts = now_ts;
    } 

    if (last_packet) {
        frame_ptr->send_end_ts = now_ts;
        hist_->AddFrame(frame_ptr);
        delete frame_ptr;
        frame_map_.erase(frame_idx);
    }
}

int SQPFeedbackAdaptor::OnFeedback(BCC_SQP::FeedBackMsgItem* msg) {
    int32_t i = 0, feedback_rtt = 0;
    int64_t now_ts = GET_SYS_MS();
    int64_t delta_ts = 0;
    feedback_rtt = -1;
    num_ = 0;

    for (i = 0; i < msg->samples_num; i++) {
        //根据反馈的SEQ获取对应的报文发送信息,计算反馈RTT,更新报文到达时刻
        if (hist_->Get(msg->samples[i].seq, &packets_[num_], 1) == 0) {
            //计算反馈RTT
            if (packets_[num_].send_ts > 0) {
                feedback_rtt = (std::max)(now_ts - packets_[num_].send_ts, (int64_t)feedback_rtt);
                rtts_[index_++ % FEEDBACK_RTT_WIN_SIZE] = feedback_rtt;
                srtt_ = SRTT_FACTOR * srtt_ + (1 - SRTT_FACTOR) * feedback_rtt;
            }

            //更新到达的值
            packets_[num_].arrival_ts = msg->samples[i].ts;
            delta_ts = packets_[num_].arrival_ts - packets_[num_].send_ts;
            if (new_min_one_way_delay_ == 0 || new_min_one_way_delay_ > delta_ts) {
                new_min_one_way_delay_ = delta_ts;
            }
            num_++;

            //更新时间窗内的最小one way delay的值
            if (now_ts - last_one_way_delay_update_ts_ > srtt_ * 2) {
                last_one_way_delay_update_ts_ = now_ts;
                min_one_way_delay_ = new_min_one_way_delay_;
                new_min_one_way_delay_ = 0;
            }

        }
    }

    frame_num_ = 0;
    for (i = 0; i < msg->frame_samples_num; i++) {
        //根据反馈的SEQ获取对应的报文发送信息,计算反馈RTT,更新报文到达时刻
        if (hist_->GetFrame(msg->frame_samples[i].frame_index, &frames_[frame_num_], 1) == 0) {
            //更新到达的值
            frames_[frame_num_].arrival_start_ts = msg->frame_samples[i].arrival_start_ts;
            frames_[frame_num_].arrival_end_ts = msg->frame_samples[i].arrival_end_ts;

            if (max_frame_size_ < frames_[frame_num_].frame_size) {
                max_frame_size_ = frames_[frame_num_].frame_size;
            }
            frame_num_++;
        }
    }

    //更新报文与反馈的rtt最小值
    if (feedback_rtt > 0) {
        min_feedback_rtt_ = rtts_[0];

        for (i = 1; i < FEEDBACK_RTT_WIN_SIZE; i++) {
            if (min_feedback_rtt_ > rtts_[i] && rtts_[i] > 0) {
                min_feedback_rtt_ = rtts_[i];
                LOGD("[bcc][feedback] min feed back rtt update {}", min_feedback_rtt_);
            }
        }
    }

    //进行按到达时间的先后顺序进行排序
    FeedbackQsort();
    FrameFeedbackQsort();
    return num_;
}
}  // namespace BCC

带宽估计和发送速率控制:

#include "sqp_congestion_control.h"
#include "common/logger.h"

namespace BCC {
        SQPCongestionControl::SQPCongestionControl(uint32_t min_bitrate, uint32_t max_bitrate) {
                min_bitrate_ = min_bitrate;
                max_bitrate_ = max_bitrate;
                bandwidth_ = min_bitrate_;
        }
    SQPCongestionControl::~SQPCongestionControl() {

        }

        void SQPCongestionControl::UpdateBandwidthEstimator(uint32_t frame_size, uint32_t max_frame_size, int64_t frame_send_start_ts,
                int64_t frame_send_end_ts, int64_t frame_recv_start_ts, int64_t frame_recv_end_ts, int32_t one_way_delay) {

                double bandwidth_sample = 0.0f;
                double gama = ((double)max_frame_size) / frame_size;
                if (frame_recv_end_ts == frame_send_start_ts && frame_recv_end_ts == frame_recv_start_ts) {
                        return;
                }
                bandwidth_sample = frame_size * 8 * gama * 1000 / (frame_recv_end_ts - frame_send_start_ts - one_way_delay + (frame_recv_end_ts - frame_recv_start_ts) * (gama - 1));
                bandwidth_sample *= T_;
                if (bandwidth_ <= 0) {
                        bandwidth_ = bandwidth_sample;
                }
                else {
                        bandwidth_ = bandwidth_ + delta_ * (r_ * (bandwidth_sample / bandwidth_ - 1) - (bandwidth_ / bandwidth_sample - 1));
                }

        
        }

        uint32_t SQPCongestionControl::ComputePacingRate() {
                return (uint32_t)(bandwidth_ * m_);
                return 0;
        }

}; 

对比实验:

  1. 采用panthoen实验平台

    1. 安装panthoen: 以下三个主要组件

    2. Local 和 remote两种网络模式

  2. 编译llama-sqp生成sender和receiver, 拷贝到panthoen/third_party/llama_sqp下

修改配置:

  • 生成src/wrappers/llama_sqp.py:
#!/usr/bin/env python

'''REMOVE ME: Example file to add a new congestion control scheme.

Use Python 2.7 and conform to PEP8.
Use snake_case as file name and make this file executable.
'''

from os import path
from subprocess import check_call

import arg_parser
import context

def main():
    # use 'arg_parser' to ensure a common test interface
    args = arg_parser.receiver_first()  # or 'arg_parser.sender_first()'

    # paths to the sender and receiver executables, etc.
    cc_repo = path.join(context.third_party_dir, 'llama_sqp')
    send_src = path.join(cc_repo, 'transport_sender')
    recv_src = path.join(cc_repo, 'transport_receiver')

    # [optional] dependencies of Debian packages
    if args.option == 'deps':
        print 'example_dep_1 example_dep_2'
        return

    # [optional] persistent setup that only needs to be run once
    if args.option == 'setup':
        # avoid running as root here
        return

    # [optional] non-persistent setup that should be performed on every reboot
    if args.option == 'setup_after_reboot':
        # avoid running as root here
        return

    # [required] run the first side on port 'args.port'
    if args.option == 'receiver':
        cmd = [recv_src, args.port]
        check_call(cmd)
        return

    # [required] run the other side to connect to the first side on 'args.ip'
    if args.option == 'sender':
        cmd = [send_src, args.ip, args.port]
        check_call(cmd)
        return

if __name__ == '__main__':
    main()
  • 配置src/config.yml添加llama_sqp

  • 运行实验:

src/experiments/setup.py --install-deps --schemes "bbr copa cubic vivace llama_bcc llama_sqp"
src/experiments/setup.py --schemes "bbr copa cubic vivace llama_bcc llama_sqp"
src/experiments/test.py local --schemes "bbr copa cubic vivace llama_bcc llama_sqp"
src/analysis/analyze.py --data-dir src/experiments/data/

实验报告:

  1. 本地网络实验:


    算法对比

    算法对比

Llama-SQP时延最低,但吞吐量只有72.3%,远没有利用好带宽, 待改进。

以下是详细的实验数据:


SQP详细实验数据
  1. 远程网络实验
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • TCP通过维护一个拥塞窗口来进行拥塞控制,拥塞控制的原则是,只要网络中没有出现拥塞,拥塞窗口的值就可以再增大一些,...
    风亡小窝阅读 1,774评论 0 1
  • 拥塞控制算法分类 基于丢包(loss rate)的拥塞控制算法例如TCP中早期的拥塞控制算法Reno, 会带来较高...
    myxu_bin阅读 3,290评论 1 1
  • 一、TCP Reno拥塞控制算法回顾 二、基于TCP Reno拥塞控制算法的改进 改进原因分析TCP Reno 提...
    风亡小窝阅读 2,347评论 0 0
  • 网络传输问题本质上是对网络资源的共享和复用问题,因此拥塞控制是网络工程领域的核心问题之一,并且随着互联网和数据中心...
    DeepNoMind阅读 717评论 0 0
  • TCP拥塞控制算法的目的可以简单概括为:公平竞争、充分利用网络带宽、降低网络延时、优化用户体验,然而就目前而言要实...
    VictorHong阅读 2,277评论 0 0