Qt结合ffmpeg代码实现udp推流/组播推流/rtp推流/监控GB28181推流/onvif推流

一、前言说明

之前已经用ffmpeg代码实现了rtsp和rtmp推流,在没有搞过推流的时候,以为很难,其实推流就是保存文件到一个rtsp/rtmp地址,完全复用保存到MP4文件的代码,唯一不同的时候就是在avformat_alloc_output_context2函数第三个参数,保存到MP4文件填的是mp4,而rtsp推流填的是rtsp,rtmp推流填的是flv,同理后面增加的udp推流填的mpegts,28181推流填的rtp_mpegts,其实rtp默认推流用的是mpegts,而28181的标准要求是在mpegts还要加上rtp头,所以需要填rtp_mpegts,否则推流的数据在gb28181平台软件上无法正常解析。

udp推流默认是mpegts格式,有些用户希望是h264/h265的裸流数据,那只需要这里改成h264即可,这样就是直接裸流数据,而不是ts格式的封装,怎么样是不是很方便,底层全部ffmpeg给自动处理,自己如果按照规范文档写对应组包数据,会非常繁琐,而且兼容性也未必有ffmpeg好,自此这个封装的ffmpeg的视频组件越来越强大了。

二、效果图

Snipaste_2025-09-28_08-58-46

Snipaste_2025-09-28_09-00-24

三、功能特点

  1. 支持各种本地音视频文件和网络音视频文件,格式包括mp3、aac、wav、wma、mp4、mkv、rmvb、wmv、mpg、flv、asf等。
  2. 支持各种网络音视频流,网络摄像头,协议包括rtsp、rtmp、http等。
  3. 支持本地摄像头设备推流,可指定分辨率、帧率、格式等。
  4. 支持本地桌面采集推流,可指定屏幕索引、采集区域、起始坐标、帧率等,也支持指定窗口标题进行采集。
  5. 可实时切换预览视频文件,可切换音视频文件播放进度,切换到哪里就推流到哪里。预览过程中可以切换静音状态和暂停推流。
  6. 可指定重新编码推流,任意源头格式可选强转264或265格式。
  7. 可转换分辨率推流,设置等比例缩放或者指定分辨率进行转换。
  8. 推流的清晰度、质量、码率都可调,可以节约网络带宽和拉流端的压力。
  9. 音视频文件自动循环不间断推流。
  10. 音视频流有自动掉线重连机制,重连成功自动继续推流。
  11. 支持各种流媒体服务程序,包括但不限于mediamtx、ZLMediaKit、srs、LiveQing、nginx-rtmp、EasyDarwin、ABLMediaServer。
  12. 通过配置文件自动加载对应流媒体程序的协议和端口,自动生成推流地址和各种协议的拉流地址。可以通过配置文件自己增加流媒体程序。
  13. 可选rtmp、rtmp格式推流,推流成功后,支持多种格式拉流,包括但不限于rtsp、rtmp、hls、flv、ws-flv、webrtc等。
  14. 在软件上推流成功后,可以直接单击网页预览,实时预览推流后拉流的画面,多画面网页展示。
  15. 软件界面上可单击对应按钮,动态添加文件和目录,可手动输入地址。
  16. 推拉流实时性极高,延迟极低,延迟时间大概在100ms左右。
  17. 极低CPU资源占用,4路主码流推流只需要占用0.2%CPU。理论上常规普通PC机器推100路毫无压力,主要性能瓶颈在网络。
  18. 可以推流到外网服务器,然后通过手机、电脑、平板等设备播放对应的视频流。
  19. 每路推流都可以手动指定唯一标识符(方便拉流/用户无需记忆复杂的地址),没有指定则按照策略随机生成hash值。也支持自动按照指定标识后面加数字的方式递增命名。比如设置标识为字母v,策略为标识递增,则每添加一个对应的推流码命名依次是v1、v2、v3等。
  20. 根据推流协议自动转码格式,默认策略按照选择的推流协议,比如rtsp支持265而rtmp不支持,如果是265的文件而选择rtmp推流,则自动转码成264格式再推流。
  21. 音视频同步推流,在拉流和采集的时候就会自动处理好同步,同步后的数据再推流。
  22. 表格中实时显示每一路推流的分辨率和音视频数据状态,灰色表示没有输入流,黑色表示没有输出流,绿色表示原数据推流,红色表示转码后的数据推流。
  23. 自动重连视频源,自动重连流媒体服务器,保证启动后,推流地址和打开地址都实时重连,只要恢复后立即连上继续采集和推流。
  24. 根据不同的流媒体服务器类型,自动生成对应的rtsp、rtmp、hls、flv、ws-flv、webrtc拉流地址,用户可以直接复制该地址到播放器或者网页中预览查看。
  25. 添加的推流地址等信息自动存储到文件,可以手动打开进行修改,默认启动后自动加载历史记录。
  26. 可以指定生成的网页文件保存位置,方便作为网站网页发布,可以直接在浏览器中输入网址进行访问,发布后可以直接在局域网其他设备比如手机或者电脑打开对应网址访问。
  27. 可选是否开机启动、后台运行等。网络推流添加的rtsp地址可勾选是否隐藏地址中的用户信息。
  28. 自带设备推流模块,自动识别本地设备,包括本地的摄像头和桌面,可以手动选择不同的是视频和音频采集设备进行推流。
  29. 自带文件点播模块,添加文件后用户可以拉取地址点播,用户端可以任意切换播放进度。支持各种浏览器(谷歌chromium、微软edge、火狐firefox等)、各种播放器(vlc、mpv、ffplay、potplayer、mpchc等)打开请求。
  30. 文件点播模块实时统计显示每个文件对应的访问数量、总访问数量、不同IP地址访问数量。
  31. 文件点播模块采用纯QTcpSocket通信,不依赖流媒体服务程序,核心源码不到500行,注释详细,功能完整。
  32. 支持任意Qt版本(Qt4、Qt5、Qt6),支持任意系统(windows、linux、macos、android、嵌入式linux等)。

四、相关地址

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 文件地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_push.zip。

五、相关代码

#include "netpushclient.h"
#include "ffmpegthread.h"
#include "ffmpegsave.h"
#include "videohelper.h"
#include "osdgraph.h"bool NetPushClient::checkB = false;
bool NetPushClient::recordInteger = false;
int NetPushClient::recordDuration = 0;
int NetPushClient::encodeVideo = 0;
float NetPushClient::encodeVideoRatio = 1;
QString NetPushClient::encodeVideoScale = "1";NetPushClient::NetPushClient(QObject *parent) : QObject(parent)
{ffmpegThread = NULL;ffmpegSave = NULL;//定时器控制多久录制一个文件timerRecord = new QTimer(this);timerRecord->setInterval(1000);connect(timerRecord, SIGNAL(timeout()), this, SLOT(checkRecord()));
}NetPushClient::~NetPushClient()
{this->stop();
}QString NetPushClient::getMediaUrl()
{return this->mediaUrl;
}QString NetPushClient::getPushUrl()
{return this->pushUrl;
}FFmpegThread *NetPushClient::getVideoThread()
{return this->ffmpegThread;
}void NetPushClient::checkRecord()
{if (!ffmpegSave) {return;}//0. 时长单位分钟/触发条件自动重新录像/recordDuration=0/表示禁用录像//1. recordInteger参数控制是否整数倍数录像/recordDuration参数控制录制文件时长/整数倍录像下时长为对应的模数//2. 一般监控行业会按照整点录像/比如30分钟60分钟一个视频文件/这样录制的文件起始时间和结束时间整整齐齐//3. 整点录像情况下除了第一个和最后一个录像文件可能时长不一样/中间的文件肯定时长都一样//4. 非整点录像就按照录像总时长计时/所有保存的文件都是按照时长保存的//5. recordInteger=true/recordDuration=5/表示每到5分钟的时候录制一个文件//6. 上面录制结果: 11:01开始录制/11:05结束上一个录制并重新录制/第一个文件时长4分钟//7. recordInteger=false/recordDuration=5/表示每过5分钟的时候录制一个文件//8. 上面录制结果: 11:01开始录制/11:06结束上一个录制并重新录制/第一个文件时长5分钟bool ok = false;QDateTime now = QDateTime::currentDateTime();qint64 offset = recordTime.msecsTo(now);if (recordInteger && recordDuration > 1) {QTime time = now.time();int min = time.minute();int sec = time.second();min = (min == 0 ? 60 : min);ok = ((min % recordDuration == 0) && sec >= 0 && sec <= 2);//qDebug() << TIMEMS << min << sec << (min % recordDuration == 0) << offset << ok;} else {ok = (offset >= (recordDuration * 60 * 1000));//qDebug() << TIMEMS << recordDuration << offset << ok;}if (ok && offset >= 5000) {this->record();}
}void NetPushClient::record()
{//取出推流码QString flag = pushUrl.split("/").last();//文件名不能包含特殊字符/需要替换成固定字母QString pattern("[\\\\/:|*?\"<>]|[cC][oO][mM][1-9]|[lL][pP][tT][1-9]|[cC][oO][nM]|[pP][rR][nN]|[aA][uU][xX]|[nN][uU][lL]");
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))QRegularExpression rx(pattern);
#elseQRegExp rx(pattern);
#endifflag.replace(rx, "X");//文件名加上时间结尾QString path = QString("%1/video/%2").arg(qApp->applicationDirPath()).arg(QDATE);QString name = QString("%1/%2_%3.mp4").arg(path).arg(flag).arg(STRDATETIME);//目录不存在则新建QDir dir(path);if (!dir.exists()) {dir.mkpath(path);}//先停止再打开重新录制QMutexLocker locker(&mutex);if (ffmpegSave) {ffmpegSave->stop();ffmpegSave->open(name);recordTime = QDateTime::currentDateTime();}
}void NetPushClient::receivePlayStart(int time)
{//演示添加OSD后推流
#ifdef betaversionint height = ffmpegThread->getVideoHeight();QList<OsdInfo> osds = OsdGraph::getTestOsd(height);ffmpegThread->setOsdInfo(osds);
#endif//打开后才能启动录像ffmpegThread->recordStart(pushUrl);//推流以外还单独存储if (!ffmpegSave && recordDuration > 0) {//源头保存没成功就不用继续FFmpegSave *saveFile = ffmpegThread->getSaveFile();if (!saveFile->getIsOk()) {return;}ffmpegSave = new FFmpegSave(this);//重新编码过的则取视频保存类的对象AVStream *videoStreamIn = saveFile->getVideoEncode() ? saveFile->getVideoStream() : ffmpegThread->getVideoStream();AVStream *audioStreamIn = saveFile->getAudioEncode() ? saveFile->getAudioStream() : ffmpegThread->getAudioStream();ffmpegSave->setSavePara(ffmpegThread->getMediaType(), SaveVideoType_Mp4, videoStreamIn, audioStreamIn);this->record();timerRecord->start();}
}void NetPushClient::receivePlayFinsh()
{QMutexLocker locker(&mutex);if (ffmpegSave) {ffmpegSave->stop();ffmpegSave->deleteLater();ffmpegSave = NULL;}
}void NetPushClient::receivePacket(AVPacket *packet)
{if (ffmpegSave && ffmpegSave->getIsOk()) {ffmpegSave->writePacket2(packet);}FFmpegHelper::freePacket(packet);
}void NetPushClient::recorderStateChanged(const RecorderState &state, const QString &file)
{int width = 0;int height = 0;int videoStatus = 0;int audioStatus = 0;if (ffmpegThread) {width = ffmpegThread->getVideoWidth();height = ffmpegThread->getVideoHeight();FFmpegSave *saveFile = ffmpegThread->getSaveFile();if (saveFile->getIsOk()) {if (saveFile->getVideoIndexIn() >= 0) {if (saveFile->getVideoIndexOut() >= 0) {videoStatus = (saveFile->getVideoEncode() ? 3 : 2);} else {videoStatus = 1;}}if (saveFile->getAudioIndexIn() >= 0) {if (saveFile->getAudioIndexOut() >= 0) {audioStatus = (saveFile->getAudioEncode() ? 3 : 2);} else {audioStatus = 1;}}}}//只有处于录制中才表示正常推流开始bool start = (state == RecorderState_Recording);emit pushStart(mediaUrl, width, height, videoStatus, audioStatus, start);
}void NetPushClient::receiveSaveStart()
{emit pushChanged(mediaUrl, 0);
}void NetPushClient::receiveSaveFinsh()
{emit pushChanged(mediaUrl, 1);
}void NetPushClient::receiveSaveError(int error)
{emit pushChanged(mediaUrl, 2);
}void NetPushClient::setMediaUrl(const QString &mediaUrl)
{this->mediaUrl = mediaUrl;
}void NetPushClient::setPushUrl(const QString &pushUrl)
{this->pushUrl = pushUrl;
}void NetPushClient::start()
{if (ffmpegThread || mediaUrl.isEmpty() || pushUrl.isEmpty()) {return;}//实例化视频采集线程ffmpegThread = new FFmpegThread;//关联播放开始信号/用来启动推流connect(ffmpegThread, SIGNAL(receivePlayStart(int)), this, SLOT(receivePlayStart(int)));connect(ffmpegThread, SIGNAL(receivePlayFinsh()), this, SLOT(receivePlayFinsh()));//关联录制信号变化/用来判断是否推流成功connect(ffmpegThread, SIGNAL(recorderStateChanged(RecorderState, QString)), this, SLOT(recorderStateChanged(RecorderState, QString)));//设置保存视频类将数据包信号发出来用于保存文件FFmpegSave *saveFile = ffmpegThread->getSaveFile();saveFile->setProperty("checkB", checkB);saveFile->setSendPacket(recordDuration > 0, false);connect(saveFile, SIGNAL(receivePacket(AVPacket *)), this, SLOT(receivePacket(AVPacket *)));connect(saveFile, SIGNAL(receiveSaveStart()), this, SLOT(receiveSaveStart()));connect(saveFile, SIGNAL(receiveSaveFinsh()), this, SLOT(receiveSaveFinsh()));connect(saveFile, SIGNAL(receiveSaveError(int)), this, SLOT(receiveSaveError(int)));//设置视频模式
#ifdef openglxffmpegThread->setVideoMode(VideoMode_Opengl);
#elseffmpegThread->setVideoMode(VideoMode_Painter);
#endif//设置通信协议/果是rtsp视频流建议设置tcp//ffmpegThread->setTransport("tcp");//设置硬解码/和推流无关/只是为了加速显示/推流只和硬编码有关//ffmpegThread->setHardware("dxva2");//设置缓存大小/如果分辨率帧率码流很大需要自行加大缓存ffmpegThread->setCaching(8192000);//设置解码策略/推流的地址再拉流建议开启最快速度//ffmpegThread->setDecodeType(DecodeType_Fastest);//设置播放地址ffmpegThread->setMediaUrl(mediaUrl);//设置读取超时时间超时后会自动重连ffmpegThread->setReadTimeout(10 * 1000);//设置连接超时时间ffmpegThread->setConnectTimeout(0);//设置重复播放相当于循环推流ffmpegThread->setPlayRepeat(true);//设置默认不播放音频/界面上切换到哪一路就开启ffmpegThread->setPlayAudio(false);//设置默认不解码数据/界面上切换到哪一路就关闭ffmpegThread->setDisableDecode(true);    //如果是本地设备或者桌面录屏要取出其他参数VideoHelper::initVideoPara(ffmpegThread, mediaUrl, encodeVideoRatio, encodeVideoScale);//设置视频编码格式/视频压缩比率/视频缩放比例ffmpegThread->setEncodeVideo((EncodeVideo)encodeVideo);ffmpegThread->setEncodeVideoRatio(encodeVideoRatio);ffmpegThread->setEncodeVideoScale(encodeVideoScale);//启动播放ffmpegThread->play();
}void NetPushClient::stop()
{//停止推流和采集并彻底释放对象if (ffmpegThread) {ffmpegThread->recordStop();ffmpegThread->stop();ffmpegThread->deleteLater();ffmpegThread = NULL;}//停止录制if (ffmpegSave) {timerRecord->stop();ffmpegSave->stop();ffmpegSave->deleteLater();ffmpegSave = NULL;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/920423.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

个人网站建设多少钱网站建设翻译插件

矩阵中的路径 题目 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是…

新乡住房与城乡建设厅网站xsxz wordpress

近日&#xff0c;腾讯的大动作一个接一个&#xff0c;前脚刚公布2023上半年财报&#xff0c;后脚就开启了2024校招&#xff0c;不得不让人感叹腾讯真速度&#xff01; 此次招聘对象为毕业时间在2023年9月至2024年8月期间的2024届应届毕业生&#xff0c;覆盖北上广深等多个城市…

在线做漫画的网站营销网络的建设是什么意思

欢迎关注 『youcans 的 OpenCV 例程 200 篇』 系列&#xff0c;持续更新中 欢迎关注 『youcans 的 OpenCV学习课』 系列&#xff0c;持续更新中 【youcans 的 OpenCV 例程 200 篇】124. 孔洞填充的泛洪算法 3. 形态学算法 形态学处理的主要应用是提取图像中用来表示和描述形状…

linux防火墙firewalld

1.systemctl start firewalld2.systemctl enable firewalld (开启开机自启防火墙) / systemctl disable firewalld3.systemctl list-unit-files | grep firewalld解读:systemctl list-unit-files 列出所有已安装单元…

AI提示词应用 - 详解

AI提示词应用 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "…

【环境武装】认识你的战友 —— 经典而稳定的 PowerShell ISE

【环境武装】认识你的战友 —— 经典而稳定的 PowerShell ISE在激动人心的前言和历史故事之后,我们终于要打开工具箱,准备开始真正的实践了。对于初学者而言,最怕的就是在“环境配置”这个第一步就卡住,信心受挫。…

做商城网站的公司推荐外贸网站收到询盘

JAVA几个常见错误简析Java看起来设计得很像C&#xff0c;但是为了使语言小和容易熟悉&#xff0c;设计者们把C语言中许多可用的特征去掉了&#xff0c;这些特征是一般程序员很少使用的。下面就来和小编一起看看JAVA几个常见错误简析吧。1、空指针错误 java.lang.NullPointerExc…

少儿编程免费网站wordpress 密码注册

查询数据库名&#xff1a; select database()查询表结构&#xff1a; select * from INFORMATION_SCHEMA.TABLES where TABLE_NAME表名 and table_schema (select database())

学校网站源码html租房注册公司需要什么资料

文章目录 题目描述代码 题目描述 如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称为 摆动序列 。第一个差&#xff08;如果存在的话&#xff09;可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。 例如&#xff0c; [1, 7, …

详细介绍:基于FPGA的HDB3编解码(verilog语言)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

很多大公司为什么禁止在SpringBoot项目中使用Tomcat?

前言 今天我们来聊聊一个很有意思的现象:为什么越来越多的大公司禁止SpringBoot项目使用默认的Tomcat,而强制要求使用Undertow? 有些小伙伴在工作中可能已经发现了这个趋势,但背后的原因你真的清楚吗? 一、Spring…

Java作业动手又动脑

一. 枚举定义 enum Size{SMALL,MEDIUM,LARGE}; 定义了包含三个枚举常量的Size枚举类型。 主要测试逻辑 引用比较测试 java Size s=Size.SMALL; Size t=Size.LARGE; System.out.println(s==t); // false 输出:false 原…

学做文案的网站网站内部代码优化

为什么IDEA要对Git进行更新操作的呢&#xff0c;因为当我们提交项目的时候我们必须要更新到最新的项目然后才能够使我们在提交项目的过程中不出现错误。 IDEA Git更新详细步骤 1 . 打开我们的IDEA &#xff0c;首先要确保我们已经从git里面clone项目到我们的IDEA里面。 2 . 当…

PHP 开发者必须掌握的基本 Linux 命令

PHP 开发者必须掌握的基本 Linux 命令 刚开始 PHP 开发时,我并没有特别的理由选择 Linux。既不是出于对开源软件的热情,也不是认为 Linux 比 macOS 或 Windows 更优秀。当时只是希望找到一个更轻量、更可定制的开发环…

维度网络网站建设建筑英才招聘网首页

在线题目链接&#xff1a;斐波那契数列 文章目录1、题目描述2、题目分析3、代码3.1 递归方法3.11 Java代码3.12 C代码3.2 动态规划3.21 Java代码3.22 C代码3.3 循环方法3.31 Java代码3.32 C代码4、总结1、题目描述 大家都知道斐波那契数列&#xff0c;现在要求输入一个整数n&a…

使用python写一个应用程序要求实现微软常用vc++功能排查与安装功能

import os import sys import subprocess import re import requests import tempfile import platform from bs4 import BeautifulSoup import winregclass VCRedistManager:def __init__(self):self.supported_versi…

网站建设尺寸规范网络营销策划案的形式

给AWS新账户做完了对等连接&#xff0c;因为默认VPC网段都冲突 就换了VPC&#xff0c;然后发现新VPC内创建的实例都没有分配公网IP地址&#xff0c;自动分配公网IP地址变成了禁用。后续建机子需要手动修改成启用太麻烦了。 在VPC里面找到编辑子网设置&#xff0c;勾上启用自动…

网站建设全网营销客户资源心理咨询网站后台

传统蜂窝网络一般基于特定接入技术并针对大规模公共网络设计&#xff0c;无法灵活适配小规模网络以及异构无线技术。本文介绍了Magma在构建低成本异构无线接入方面的探索。原文: Building Flexible, Low-Cost Wireless Access Networks With Magma 摘要 当今仍然有数十亿人受限…

网站内容更新教程泰安网站开发公司

1.三次握手的概述 我们在学网络的概念时&#xff0c;每当讲到TCP都会听到三次握手和四次挥手&#xff0c;一直以来可能都对这个概念模糊不清&#xff0c;那么什么是三次握手和四次挥手呢&#xff1f;简单的举一个例子&#xff0c;如果我们和朋友打游戏&#xff0c;我们要和朋友…

广西壮族自治区住房和城乡建设厅网站手机网站开发源码

目录 一、什么是sql注入 二、sql语句的执行流程 三、内连接和外连接的区别 四、Union和Union All 有什么区别 五、MySql如何取差集 六、DELETE和TRUNCATE有什么区别 七、count&#xff08;*&#xff09;和count&#xff08;1&#xff09;的区别 八、MyISAM和InnoDB的区…