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访问网站,然后从界面上获取…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
第三周 Day 3 🎯 今日目标 理解类(class)和对象(object)的关系学会定义类的属性、方法和构造函数(init)掌握对象的创建与使用初识封装、继承和多态的基本概念(预告) &a…...
