Qt Demo:基于TCP协议的视频传输Demo
目录
1.设计思路
2.Pro文件配置
3.头文件引入
4.界面设计
5.初始化设备函数
6.发起视频链接函数
7.初始化定时器模块函数
8.TCP链接模块函数
9.处理接收的数据线程函数
10.实现功能展示
设计思路
基于TCP协议的视频传输Demo,设计要实现的功能主要是TCP传输还有视频,可参考以下开发思路:
1.视频本质都是通过照片组成,由于连续的照片数量多了,画面就由单纯的静态图片变成动态的视频,所以要实现视频传输,实现得把摄像头录制到的画面分成帧使用TCP协议发送,具体开发细节如下:
1.设置QTimer类对象,实现定时器功能,定期捕获摄像头录制的画面
2.为了实现捕获画面功能,需要定义QImageCapture对象,并使用QMediaCaptureSession类对象统一管理摄像头和QImageCapture对象
2.TCP协议的链接,主要通过三次握手形成可靠的链接。所以我们需要一个QTcpSocket对象来进行链接,并对该对象发出的readyRead信号(读取到数据)进行相对应的槽函数绑定
3.由于要同时录制视频,还有捕获视频画面发送至其他用户,所以要使用线程,单独使用线程往往管理不是很方便,所以Demo中使用了QThradPool类(线程池)来管理线程,并使用QThreadPool::globalInstance()->start()函数将线程放置全局线程池统一管理启动
4.有时候不仅仅满足于视频传输的需求(现实生活中或许只有倒车视频,监控等功能不需要实现音频录制),所以该Demo使用了QAudioDevice访问音频设备,并录制
Pro文件配置
为了实现基于TCP协议的视频传输Demo,我们需要Qt中自带的两个模块,分别为multimedia 模块和multimediawidgets模块:
1.multimedia模块:该模块提供了音频和视频播放、录制以及处理的基本功能。支持多种音频和视频格式,能实现流媒体播放等功能
2.multimediawidgets模块:该模块提供了一组预构建的多媒体小部件,使得开发者可以快速地在应用程序中添加多媒体播放和控制功能
所以开发前Pro文件中应该添加以下字段
QT += multimedia multimediawidgets
头文件引入
以下为项目main文件中需要引入的头文件,我会把为什么要引入该头文件列在备注中,以供参考:
头文件 | 备注 |
QDir | 用于获取操作文件系统目录 |
QFile | 用于文件操作的类 |
QTimer | 用于实现定时器功能 |
QCamera | 用于访问和控制摄像头设备 |
QHostInfo | 用于获取主机信息 |
QTcpSocket | 用于实现TCP套接字的类 |
QTcpServer | 用于实现TCP服务器端的类 |
QMessageBox | 用于显示消息框,向用户显示信息、警告或错误 |
QAudioInput | 用于音频输入设备的类 |
QThreadPool | 用于管理线程池 |
QAudioDevice | 用于访问和控制音频设备 |
QMediaDevices | 提供对媒体设备信息的访问 |
QCameraDevice | 用于访问和控制相机设备 |
QImageCapture | 用于捕获摄像头录制的图片 |
QMediaRecorder | 用于录制媒体内容 |
QMediaCaptureSession | 管理媒体捕获会话的类 |
表1.头文件列表
界面设计
设计思想是为了实现多个用户同时视频的功能,但是没实现服务端,于是把服务端使用的QTcpServer转到客户端实现,写Demo时本人将更多考虑的是提供更强的可扩展性
初始化设备函数
以下函数为初始化设备的函数,针对Ui文件中控件名称的不同可以修改对应的指针,完成效果
/* 初始化MainWindow界面信息* device:当前摄像头* 返回值:void
*/
void MainWindow::init_MainWindowInfo()
{session = new QMediaCaptureSession(this);session->setVideoOutput(ui->MyvideoPreview); //设置视频输出控件QAudioInput* audioInput = new QAudioInput(this);audioInput->setDevice(QMediaDevices::defaultAudioInput()); //获取默认的音频输入设备session->setAudioInput(audioInput); //设置音频输入设备QCameraDevice defaultCameraDevice = QMediaDevices::defaultVideoInput();//获取当前摄像头 if(defaultCameraDevice.isNull()){QMessageBox::critical(this,"提示","没有发现摄像头");return;}init_CameraDevice(defaultCameraDevice);
}/* 初始化摄像头* device:当前摄像头* 返回值:void
*/
void MainWindow::init_CameraDevice(const QCameraDevice& device)
{QByteArray defaultCameraID = device.id();int index=0;for(int i=0; i<QMediaDevices::videoInputs().size(); i++){QCameraDevice device =QMediaDevices::videoInputs().at(i);if (device.id() == defaultCameraID){ui->comboCam_List->addItem(device.description()+"(默认)",QVariant::fromValue(device));index=i;}else{ui->comboCam_List->addItem(device.description(),QVariant::fromValue(device));}}if (ui->comboCam_List->currentIndex() != index){ui->comboCam_List->setCurrentIndex(index);}camera_Obj = new QCamera(this); //初始化摄像头对象camera_Obj->setCameraDevice(device);session->setCamera(camera_Obj); //为session设置摄像头capture_Obj = new QImageCapture(this);//创建图像捕获对象session->setImageCapture(capture_Obj);//设置画面捕获对象//捕获到画面时发送画面connect(capture_Obj, &QImageCapture::imageCaptured, this, &MainWindow::handle_SendingImg);//设置摄像头信息connect(ui->comboCam_List,&QComboBox::currentIndexChanged,this, &MainWindow::handle_ShowCameraDevice);handle_ShowCameraDevice(ui->comboCam_List->currentIndex());
}/* 初始化摄像头设备信息* device:当前摄像头* 返回值:void
*/
void MainWindow::init_CameraDeviceInfo(const QCameraDevice& device)
{ui->comboCam_VideoRes->clear(); //支持的视频分辨率ui->comboCam_FrameRate->clear(); //支持的帧率范围foreach(QCameraFormat format, device.videoFormats()){QSize size=format.resolution();QString str=QString::asprintf("%d X %d",size.width(),size.height());ui->comboCam_VideoRes->addItem(str);str=QString::asprintf("%.1f ~ %.1f",format.minFrameRate(),format.maxFrameRate());ui->comboCam_FrameRate->addItem(str);}
}/* 显示摄像头信息* index:项下标* 返回值:void
*/
void MainWindow::handle_ShowCameraDevice(int index)
{QVariant var=ui->comboCam_List->itemData(index);QCameraDevice device = var.value<QCameraDevice>();init_CameraDeviceInfo(device); //显示摄像头设备信息camera_Obj->setCameraDevice(device); //重新设置摄像头设备
}
发起视频链接函数
//发起视频链接
void MainWindow::on_Camclose_Btn_2_clicked()
{QString ip = ui->lineEdit->text();QString port = ui->lineEdit_2->text();if(port.isEmpty() || ip.isEmpty()){QMessageBox::information(nullptr,"提示","未输入目标端口或IPv4地址",QMessageBox::Ok);}if(tcpSocket != nullptr){tcpSocket->deleteLater();}if(video_ThreadObj != nullptr){video_ThreadObj->handle_CloseThread();}tcpSocket = new QTcpSocket(this);qDebug() << "发起连接,目标地址:" << ip << "目标端口" << port.toUShort();//连接成功connect(tcpSocket,&QAbstractSocket::connected,this,&MainWindow::handle_Connect);//显示接收的图片connect(tcpSocket,&QTcpSocket::readyRead,this,&MainWindow::hanlde_RelayinThread);tcpSocket->connectToHost(ip,port.toUShort()); //链接服务器video_ThreadObj = new VideoThread(ui->show_Img);
}void MainWindow::handle_Connect()
{qDebug() << "连接成功";init_TimerModel();
}
初始化定时器模块函数
//初始化定时器
void MainWindow::init_TimerModel()
{timer_Obj = new QTimer;timer_Obj->start(100);//定时截取摄像头拍摄的图片connect(timer_Obj,&QTimer::timeout,this,&MainWindow::handle_InterceptImg);
}//定时截取摄像头图片
void MainWindow::handle_InterceptImg()
{QDir().mkdir("images");QString path = QDir::currentPath() + "/images/image.jpg"; //捕获图像的保存路径qDebug() << "定时截取摄像头图片" << path;capture_Obj->captureToFile(path);
}//发送截取的图片
void MainWindow::handle_SendingImg(int id, const QImage &image)
{QDir().mkdir("images");QString path = QDir::currentPath() + "/images/image.jpg"; //捕获图像的保存路径if (!image.isNull()) { //截取到图片image.save(path,"jpg",30); //保存图像并压缩,质量为30QFile file(path);file.open(QIODevice::ReadOnly);tcpSocket->write(file.readAll()); //发送图片tcpSocket->flush(); //确保消息被发送file.close();qDebug() << "发送图片";}
}
TCP链接模块函数
//初始化Tcp模块
void MainWindow::init_TcpModule()
{tcpSocket = new QTcpSocket;tcpServer = new QTcpServer(this);connect(tcpServer,&QTcpServer::newConnection,this,&MainWindow::init_TcpConnection);get_LocalIPv4(); //获取IPv4地址QString port = ui->lineEdit_3->text();//监听端口qDebug() << "监听端口" << port.toUShort();tcpServer->listen(QHostAddress::Any,port.toUShort()); //开始监听
}//初始化Tcp链接
void MainWindow::init_TcpConnection()
{qDebug() << "获取到连接";//获取第一个链接的客户端tcpSocket = tcpServer->nextPendingConnection();video_ThreadObj = new VideoThread(ui->show_Img);//显示接收的图片connect(tcpSocket,&QAbstractSocket::readyRead,this,&MainWindow::hanlde_RelayinThread);//连接成功connect(tcpSocket,&QAbstractSocket::connected,this,&MainWindow::handle_Connect);QThreadPool::globalInstance()->start(video_ThreadObj);//启动线程
}//转发至线程处理读取到的数据
void MainWindow::hanlde_RelayinThread()
{qDebug() << "处理接收的函数";video_ThreadObj->handle_ShowReceiveImg(tcpSocket->readAll());
}// 获取本机IPV4地址
int MainWindow::get_LocalIPv4()
{QString hostName = QHostInfo::localHostName(); //获取主机设备名QHostInfo hostInfo = QHostInfo::fromName(hostName);//获取主机信息QList<QHostAddress> addList = hostInfo.addresses();//获取主机地址列表if(addList.isEmpty()){ //未获取到地址return 0;}else{foreach(const QHostAddress& item,addList){if(item.protocol() == QAbstractSocket::IPv4Protocol){ui->udpIpv4_Box->addItem(item.toString());}}return 1;}return -1;
}
处理接收的数据线程函数
VideoThread::VideoThread(QObject *parent): QRunnable{}, QObject{parent}
{setAutoDelete(true); //设置线程自动析构
}VideoThread::VideoThread(QLabel *widget, QObject *parent): QRunnable{}, QObject{parent}, video_Label{widget}
{ok = true; //初始化线程状态setAutoDelete(true); //设置线程自动析构
}void VideoThread::run()
{while(ok);
}//显示接收的图像
void VideoThread::handle_ShowReceiveImg(QByteArray data)
{QPixmap pic = QPixmap::fromImage(QImage::fromData(data));video_Label->setPixmap(pic.scaled(video_Label->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
}//关闭线程
void VideoThread::handle_CloseThread()
{if(ok){ok = false;}
}
实现功能展示
PS:由于本机只有一个摄像头,就不能开另一边应用的摄像头了,后续会把Demo的github地址发布在评论区。该Demo还需要更多的完善才能真正的实现一个视频通话功能,后续会更新优化后的Demo
相关文章:

Qt Demo:基于TCP协议的视频传输Demo
目录 1.设计思路 2.Pro文件配置 3.头文件引入 4.界面设计 5.初始化设备函数 6.发起视频链接函数 7.初始化定时器模块函数 8.TCP链接模块函数 9.处理接收的数据线程函数 10.实现功能展示 设计思路 基于TCP协议的视频传输Demo,设计要实现的功能主要是TCP传输还有视频&…...

内存管理【C++】
内存分布 C中的内存区域主要有以下5种 栈(堆栈):存放非静态局部变量/函数参数/函数返回值等等,栈是向下增长的【地址越高越先被使用】。栈区内存的开辟和销毁由系统自动执行 堆:用于程序运行时动态内存分配ÿ…...

D3D 顶点格式学习
之前D3D画三角形的代码中有这一句, device.VertexFormat CustomVertex.TransformedColored.Format; 这是设置顶点格式; 画出的三角形如下, 顶点格式是描述一个三维模型的顶点信息的格式;可以包含以下内容, 位置…...

gmssl vs2010编译
1、虚拟机win10 x64,离线安装vs2010和2010sp1补丁; 2、安装ActivePerl_v5.28.1.0000和nasm-2.16.03-installer-x64均是默认完整安装; nasm官网下载: Index of /pub/nasm/releasebuilds/2.16.03/win64https://www.nasm.us/pub/nas…...

容器化部署gitlab、jenkins,jenkins应用示例
一、容器化部署docker和docker conpose安装 Docker&Docker-compose的安装及部署_docker 20 使用什么版本docker-compose-CSDN博客 1.docker 安装脚本 cat >01_docker.sh<<EOF #!/bin/bash yum remove docker \docker-client \docker-client-latest \docker-co…...

基于STM32的轻量级Web服务器设计
文章目录 一、前言1.1 开发背景1.2 实现的功能1.3 硬件模块组成1.4 ENC28J60网卡介绍1.5 UIP协议栈【1】目标与特点【2】核心组件【3】应用与优势 1.6 添加UIP协议栈实现创建WEB服务器步骤1.7 ENC28J60添加UIP协议栈实现创建WEB客户端1.8 ENC28J60移植UIP协议并编写服务器测试示…...
用r语言处理 Excel数据当中的缺失值方法
以下是使用 R 编程语言处理 Excel 缺失数据的一些常见方法示例代码:(无需循环) 读取包含缺失数据的 Excel 文件 data <- read.csv(“your_file.csv”) 查看数据中是否有缺失值 sum(is.na(data)) 用平均值填充缺失值 data c o l u m …...

AWS 高防和阿里云高防深度对比
随着网络攻击的不断增加,企业对于网络安全的需求也越来越高。在这种情况下,高防护服务成为了企业网络安全的重要组成部分。AWS和阿里云作为全球领先的云计算服务提供商,都提供了高防护服务,但它们之间存在着一些差异。我们九河云一…...

ctfshow web 月饼杯II
web签到 <?php //Author:H3h3QAQ include "flag.php"; highlight_file(__FILE__); error_reporting(0); if (isset($_GET["YBB"])) {if (hash("md5", $_GET["YBB"]) $_GET["YBB"]) {echo "小伙子不错嘛ÿ…...
「前端+鸿蒙」核心技术HTML5+CSS3(二)
1、开发者文档 开发者文档通常由浏览器厂商或技术社区提供,包含有关Web技术(如HTML、CSS、JavaScript)的详细信息,API文档,以及最佳实践。例如,MDN Web Docs是一个广泛认可的开发者资源。 2、块级元素与行列元素 块级元素:在页面上占据整行的元素,如<div>、<…...

unity接入live2d
在bilibili上找到一个教程,首先注意一点,你直接导入那个sdk,并且打开示例,显示的模型是有问题的,你需要调整模型上脚本的一个枚举值,调整它的渲染顺序是front z to我看教程时候,很多老师都没有提…...
练习题-17
以下题目来自2024年5月清华大学“丘成桐数学科学领军计划数学水平考试”。第11题本人参考了网友Fiddie (数学兔的极大理想)的解答,原网址是 https://mp.weixin.qq.com/s/q9slRWL4iO_TcSdkmbfbbw. 第10题:在10维列向量构成的内积空间 V V V中…...

乐高小人分类项目
数据来源 LEGO Minifigures | Kaggle 建立文件目录 BASE_DIR lego/star-wars-images/ names [YODA, LUKE SKYWALKER, R2-D2, MACE WINDU, GENERAL GRIEVOUS ] tf.random.set_seed(1)# Read information about dataset if not os.path.isdir(BASE_DIR train/):for name in …...
个人关于ChatGPT的用法及建议
概述 这里只是个人常用的几个软件,做一下汇总,希望对各位有用。 如果有更高认知的朋友,请留下你的工具名称,提醒我一下,谢谢~ 常用的chatgpt模型工具: 以下是一些知名的例子: 文…...

神经网络的工程基础(二)——随机梯度下降法|文末送书
相关说明 这篇文章的大部分内容参考自我的新书《解构大语言模型:从线性回归到通用人工智能》,欢迎有兴趣的读者多多支持。 本文涉及到的代码链接如下:regression2chatgpt/ch06_optimizer/stochastic_gradient_descent.ipynb 本文将讨论利用…...
常见的几种编码方式
常见的编码方式及其特点: 编码方式的设计是为了适应不同的字符集和应用需求,因此它们在表示字符时使用的位数和字节数各不相同 常见编码方式及其位数和字节数 ASCII(American Standard Code for Information Interchange)&#x…...

ubuntu移动硬盘重命名
因为在ubuntu上移动硬盘的名字是中文的,所以想要改成英文的。 我的方法: 将移动硬盘插到windows上,直接右键重命名。再插到ubuntu上名字就改变了。 别人的方法: ubuntu下如何修改U盘名字-腾讯云开发者社区-腾讯云 在自带的软件…...
VUE框架前置知识总结
一、前言 在学习vue框架中,总是有些知识不是很熟悉,又不想系统的学习JS,因为学习成本太大了,所以用到什么知识就学习什么知识。此文档就用于记录零散的知识点。主要是还是针对与ES6规范的JS知识点。 以下实验环境都是在windows环…...

张宇1000题80%不会?别急,这个方法肯定有用!
这太正常了,1000题的难度本来就高,不要慌 我考研的时候跟的也是张宇老师,但是1000题我根本就没做几道题就给换成880题660题了,而且只是强化阶段用880题,基础阶段我用的都是汤家凤的1800题。 不要担心做的不是张宇老师…...

【python】爬虫记录每小时金价
数据来源: https://www.cngold.org/img_date/ 因为这个网站是数据随时变动的,用requests、BeautifulSoup的方式解析html的话,数据的位置显示的是“--”,并不能取到数据。 所以采用webdriver访问网站,然后从界面上获取…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...