Qt/C++开发经验小技巧311-315
- 关于流媒体推拉流延时的几点说明。
- 经常看到一些流媒体相关的程序,号称零延迟,不用怀疑,这肯定吹牛逼的。
- 搞音视频开发,有个核心的指标就是实时性,也就是延迟多少毫秒,这个问题问的也是最多的。
- 音视频文件几乎不存在实时性问题,只有音视频流才有实时性的指标。
- 延迟多久这个涉及到很多方面,也要看你如何计算,从推流开始计算还是从拉流开始计算。
- 很多小伙伴们并不能明白什么叫延时,认为随便一个播放器播放出来的画面跟原始流画面时间差就是延时,其实这是对延时最大的误解。延时不是表象,很多人在测试延时时很不专业,对延时测试的专业性认识不足。
- 下面整理的是zlm作者写的关于延时的文章,非常完整而且有代表性。
- 采集延时:在采集摄像头或显卡画面时,由于fps的限制和cpu性能、内存拷贝速度等客观限制,采集画面成YUV/RGB等数据时会有一定的延时,一般延时为毫秒级别。由于一般编码器对输入数据格式存在限制,譬如要求统一输入YUV420P,这样在做RGB->YUV420P转换时,也会有转换计算延时(这个可以通过libyuv库来降低)。总而延时,采集延时大概为毫秒级别,如果fps为25,那么一般采集延时会有40毫秒(1000毫秒/25fps)以上的延时,在内存拷贝和颜色转换时,又可能增加若干毫秒的延时。
- 编码延时:在把原始画面输入到编码器时,并不会立即输出编码后的数据,特别是在开启B帧时,由于需要参考后面的P帧,那么延时会更大,所以延时敏感的情况下一般不开启B帧,这种情况下编码延时应该是毫秒级别,不是很大。
- 上行延时:编码后的数据,要经过一定的协议打包才能写入socket,然后传输给推流服务器或拉流代理服务器,协议打包会有一定的内存拷贝和计算量,那么会增加延时,不过这个延时很小,基本忽略不计。数据在上传到服务器时,这个延时可大可小,取决于网络质量。
- 转换延时:服务器在收到数据后,要读socket缓存、协议解析、解复用、重新打包等操作,不过总体而言,这个延时比较小,基本没什么影响。有时,服务器为了提高性能,会采取合并写的机制,也就是收到一定量的数据后才会一并转发,这个延时一般为几百毫秒,ZLMediaKit默认300毫秒左右,不过ZLMediaKit默认关闭合并写,也就是这个延时也很小。
- 下行延时:流媒体在把视频数据转发给播放器时,会存在网络发送,这个延时大小取决于网络质量,ZLMediaKit在关闭低延时模式时,还会增加MSG_MORE和关闭TCP_NODELAY导致的延时,不过ZLMediaKit默认开启低延时模式。
- 播放延时:播放器延时主要有网路接收延时、协议解析解复用延时、解码延时、缓存延时、渲染延时组成,这些延时中缓存延时最大,因为一般的播放器为了保证在网络抖动情况下视频播放的流畅性,会以增加延时为代价,增加播放缓存,这样在网络变差时,不至于播放缓冲卡顿。而且为了音视频同步,也必须确保一定的缓存量。这种延时一般都是秒级别,一般5秒左右。有部分播放策略是接收到数据后立即解码显示比如rtsp视频流,这样可以做到延迟最小。
- 缓存延时:流媒体服务器为了能让播放器立即出画面,往往会缓存最近的一个I帧,这个I帧往后的所有音视频数据被称作为GOP缓存。如果不缓存GOP,那么播放器要等下一个I帧才能解码成功或不花屏,显然为了提高播放体验,这个GOP缓存是不能去掉的。而一般GOP短则1~3秒,长则10几秒,这个跟采集端编码器设置有关,服务器改变不了。但是由于一般的播放器收到缓存后,并不会丢弃过多的画面来确保低延时。况且播放器还希望有一定的缓存来确保播放的流畅性,所以这个GOP缓存将会增大播放器的延时。
- 综合延时:最快可以做到200-300ms的延迟,比如rtsp视频流,对实时性要求高,可以不做缓存和音视频同步,收到就立即解码播放。hls一般最快可以做到5s延迟,flv一般可以做到3s延迟。
- 最终总结:综合考虑实时性以及支持的音视频格式,个人建议,推流用rtsp推流(支持的音视频格式最友好,比如支持265),拉流在web上个人推荐用ws-flv格式拉流(支持的格式多,没有6个同源的限制),拉流在可执行文件上用rtsp(格式多而且实时性最好,可以最快速度解码播放),在网页上虽然webrtc实时性最好,但是不支持265,这个就难搞。
- Qt自带的对话框的按钮设置中文。
//信息框设置中文
QMessageBox dialog(QMessageBox::Question, "询问", text);
dialog.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
dialog.button(QMessageBox::Yes)->setText("确 定");
dialog.button(QMessageBox::No)->setText("取 消");
return dialog.exec();
//输入框设置中文
QInputDialog dialog;
dialog.setOkButtonText("确定");
dialog.setCancelButtonText("取消");
return dialog.exec();
//对话框设置中文
QFileDialog dialog;
dialog.setOption(QFileDialog::DontUseNativeDialog, true);
QLabel *lookinLabel = dialog.findChild<QLabel*>("lookInLabel");
lookinLabel->setText("文件目录:");
- 有时候需要对树状节点进行搜索过滤显示,匹配到的节点显示,没有匹配的隐藏。这个功能很多地方用,封装了一个静态函数直接调用。
//调用方法 QtHelper::search(ui->treeWidget, "测试", 5);
void QtHelper::search(QTreeWidget *treeWidget, const QString &key, int level)
{
//找到所有匹配的节点
QList<QTreeWidgetItem *> items = treeWidget->findItems(key, Qt::MatchContains | Qt::MatchRecursive);
//将匹配到的节点加入队列/该节点的父节点也相当于匹配/不然父节点隐藏子节点也会跟着隐藏
QList<QTreeWidgetItem *> itemAll;
foreach (QTreeWidgetItem *item, items) {
//当前节点的所有父节点也添加进去/几次循环相当于几个层级
for (int i = 0; i < level; ++i) {
//去重添加
if (!itemAll.contains(item)) {
itemAll << item;
}
//为空表示没有父节点则跳出循环
item = item->parent();
if (!item) {
break;
}
}
}
//遍历所有节点/匹配的节点显示否则隐藏
QTreeWidgetItemIterator it(treeWidget);
while (*it) {
(*it)->setHidden(!itemAll.contains(*it));
++it;
}
}
- 在Qt中实现组播是非常容易的事情,从4.8开始支持组播,为啥要用组播而不是广播?因为广播会产生广播数据风暴,每个设备都会收到,而且针对某个网段的广播比如192.168.0.255,不能跨网段。而组播不仅可以跨网段,还不会出现数据风暴,只对加入了组播的目标进行数据发送,非常适合用来做局域网设备搜索和配置。在测试过程中,如果是两台真机之间测试组播,没有问题,但是很多时候开发机只有一台,最多就是在开发机上安装了虚拟机,可以有多个系统可以测试,那么问题来了,虚拟机之间的组播需要经过设置才能正常通信。第一点就是虚拟机的网络必须是桥接模式,也就是网络地址和宿主主机同一网段。第二点最关键,需要在两个虚拟机产生的网卡设备(VMware Network Adapter VMnet1/VMware Network Adapter VMnet8)右键属性进去设置,在此连接使用下列项目中勾选一个VMware Bridge Protocol确定,重启网卡即可。
//绑定端口
udpSocket->bind(QHostAddress("0.0.0.0"), 6789);
//设置组播数据不给自己发送/一般都会有这个设置/防止数据又发给自己造成死循环
udpSocket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, 0);
//加入组播地址
udpSocket->joinMulticastGroup(QHostAddress("224.0.0.10"));
//往组播发送数据
udpSocket->writeDatagram("hello", QHostAddress("224.0.0.10"), 6789);
//接收数据和UDP接收数据处理完全一致
- 在Qt中结构体数据也是可以保存到ini配置文件,只不过保存后的数据是一堆qbytearray之类的字符,所以如果可读性优先,建议不要存储结构体数据,最起码也要是格式化后的结构体数据存储进去。要想用QSettings保存结构体数据,必须在结构体中重载实现输入输出数据流。
struct ClientConfig {
int tabIndex;
QString serverInfo;
//重载数据流输出
friend QDataStream &operator << (QDataStream &out, const ClientConfig &clientConfig) {
out << clientConfig.tabIndex;
out << clientConfig.serverInfo;
return out;
}
//重载数据流输入
friend QDataStream &operator >> (QDataStream &in, ClientConfig &clientConfig)
{
in >> clientConfig.tabIndex;
in >> clientConfig.serverInfo;
return in;
}
};
//必须加上下面这句用来注册元数据类型,不然报错
//error: static assertion failed: Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system
Q_DECLARE_METATYPE(ClientConfig)
//定义
ClientConfig clientConfig;
//读取
set.beginGroup("ClientConfig");
clientConfig = set.value("clientConfig").value<ClientConfig>();
set.endGroup();
//写入
set.beginGroup("ClientConfig");
set.setValue("clientConfig", QVariant::fromValue(clientConfig));
set.endGroup();

Qt/C++开发经验小技巧311-315的更多相关文章
- Qt开发经验小技巧81-90
Qt中的QColor对颜色封装的很完美,支持各种转换,比如rgb.hsb.cmy.hsl,对应的是toRgb.toHsv.toCmyk.toHsl,还支持透明度设置,颜色值还能转成16进制格式显示. ...
- Qt开发经验小技巧61-70
很多人问Qt嵌入式平台用哪个好,这里统一回答(当前时间节点2018年):imx6+335x比较稳定,性能高就用RK3288 RK3399,便宜的话就用全志H3,玩一玩可以用树莓派香橙派. 对于大段的注 ...
- Qt开发经验小技巧41-50
如果使用sqlite数据库不想产生数据库文件,可以创建内存数据库. QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); ...
- Qt开发经验小技巧71-80
在我们使用QList.QStringList.QByteArray等链表或者数组的过程中,如果只需要取值,而不是赋值,强烈建议使用 at() 取值而不是 [] 操作符,在官方书籍<C++ GUI ...
- Qt开发经验小技巧51-60
在某些http post数据的时候,如果采用的是&字符串连接的数据发送,中文解析乱码的话,需要将中文进行URL转码. QString content = "测试中文"; Q ...
- Qt开发经验小技巧31-40
代码判断MSVC编译器版本. if (_MSC_VER == 1800) MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) MSVC++ 12.0 _ ...
- Qt开发经验小技巧21-30
如果出现Z-order assignment: is not a valid widget.错误提示,用记事本打开对应的ui文件,找到为空的地方,删除即可. 善于利用QComboBox的addItem ...
- Qt开发经验小技巧11-20
获取类的属性 const QMetaObject *metaobject = object->metaObject(); int count = metaobject->propertyC ...
- Qt开发经验小技巧1-10
当编译发现大量错误的时候,从第一个看起,一个一个的解决,不要急着去看下一个错误,往往后面的错误都是由于前面的错误引起的,第一个解决后很可能都解决了. 定时器是个好东西,学会好使用它,有时候用QTime ...
- Qt开发之Hello Qt及学习小技巧
创建第一个Qt程序的简单流程 如果安装了andriod或ios的版本也会显示出来,这里只显示了桌面端的 一个项目文件代码结构如下: 如下图可直接编辑label里的文字内容: 运行结果:(快捷键ctrl ...
随机推荐
- PostGIS数据库操作简介
PostGIS数据库操作简介 PostGIS Docker安装 docker pull postgis/postgis docker run --name postgis -e POSTGRES_PA ...
- 夜莺监控支持 ES 日志告警了
夜莺项目( https://github.com/ccfos/nightingale )发布了 v8.0.0-beta.3 版本,这个版本主要是支持了 ES 日志告警,下面给大家介绍一下. 新版本下载 ...
- CDS标准视图:催款代码 I_DunningKey
视图名称:催款代码 I_DunningKey 视图类型:基础 视图代码: 点击查看代码 @AccessControl.authorizationCheck: #NOT_REQUIRED @EndUse ...
- 前端实现 HTML 网页转 PDF 并导出🤓
有个新需求,当点击[下载]按钮时,直接将当前 html页面下载为 PDF.通过 html2canvas + jsPDF 可实现PDF单页下载,甚至是多页下载,记录分享一下~ 最后有源码,可自取 htm ...
- 利用JavaScript自定义事件完成组件间的数据通信
我们知道,在JavaScript中,原生DOM事件在开发中是很有用的(与用户交互的重要方式),但是操作原生DOM事件其实有两大缺点:性能低.依赖于浏览器(NodeJs.小程序等不可用).那么这个时候, ...
- C :文件
一直没有系统学习过该章节,现参考<C语言程序设计 (第四版)谭浩强> C文件基本知识 什么是文件 文件名 文件的分类 文件缓冲区 文件类型指针 typedef struct { short ...
- Slort pg walkthrough Intermediate window
nmap ┌──(root㉿kali)-[~] └─# nmap -p- -A -sS 192.168.226.53 Starting Nmap 7.94SVN ( https://nmap.org ...
- ASP.NET Core 快速轻量级的浏览器检测和设备检测库
在 .NET Framework 4.7 中那样,通过 HttpContext.Request 的 Browser 属性轻松获取发起 HTTP 请求的浏览器信息,ASP.NET Core 并未直接提供 ...
- 0511-FileWrite字符输出流和JDK7中try..finally新的特性
package A10_IOStream; import java.io.*; /* java.io.Writer:字符输出流,是所有字符数出流的最顶层抽象父类 共性方法 void write(int ...
- TCP协议的三次握手-4次挥手
TCP的连接建立是一个三次握手过程,目的是为了通信双方确认开始序号,以便后续通信的有序进行.主要步骤如下: 连接开始时,连接建立方(Client)发送SYN包,并包含了自己的初始序号a: 连接接受方( ...