webrtc 共享屏幕延时测试
1.可能导致延时的因素
测试方向
- 音频对延时的影响,音频的处理耗时,以及音视频同步耗时;
- 视频抖动缓冲延时,在局域网内,如果缩小抖动缓冲区,可能会减少延时;
测试方法
由于测试目的是为了分析 webrtc 在局域网内的延时情况,所以选择在本地主机和虚拟机之间测试通信延时。
因为是双屏,所以本地主机和虚拟机各占一个显示屏,将虚拟机的屏幕投递到本地主机,通过点击键盘上的 Print Screen键,可以同时捕获两个屏幕,再将捕获的图片粘贴到画图板或者通过其他软件打开,可以比较两者的延时。
2.测试音频共享屏幕延时
2.1 共享音频
在开始共享屏幕的时候,会弹出对话框让选择是否共享音频,通过比较勾选和取消勾选共享音频选项,判断音频对共享屏幕延时的影响。
(1)通过在浏览器上开启在线秒表,观察投递源和目标屏幕上的时间。在开始共享屏幕之前就开启在线秒表,刚开始共享屏幕时,源屏幕上的时间为:
目标屏幕上接收到共享屏幕显示的时间为:
可以看到延时 2 秒加 752 毫秒,数量级上肯定达到秒级的。等待一段时间,延时最终会收敛,稳定到某一个较小的数值范围,达到200到400毫秒的范围,当然可能更大或者更小,但是数量级上是100毫秒。
(2)经过等待,进行第二次采样
源屏幕上的时间为:
目标屏幕上的时间为:
两个屏幕上的时间差为 264 ms。
(3)进行第三次采样
源屏幕上的时间为:
目标屏幕上的时间为:
两个屏幕上的时间差为 269 ms。
这是勾选了音频选项情况下的延时,可见稳定的延时在 200ms 左右。
2.2 不共享音频
(1) 取消勾选共享音频的选项,刚开始共享屏幕时,源端秒表计时为:
目的端秒表计时为:
可以看到两者的延时为 1秒加495 毫秒。
(2) 经过一段时间的等待,继续观察延时情况,源端的秒表计时为:
目的端的秒表计时为:
延时趋于稳定到某一个范围后,两者的延时为 171ms。
(3) 再次取样,观察延时时间
源端的秒表计时为:
目的端的秒表计时为:
延时还在继续减小,此时的延时为 79ms 。
(4) 第四次取样,观察延时时间
源端秒表计时为:
目的端秒表计时为:
延时又增大了,达到 143ms。
后续又观察了很多次,延时时间围绕 100ms 波动,但一般都在 50 ~ 200 毫秒的范围内。
2.3 小结
通过多次测试比较,发现共享音频会造成额外的几十到几百毫秒的延时,根据之前博客的分析,在一次测试中, webrtc 的日志文件中记录的 WebRTC.Video.AVSyncOffsetInMs 平均时间是 65 毫秒。因此,猜测可能是因为音视频同步造成了额外的延时。
3.更改抖动缓冲区的大小
在 webrtc 中视频抖动缓冲区是动态变化的,因为目前的屏幕共享仅限于局域网,因此可以考虑将抖动缓冲区设置为较小的固定值。
3.1 webrtc 抖动缓冲
音视频通信过程中,接收方在收到对方的音视频流数据后,数据流会进入缓冲区,缓冲一定的时间才开始播放,这样可以消除网络抖动对通信质量的影响,缓冲时间越长,应对网络抖动的能力越强,但是延迟也越大。不同的应用场景,不同的网络环境,可以设置不同的缓冲时间。
webrtc 的抖动缓冲代码主要在:
src\third_party\webrtc\modules\video_coding\jitter_estimator.cc
src\third_party\webrtc\modules\video_coding\jitter_estimator.h
src\third_party\webrtc\modules\video_coding\frame_buffer2.cc
src\third_party\webrtc\modules\video_coding\frame_buffer2.h
相关类图如图所示:
其中,ref 表示只是引用,但不拥有这个对象;而 possess 则表示拥有这个对象。
当 VideoReceiveStream 对象在开始接收数据之前,会执行如下准备工作,这里只列出关心的部分代码:
void VideoReceiveStream::Start() {...frame_buffer_->Start();...// Start the decode threaddecode_thread_.Start();rtp_video_stream_receiver_.StartReceive();
}
准备工作中,启动 FrameBuffer 对象,开启解码线程,之后,开始接收数据。所有接收到的数据首先进入缓冲区,
这里写代码片void VideoReceiveStream::OnCompleteFrame(std::unique_ptr<video_coding::FrameObject> frame) {int last_continuous_pid = frame_buffer_->InsertFrame(std::move(frame));if (last_continuous_pid != -1)rtp_video_stream_receiver_.FrameContinuous(last_continuous_pid);
}
解码线程从 FrameBuffer 中取出数据进行解码,
bool VideoReceiveStream::Decode() {...std::unique_ptr<video_coding::FrameObject> frame;video_coding::FrameBuffer::ReturnReason res =frame_buffer_->NextFrame(wait_ms, &frame);if (frame) {int64_t now_ms = clock_->TimeInMilliseconds();RTC_DCHECK_EQ(res, video_coding::FrameBuffer::ReturnReason::kFrameFound);if (video_receiver_.Decode(frame.get()) == VCM_OK) {keyframe_required_ = false;frame_decoded_ = true;rtp_video_stream_receiver_.FrameDecoded(frame->picture_id);} ...}return true;
}
代码 frame_buffer_->NextFrame(wait_ms, &frame) 是从 FrameBuffer 中取出数据。
代码 video_receiver_.Decode(frame.get()) 是执行对 frame 的解码操作。
FrameBuffer 的 NextFrame 函数定义如下:
// Get the next frame for decoding. Will return at latest after
// |max_wait_time_ms|.
// - If a frame is available within |max_wait_time_ms| it will return
// kFrameFound and set |frame_out| to the resulting frame.
// - If no frame is available after |max_wait_time_ms| it will return
// kTimeout.
// - If the FrameBuffer is stopped then it will return kStopped.FrameBuffer::ReturnReason FrameBuffer::NextFrame(int64_t max_wait_time_ms,std::unique_ptr<FrameObject>* frame_out,bool keyframe_required) {...do {// 等待新的连续帧的到来...} while (new_continuous_frame_event_.Wait(wait_ms));{...std::unique_ptr<FrameObject> frame =std::move(next_frame_it_->second.frame);if (inter_frame_delay_.CalculateDelay(frame->timestamp, &frame_delay,frame->ReceivedTime())) {jitter_estimator_->UpdateEstimate(frame_delay, frame->size());}float rtt_mult = protection_mode_ == kProtectionNackFEC ? 0.0 : 1.0;timing_->SetJitterDelay(jitter_estimator_->GetJitterEstimate(rtt_mult));timing_->UpdateCurrentDelay(frame->RenderTime(), now_ms);} else {if (webrtc::field_trial::IsEnabled("WebRTC-AddRttToPlayoutDelay"))jitter_estimator_->FrameNacked();}// Gracefully handle bad RTP timestamps and render time issues.if (HasBadRenderTiming(*frame, now_ms)) {jitter_estimator_->Reset();timing_->Reset();frame->SetRenderTime(timing_->RenderTimeMs(frame->timestamp, now_ms));}UpdateJitterDelay();UpdateTimingFrameInfo();PropagateDecodability(next_frame_it_->second);...*frame_out = std::move(frame);...
}
在 FrameBuffer 的 NextFrame 函数中,最晚会在 max_wait_time_ms 后退出,如果在 max_wait_time_ms 时间内取得数据帧,就返回 kFrameFound,否则就返回 kTimeout,如果 FrameBuffer 被停止了,那么会返回 kStopped。之后,VCMJitterEstimator 计算并更新抖动估计延时的估计时间。
看了这段代码后,以为控制延时是通过 timing_->SetJitterDelay() 来设置的,但是尝试之后没有任何效果。查看代码,发现这个设置只是根据 webrtc 上下文计算当前的延时,是为了统计输出用的,这是延时的结果,而不是延时的起因。
3.2 播放延时
在 FrameBuffer 中,有一个成员函数 UpdatePlayoutDelays(),根据注释可知这个函数是“根据数据帧来更新最大和最小播放延迟”。其实现为:
void FrameBuffer::UpdatePlayoutDelays(const FrameObject& frame) {TRACE_EVENT0("webrtc", "FrameBuffer::UpdatePlayoutDelays");PlayoutDelay playout_delay = frame.EncodedImage().playout_delay_;if (playout_delay.min_ms >= 0)timing_->set_min_playout_delay(playout_delay.min_ms);if (playout_delay.max_ms >= 0)timing_->set_max_playout_delay(playout_delay.max_ms);
}
添加日志,打印 playout_delay.min_ms 和 playout_delay.max_ms ,发现这两个值均为 -1,分析 -1 代表的含义。查看 PlayoutDelay 的实现:
struct PlayoutDelay {int min_ms;int max_ms;
};
分析代码中对这个结构体的注释:
// Minimum and maximum playout delay values from capture to render.
// These are best effort values.
//
// A value < 0 indicates no change from previous valid value.
//
// min = max = 0 indicates that the receiver should try and render
// frame as soon as possible.
//
// min = x, max = y indicates that the receiver is free to adapt
// in the range (x, y) based on network jitter.
//
// Note: Given that this gets embedded in a union, it is up-to the owner to
// initialize these values.
-1 表示播出最大和最小延时和前面的有效值保持一致。
如果最大值和最小值均为0,表示接收端应当尽可能快的渲染当前数据帧。
修改前面的 UpdatePlayoutDelays() 代码,修改后代码如下:
void FrameBuffer::UpdatePlayoutDelays(const FrameObject& frame) {TRACE_EVENT0("webrtc", "FrameBuffer::UpdatePlayoutDelays");PlayoutDelay playout_delay = frame.EncodedImage().playout_delay_;if (playout_delay.min_ms >= 0)timing_->set_min_playout_delay(playout_delay.min_ms);if (playout_delay.max_ms >= 0)timing_->set_max_playout_delay(playout_delay.max_ms);timing_->set_min_playout_delay(0);timing_->set_max_playout_delay(0);
}
跟踪代码 frame.EncodedImage().playout_delay_ 寻找 playout_delay_ 首次被设置的位置,得到如下图的类继承关系图:
函数 UpdatePlayoutDelays() 的参数 FrameObject 其实是引用了 RtpFrameObject 的对象,收到数据包构建数据帧时,在 RtpFrameObject 的构造函数中有如下代码:
// Setting frame's playout delays to the same values// as of the first packet's.SetPlayoutDelay(first_packet->video_header.playout_delay);
继续跟踪下去,在 VCMPacket 的构造函数中,有如下初始化的代码:
VCMPacket::VCMPacket(): payloadType(0),...video_header(),receive_time_ms(0) {video_header.playout_delay = {-1, -1};
}
这里就是播放延时最开始赋值的位置,根据打印的日志可以看出,后面基本上都是直接使用这个默认的播出延时设置。
3.3 延时效果测试
根据上面修改的最大和最小播出延时时间,编译运行,在选择共享音频的条件下,测试延时情况。
(1)刚开始建立连接时,延时情况
源端秒表计时时间:
目的端秒表计时时间:
在刚开始共享屏幕的时候,两者延时达到2秒加454毫秒,将最大和最小播出延迟时间修改为 0,无法降低刚开始的播出延时。
(2)系统投屏一段时间后,延时情况
源端秒表计时时间:
目的端秒表计时时间:
在共享屏幕一段时间后,两者的延时惊人的降到 1ms 以下,很明显,设置最大和最小播出延迟时间为 0 起作用了。
(3) 经过一段时间之后,再次采样
源端秒表计时时间:
目的端秒表计时时间:
两端的延时为 47ms,可见即使设置了应当尽可能快的渲染播出,但也会有一定的延时。
之后,又经过多次采样,结果延时时间都落在 50ms 左右 或者小于 1ms,总的来说,整个延时将在 100ms 以内。
3.4 小结
抖动缓冲区以及 FrameBuffer 这些缓冲区的大小,都是由最大最小放出延时决定的。当我们设置了期望的播出延时时,webrtc 会自己分解延时目标,根据内部机制调整这些缓冲区的大小。
4. 结论
经过以上局域网内的延时测试,在局域网内使用 webrtc 能够得出以下结论:
(1) 延时主要是由接收端决定的;
(2) 音频对共享屏幕的延时影响甚微;
(3) 编解码耗时都在 10ms 以内;
(4) 根据最后的稳定下来的结果来看,局域网内传输延时非常小;
(5) webrtc 在局域网内的延时很大程度上受最大和最小播出延时决定。
参考:
WebRTC视频JitterBuff
webrtc 共享屏幕延时测试相关推荐
- 通过 WebRTC 共享屏幕很容易
简介 网络会议中常用的屏幕共享功能使用 WebRTC 提供的 getDisplayMedia API 就能轻松实现,接口如下 var promise = navigator.mediaDevices. ...
- 传统大华海康宇视安防摄像头RTSP流如何转webrtc直播低延时无插件浏览器视频播放
传统大华海康宇视安防摄像头RTSP流如何转webrtc直播低延时无插件浏览器视频播放 1.问题场景 2.WEBRTC延时对比 3.LiveNVR支持WEBRTC输出 4.RTSP/HLS/FLV/RT ...
- TSINGSEE青犀视频使用Vue.js搭建前端启动后共享屏幕无法获取音视频流问题解决
TSINGSEE青犀视频云边端架构产品的前端搭建大多是通过Vue来完成的,Vue的核心库只关注视图层,非常容易与其它库或已有项目整合,并且有能力驱动采用单文件组件和Vue生态系统支持的库开发的复杂单页 ...
- LiveQing视频点播RTMP推流直播服务支持H5无插件WebRTC超低延时视频直播
LiveQing视频点播RTMP推流直播服务支持H5无插件WebRTC超低延时视频直播 1.WebRTC超低延时视频直播 2.WebRTC延时对比 3.LiveQing播放WebRTC流 4.分屏页面 ...
- windows录屏html文件,录音、录屏、共享屏幕怎么玩?
为什么要录屏.录音? 有的时候产品.开发同学不在现场,用研需要一些证据给他们看,还有就是帮助我们自己回忆整理.但很多时候产品和设计都在现场,我们当时就会把问题记录下来,也可以不录屏,直接录音就好了. ...
- 量化延时法时间测量_「交易技术前沿」交易系统低延时测试与分析
本文选自 <交易技术前沿>总第三十三期文章(2018年12月) 证券期货行业测试中心(中金所) 魏畅 陈冬严 张鸿晔 摘要:订单延时(Latency)是衡量交易系统性能的重要指标.本文利用 ...
- 毕业生共享屏幕3小时 被骗70万
常州的小王刚研究生毕业,近日,他接到一个电话,让他更新支付软件信息,并指导他下载了一款APP. 通过这款APP的共享屏幕功能,对方实时掌握了小王的支付信息,并指导他从网贷平台贷款,3个小时共操作18次 ...
- 共享屏幕,录屏的方法
不管是录制屏幕还是共享屏幕首先要有chrome浏览器. 请自行在百度搜索下载. 录制屏幕的方法: 安装拓展工具: 安装地址:https://chrome.google.com/webstore/det ...
- android 图片 色温,屏幕色温测试及测试结果
屏幕色温测试及测试结果 在拍摄的照片中,经常能听到人们评价照片是偏冷或者是偏暖,这个"冷"和"暖"就是色温的表现.色温是屏幕的一个很重要的标准,如果红辐射相对说 ...
- mac设置共享屏幕 苹果mac屏幕共享设置详细教程
2019独角兽企业重金招聘Python工程师标准>>> 苹果mac怎么共享屏幕?OS X 自带屏幕共享功能,支持拖拽以及文本拷贝,操作还相当简单,仅需要点几个按钮就搞定:另外,还可以 ...
最新文章
- 零基础自学python的建议-零基础学python是学2还是3好
- nil 与 release
- leetcode55. 跳跃游戏
- 求解相机参数Camera Calibration
- jq之fadeIn()
- 【MySQL通过视图(或临时表)实现动态SQL(游标】
- 人工智能是否未来可期?3本人工智能书带你优雅升级
- CMake编译protobuf
- GitHub 使用教程图文详解(转)
- 暴风陨落,再无影音​
- 阿里如何实现海量数据实时分析?
- 用Python删除含有特定字符串的行
- 35岁,转行AI年薪100万,牛逼的人生无需解释!
- 22张图带你了解IP地址有什么作用
- ant 的详细的入门教程
- 《自己动手写嵌入式操作系统》阅读笔记之操作系统小知识
- Ubuntu1804安装及基本配置
- java 关键字 保留字_什么是Java关键字和保留字?
- C模板:十进制和十六进制数据批量转换
- WMS 原型详解 | 产品经理最讨厌的系统