文章目录

  • 简介
  • 流程
  • 详细
    • 开启
    • 准备切换
    • 进行切换
    • 丢包
    • 信息重写
  • 总结

简介

本篇文章介绍Licode是怎么做时域+空域svc的丢包的,所有的代码都是集中在QualityFilterHandler.cpp中。不包含弱网自动降级的策略,弱网自动降级会在在接下来文章介绍。
时域 svc:可以删掉不同时域层级的帧,最后表现出来的就是帧率不一样。
空域 svc:可以删掉不同空域层级的帧,不同空域的分辨率不一样。空域svc有两种,大小流的空域svc和单流的空域svc。

流程

licode中大概流程如下图。总结就是:判断当前包不属于当前时域或者当前空域我们就把它丢弃。

详细

开启

licode通过detectVideoScalability这个函数判断是否需要开启丢帧的功能。

void QualityFilterHandler::detectVideoScalability(const std::shared_ptr<DataPacket> &packet) {//如果已经开启了,直接返回if (is_scalable_ || packet->type != VIDEO_PACKET) {return;}//当这个包rid不等于0//时域属于1//空域属于1if (packet->rid != "0" ||packet->belongsToTemporalLayer(1) ||packet->belongsToSpatialLayer(1)) {is_scalable_ = true;quality_manager_->enable();}
}

从上面代码可以看到,is_scalable_ 是用于标记是否开启该功能。

当有以下情况时is_scalable_会被标记为true:

  1. 收到rid为非0的包,也就是除了rid == 0 的流还有rid != 0的流。这种情况代表了开启了simulcast,可以做空域的svc。

  2. 通过belongsToSpatialLayer判断当前包属于空域1。这种情况包含非simulcast的空域svc,在一些视频编码格式(如vp9)中不需要由大小流做svc,一个视频流中有空域的svc层。

  3. 通过belongsToTemporalLayer判断当前包属于时域1,具有时域的svc。

在上面3种情况下,会开启svc的功能。
并且会调用quality_manager_->enable()这个函数,开启了自动降级的策略,自动降级的策略简要的说:当带宽探测发现当前的带宽不足以发送当前码率视频时,会开启自动降低时域或者空域的码率。

准备切换

在切时域和空域的时候分为准备进行。在checkLayers函数中会去拿quality_manager_的结果,用于判断是否需要准备切换时域或者空域。

void QualityFilterHandler::checkLayers() {// 从弱网降级模块获取当前最新的空域层级int new_spatial_layer = quality_manager_->getSpatialLayer();// 当弱网降级模块给的空域层级不是当前的层级 && 没有正在改变层级if (new_spatial_layer != target_spatial_layer_ && !changing_spatial_layer_) {// 发送pliif (new_spatial_layer > target_spatial_layer_) {sendPLI(LOW_PRIORITY);} else {sendPLI();}//并且把要切换到的层级记录到future_spatial_layer_future_spatial_layer_ = new_spatial_layer;//正在切换changing_spatial_layer_ = true;//记录开始准备切换的时间time_change_started_ = clock::now();}int new_temporal_layer = quality_manager_->getTemporalLayer();//获取最新时域的层级target_temporal_layer_ = new_temporal_layer;
}

quality_manager_会根据带宽估计的结果,评估当前发送那一层数据。

当带宽评估出来的空域层级和当前的发送的层级不相同时,会发送pli,并且将future_spatial_layer_设置为带宽评估出来的空域层级。记录下准备切换的时间,这个时间主要是用于后面判断是否切换超时了。

相对于空域层级,这里会直接设置时域的层级。

在我看来带宽评估应该尽可能的准确,这边是一个可优化的点,带宽评估应该给出当前正在发送空域层级应该设置什么时域层级,未来要发送的空域层级应该发送什么时域层级。

进行切换

changeSpatialLayerOnKeyframeReceived函数中会真正的进行切换。当收到future_spatial_layer_层级的包 &&该包时域是当前时域&&该包是关键帧,或者当时间超过切换的时间,就会开始进行切换。

void QualityFilterHandler::changeSpatialLayerOnKeyframeReceived(const std::shared_ptr<DataPacket> &packet) {// 表示已经准备切换if (future_spatial_layer_ == -1) {return;}// 记录当前时间time_point now = clock::now();// 属于未来的空域 && 属于当前的时域 && 关键帧if (packet->belongsToSpatialLayer(future_spatial_layer_) &&packet->belongsToTemporalLayer(target_temporal_layer_) &&packet->is_keyframe) {// 将当前空域设置为需要切换的空域target_spatial_layer_ = future_spatial_layer_;// 已经进行切换了future_spatial_layer_ = -1;changing_spatial_layer_ = false;} else if (now - time_change_started_ > kSwitchTimeout) {// 当还没有收到关键帧 && 并且超过一段时间 会再次请求关键帧// 并且强制切换sendPLI();target_spatial_layer_ = future_spatial_layer_;future_spatial_layer_ = -1;changing_spatial_layer_ = false;}
}

这个函数中两种情况会进行切换

  1. 收到要切换到空域层级的包 && 属于当前时域 && 是关键帧
  2. 切换层级的时间已经超时了
    在上面这两种情况下会进行切换,把当前层级切换到future_spatial_layer_

丢包

空域丢包。丢包都相对简单,如果不是当前的空域直接丢弃。标记一下对应的seq和pid

//如果包不是当前层级的直接丢弃
if (!packet->belongsToSpatialLayer(target_spatial_layer_)) {if (!receiving_multiple_ssrc_) {//标记这个seq和pid是被删除了,需要被跳过的translator_.get(sequence_number, true);picture_id_translator_.get(picture_id, true);}return;
}

时域丢包。

//如果不是当前时域层级,直接丢弃
if (!packet->belongsToTemporalLayer(target_temporal_layer_)) {//标记这个seq和pid是被删除了,需要被跳过的translator_.get(sequence_number, true);picture_id_translator_.get(picture_id, true);return;
}

信息重写

信息重写是整个环节很重要的一环,如果你不重写的话会有各种问题。在licode中需要改变的有ssrc、seqnumber、timestamp、pictureID(vp8)、tl0PicIdx(vp8)。

void QualityFilterHandler::write(Context *ctx, std::shared_ptr<DataPacket> packet) {//....省略相关代码if (is_scalable_ && !chead->isRtcp() && enabled_ && packet->type == VIDEO_PACKET) {//....省略相关代码uint32_t ssrc = rtp_header->getSSRC();uint16_t sequence_number = rtp_header->getSeqNumber();int picture_id = packet->picture_id;uint8_t tl0_pic_idx = packet->tl0_pic_idx;//...省略相关代码uint32_t new_timestamp = rtp_header->getTimestamp();if (checkSSRCChange(ssrc)) {//当监测到切换了空域,也就是大小流切换的时候//seq必然有跳变,需要重置,避免因为跳变太大,被判断成有问题的包translator_.reset();//pid也一样,必然有跳变,需要重置,避免因为跳变太大,被判断成有问题的包picture_id_translator_.reset();//timestamp也一样,需要重新设置offsetif (last_timestamp_sent_ > 0) {timestamp_offset_ = last_timestamp_sent_ - new_timestamp + 1;}//last_tl0_pic_idx_sent_也一样,需要重新设置offsetif (last_tl0_pic_idx_sent_ > 0) {tl0_pic_idx_offset_ = last_tl0_pic_idx_sent_ - tl0_pic_idx + 1;}}//更新统一下行ssrcrtp_header->setSSRC(video_sink_ssrc_);//更新seqrtp_header->setSeqNumber(sequence_number_info.output);//计算出新的timestamplast_timestamp_sent_ = new_timestamp + timestamp_offset_;rtp_header->setTimestamp(last_timestamp_sent_);//计算出新的pidupdatePictureID(packet, picture_id_info.output & 0x7FFF);//计算出新的tl0PicIdxuint8_t tl0_pic_idx_sent = tl0_pic_idx + tl0_pic_idx_offset_;last_tl0_pic_idx_sent_ = RtpUtils::numberLessThan(last_tl0_pic_idx_sent_, tl0_pic_idx_sent, 8) ?tl0_pic_idx_sent : last_tl0_pic_idx_sent_;//更新tl0PicIdxupdateTL0PicIdx(packet, tl0_pic_idx_sent);}ctx->fireWrite(packet);
}

在write函数中,去除之前章节讲过代码,大概就剩下上面的代码。
translator_picture_id_translator_SequenceNumberTranslator对象,SequenceNumberTranslator主要有以下功能:

  1. 保证输出的SequenceNumber是连续的
  2. 忽略跳变太大的输入SequenceNumber
  3. 标记需要跳过的输入SequenceNumber(在这几乎没用)

因为在删除不同时域上包时,输入的Seq是不连续的。所以你需要做一个转换,这个Translator主要就是做这个。简单来说就是根据输入的Seq,尽量的输出连续的Seq(为什么说是尽量的输出,当在接收侧Seq乱序+时域丢包,这个策略就会有问题)。

timestamp和tl0PidIdx通过offset来保证大致的连续性,在ssrc改变的时候会重新计算一下offset,保证在大小流空域svc切换时是连续的。

在大小流空域切换时,timestamp和tl0PidIdx都会有很大的跳变(当然seq和pid也会),可能会导致接收端丢弃的问题,花屏问题。

ps:本人就遇到过一个在vp8+大小流场景下,ssrc切换时pid回退,导致关键帧被丢弃花屏问题。

总结

这篇文章大概介绍了licdoe svc怎么做大小流切换,并且怎么做丢包,丢包后需要做什么。内容也不是特别复杂,下一篇我们会介绍具体是怎么决策需要降级的。

Licode实现webrtc svc(1)相关推荐

  1. 基于Licode的WebRTC全球分布式架构

    随着在线教育行业的兴起, 许多人把目光投向了国外市场,而如何搭建全球化的音视频网络就成为了其中的关键问题.百家云研发工程师陈聪详细介绍了如何利用Licode 开源服务器搭建全球分布式架构以解决常见的教 ...

  2. Licode—基于webrtc的SFU/MCU实现

    1. webrtc浅析 webrtc的前世今生.编译方法.行业应用.最佳实践等技术与产业类的文章在网上卷帙浩繁,重复的内容我不再赘述.对我来讲,webrtc的概念可以有三个角度去解释: (1).一个W ...

  3. 基于licode搭建webrtc服务器

    https://www.cnblogs.com/changdingfang/p/11562088.html https://www.jianshu.com/p/dcc5ba06b49f

  4. 阿里云上搭建webRTC 服务器——Licode

    阿里云上搭建webRTC 服务器--Licode 系统配置 阿里云服务器 Ubuntu 14.04.5 LTS Docker 环境搭建 在一台空的机器上搭建docker环境,先要安装docker,执行 ...

  5. 如何打造自己的WebRTC 服务器

    1.引言 近年来,直播竞答.网络游戏直播等新的实时音视频通讯场景不断推陈出新,并成为引领互联网娱乐风向的弄潮儿.实时音视频应用的爆发,也使得WebRTC(Web Real-Time Communica ...

  6. 互动直播之WebRTC服务开源技术选型【转】

            最近研究了一下会议服务器相关的知识,看到了这篇文章,介绍了很基础的概念说明和选型比较,这里转载分享一下. 转自:互动直播之WebRTC服务开源技术选型 - 掘金 1 直播基础知识 最原 ...

  7. 后端: 互动直播之WebRTC服务开源技术选型

    1 直播基础知识 最原始的直播系统其实并没有想象的那么复杂,无非就是主播端将音视频数据推送到服务器,观众端则从服务器拉取数据播放. 1.1 基本常识 1.1.1 基础概念 推流  推流,是直播中的一个 ...

  8. 互动直播之WebRTC服务开源技术选型

    基于WebRTC的低延迟视频直播 https://xw.qq.com/cmsid/20200304A04E3A00 可以用WebRTC来做视频直播吗? https://www.zhihu.com/qu ...

  9. 直播软件开发互动直播之WebRTC服务开源技术选型

    直播软件开发互动直播之WebRTC服务开源技术选型 1 直播基础知识 最原始的直播系统其实并没有想象的那么复杂,无非就是主播端将音视频数据推送到服务器,观众端则从服务器拉取数据播放. 1.1 基本常识 ...

最新文章

  1. LightOJ 1088 - Points in Segments 二分
  2. python菜鸟excel教程-Python菜鸟之路: 封装通用excel操作
  3. 凌晨1点突发致命生产事故!看的我惊心动魄…
  4. 德华安顾人寿签约神策数据,精耕数字化加速保险服务质效升级
  5. [笔记] systemverilog学习笔录
  6. 阿里巴巴消息中间件: Spring Cloud Stream
  7. c 结构体中的变长数组
  8. 元宵节快乐 | 2月15日 星期二 | 携程在国内率先开启混合办公模式;米哈游推出元宇宙品牌;AMD宣布完成对赛灵思的收购...
  9. ubuntu11.04中nautilus(文件管理器)查看FTP乱码的解决办法
  10. 推荐常用的小程序Ui框架
  11. Sprin boot 加载位置顺序
  12. java Hashset去重原理及HashMap key唯一原理
  13. txt代码文件怎么转换_pdf怎么转换成txt格式?小说党速来get
  14. CodeForces 584 D.Dima and Lisa(数论)
  15. 新手CrossApp 之demo SecondViewController小结
  16. ABAP中如何建数据库视图和维护视图
  17. A股市股票行情实时数据最简封装API接口的python实现
  18. LTE(4G) GUTI分配流程
  19. 前端登陆之cookie篇
  20. 云计算学习笔记——第四章 存储虚拟化

热门文章

  1. 电视行业的三大回春良药:升级技术、加强实用性、优化服务
  2. 03_过滤器的配置介绍(通过注解进行配置)
  3. 机器学习-hjasfgiudgasu
  4. 百度开放云爱数,共推混合云!
  5. js异步实例之跨域获取图片
  6. 【小作品】STM32无线WIFI视频小车制作剖析(上)
  7. Java程序员职业规划如何做?
  8. 【2023程序员必看】大数据行业分析
  9. 优客工场携手首旅如家 双巨头开启创享办公空间新模式
  10. 从 Smartsheet 打印工作表或报告