Qt/C++编写音视频实时通话程序/画中画/设备热插拔/支持本地摄像头和桌面
一、前言
近期有客户提需求,需要在嵌入式板子上和电脑之间音视频通话,要求用Qt开发,可以用第三方的编解码组件,能少用就尽量少用,以便后期移植起来方便。如果换成5年前的知识储备,估计会采用纯网络通信收发图片和声音数据方式实现,比如用qcamera打开摄像头,转成图片,base64编码发送,接收到收到后base64解码成图片绘制,声音用qaudioinput采集后pcm数据直接tcp发送,收到后直接发给qaudiooutput设备播放即可。这种能不能实现效果呢,也是可以的,就是体验不大好友好,比如画面的流畅度要低不少,估计只能做到20fps,而且双方只能用私有协议通信,外部如果要取流比如在网页查看通话的视频,无法实现。
按照现在的知识储备,方案就很多了,最佳方案就是用推拉流,实时性极好,局域网可以做到0.2s内,而且音视频通话都是实时的,可以连续7*24小时运行,而且可以拓展成一对多通话,多一个人员加入只需要多一路拉流就行,非常的方便,悬浮画面还可以设置各种位置,使用下来的效果非常的棒。所以随着知识储备的增加,以前无法解决的疑难杂症,现在只需要分分钟就搞定。
二、效果图
三、相关地址
- 国内站点:https://gitee.com/feiyangqingyun
- 国际站点:https://github.com/feiyangqingyun
- 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
- 文件地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_call。
四、功能特点
- 支持局域网和外网音视频实时通话,延迟极低,资源占用极低。
- 自动获取本地所有视音频输入设备,本地摄像头设备自动罗列所有支持的分辨率、帧率、采集格式等信息。
- 可以指定采集的视频设备和音频输入设备,自由组合,视频设备可以设置不同的分辨率、帧率、采集格式。
- 支持本地桌面屏幕作为视频设备采集,支持多个屏幕,自动识别屏幕分辨率。
- 可以选择不同的声卡设备播放声音。
- 内置自动重连机制,视音频设备支持热插拔。
- 支持固定画中画功能,可交换主画面和浮窗画面,可设置画面左右排列等布局方式。
- 可自定义悬浮画面位置,指定左上角、右上角、左下角、右下角、自定义位置和大小。
- 内置流媒体服务程序,程序启动后自动启动流媒体服务,自动推拉流。
- 视音频流数据支持rtsp/rtmp/http/webrtc等方式拉流,可以直接网页上打开视频画面。
- 实时显示本地音频振幅和远程音量振幅,可以分别对输入输出音量设置静音,方便测试。
- 支持自定义水印,包括文字和图片水印,支持多个水印,指定任意位置。
- 支持不同的视音频设备组合,比如本地摄像头加电脑麦克风而不是摄像头的麦克风,比如本地电脑桌面屏幕加摄像头的麦克风等。
- 纯Qt+ffmpeg编写,支持windows和linux以及macos等系统,支持所有Qt版本、所有系统、所有编译器。
- 支持嵌入式linux板子和树莓派香橙派等,以及国产linux系统。
五、相关代码
#include "frmconfig.h"
#include "frmmain.h"
#include "ui_frmmain.h"
#include "qthelper.h"
#include "apphelper.h"
#include "osdgraph.h"
#include "ffmpegthread.h"
#include "ffmpegthreadcheck.h"frmMain::frmMain(QWidget *parent) : QWidget(parent), ui(new Ui::frmMain)
{ui->setupUi(this);this->initForm();this->installEventFilter(this);QMetaObject::invokeMethod(this, "formChanged", Qt::QueuedConnection);
}frmMain::~frmMain()
{delete ui;
}void frmMain::savePos()
{AppConfig::FormMax = this->isMaximized();if (!AppConfig::FormMax) {AppConfig::FormGeometry = this->geometry();}AppConfig::writeConfig();
}bool frmMain::eventFilter(QObject *watched, QEvent *event)
{//尺寸发生变化或者窗体移动位置记住窗体位置int type = event->type();if (type == QEvent::Resize || type == QEvent::Move) {QMetaObject::invokeMethod(this, "savePos", Qt::QueuedConnection);} else if (type == QEvent::Close) {audioInput->stop(false);audioOutput->stop(false);exit(0);}//尺寸发生变化重新调整小预览窗体的位置if (this->isVisible() && type == QEvent::Resize) {this->formChanged();}return QWidget::eventFilter(watched, event);
}void frmMain::initForm()
{//初始化输入输出视频控件AppHelper::initVideoWidget(ui->videoInput);AppHelper::initVideoWidget(ui->videoOutput);//初始化输入输出音频线程audioInput = new FFmpegThread(this);audioOutput = new FFmpegThread(this);checkInput = new FFmpegThreadCheck(audioInput, this);AppHelper::initAudioThread(audioInput, ui->levelInput);AppHelper::initAudioThread(audioOutput, ui->levelOutput);//输入打开成功后立即推流connect(audioInput, SIGNAL(receivePlayStart(int)), this, SLOT(receivePlayStart(int)));connect(ui->videoInput, SIGNAL(sig_receivePlayStart(int)), this, SLOT(receivePlayStart(int)));ui->ckInput->setChecked(AppConfig::MuteInput ? Qt::Checked : Qt::Unchecked);ui->ckOutput->setChecked(AppConfig::MuteOutput ? Qt::Checked : Qt::Unchecked);if (AppConfig::StartServer) {on_btnStart_clicked();}
}void frmMain::clearLevel()
{ui->levelInput->setLevel(0);ui->levelOutput->setLevel(0);
}void frmMain::formChanged()
{AppHelper::changeWidget(ui->videoInput, ui->videoOutput, ui->gridLayout, NULL);
}void frmMain::receivePlayStart(int time)
{QObject *obj = sender();if (obj == ui->videoInput) {
#ifdef betaversionOsdGraph::testOsd(ui->videoInput);
#endifui->videoInput->recordStart(AppConfig::VideoPush);} else if (obj == audioInput) {audioInput->recordStart(AppConfig::AudioPush);}
}void frmMain::on_btnStart_clicked()
{if (ui->btnStart->text() == "启动服务") {if (AppConfig::VideoUrl == "video=" || AppConfig::AudioUrl == "audio=") {QtHelper::showMessageBoxError("请先打开系统设置, 选择对应的视音频设备");//return;}ui->videoInput->open(AppConfig::VideoUrl);ui->videoOutput->open(AppConfig::VideoPull);audioInput->setMediaUrl(AppConfig::AudioUrl);audioOutput->setMediaUrl(AppConfig::AudioPull);audioInput->play();audioOutput->play();checkInput->start();ui->btnStart->setText("停止服务");} else {ui->videoInput->stop();ui->videoOutput->stop();audioInput->stop();audioOutput->stop();checkInput->stop();ui->btnStart->setText("启动服务");QMetaObject::invokeMethod(this, "clearLevel", Qt::QueuedConnection);}AppConfig::StartServer = (ui->btnStart->text() == "停止服务");AppConfig::writeConfig();
}void frmMain::on_btnConfig_clicked()
{static frmConfig *config = NULL;if (!config) {config = new frmConfig;connect(config, SIGNAL(formChanged()), this, SLOT(formChanged()));}config->show();config->activateWindow();
}void frmMain::on_ckInput_stateChanged(int arg1)
{bool muted = (arg1 != 0);audioInput->setMuted(muted);AppConfig::MuteInput = muted;AppConfig::writeConfig();
}void frmMain::on_ckOutput_stateChanged(int arg1)
{bool muted = (arg1 != 0);audioOutput->setMuted(muted);AppConfig::MuteOutput = muted;AppConfig::writeConfig();
}
相关文章:

Qt/C++编写音视频实时通话程序/画中画/设备热插拔/支持本地摄像头和桌面
一、前言 近期有客户提需求,需要在嵌入式板子上和电脑之间音视频通话,要求用Qt开发,可以用第三方的编解码组件,能少用就尽量少用,以便后期移植起来方便。如果换成5年前的知识储备,估计会采用纯网络通信收发…...
Android trace presentFence屏幕显示的帧
Android trace presentFence屏幕显示的帧 presentFence :当帧成功显示到屏幕时,present fence就会signal。 FrameMissed/GpuFrameMissed/HwcFrameMissed表示上一次合成的结果,当SurfaceFlinger合成后显示到屏幕上,present fence就…...
Spring是如何实现scope作用域支持
众所周知在Spring的Bean当中是存在两种作用域的,即单例模式与多例模式,可通过scope来指定 下面就是注册一个多例Bean <bean id"people" class"org.qlspringframework.beans.ioc.bean.People" scope"prototype"> …...
Helm Chart 中配置多个 Docker Registry 地址以实现备用访问
在 Helm Chart 中配置多个 Docker Registry 地址以实现备用访问,可以通过以下几种方式实现: 1. 在 values.yaml 中定义多个 Registry 在 values.yaml 中定义主 Registry 和备用 Registry,以便在部署时灵活切换: # values.yaml …...
FreeSWITCH rtcp-mux 测试
rtcp 跟 rtp 占用同一个端口,这就是 rtcp 复用 Fs 呼出是这样的: originate [rtcp_muxtrue][rtcp_audio_interval_msec5000]user/1001 &echo 需要同时指定 rtcp_audio_interval_msec,否则 rtcp_mux 不能生效 Fs 呼入不需要配置…...

c++ 类的语法4
测试析构函数、虚函数、纯虚函数: void testClass5() {class Parent {public:Parent(int x) { cout << "Parent构造: " << x << endl; }~Parent() {cout << "调用Parent析构函数" << endl;}virtual string toSt…...

NMOS和PMOS的区别
1 区分NMOS和PMOS:衬底箭头指向G级的是NMOS,衬底箭头背向G级的是PMOS 2 区分D和S级:针对NMOS,体二极管的正方向为S级;针对PMOS,体二极管正的方向为D级 3 区分电流方向:针对NMOS,电…...

java云原生实战之graalvm 环境安装
windows环境安装 在Windows环境下安装GraalVM并启用原生镜像功能时,需要Visual Studio的组件支持。具体要点如下: 核心依赖: 需要安装Visual Studio 2022或更新版本,并确保勾选以下组件: "使用C的桌面开发"…...

2025年电工杯新规发布-近三年题目以及命题趋势
电工杯将于2025.5.23 周五早八正式开赛,该竞赛作为上半年度竞赛规模最大的竞赛,因免报名费、一级学会承办等因素,被众多高校认可。本文将在从2025年竞赛新规、历史赛题选题分析、近年优秀论文分享、竞赛模板分析等进行电工杯备赛,…...
python打卡day30@浙大疏锦行
知识点回顾: 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑:找到根目录(python解释器的目录和终端的目录不一致) 作业:自己新建几个不同路径文件尝试下如何导入 具体操作步骤: 在桌面…...

替换word中的excel
PostMapping("/make/report/target/performance/first") public AjaxResult makeTargetReportFirst(RequestBody MakeReportDTO makeReportDTO) {Map<String, String> textReplaceMap new HashMap<>();// 替换日期LocalDateTime nowData LocalDateTime…...

大模型服务如何实现高并发与低延迟
写在前面 大型语言模型(LLM)正以前所未有的速度渗透到各行各业,从智能客服、内容创作到代码生成、企业知识库,其应用场景日益丰富。然而,将这些强大的 AI 能力转化为稳定、高效、可大规模应用的服务,却面临着巨大的挑战,其中高并发处理能力和低响应延迟是衡量服务质量的…...
异丙肌苷市场:现状、挑战与未来展望
摘要 本文聚焦异丙肌苷市场,深入分析了其市场规模与增长趋势、应用价值与市场驱动因素、面临的挑战以及竞争格局。异丙肌苷作为一种具有重要应用价值的改性核苷衍生物,在药物研发和治疗领域展现出潜力,但市场发展也面临诸多挑战。文章最后为…...

OBS Studio:windows免费开源的直播与录屏软件
OBS Studio是一款免费、开源且跨平台的直播与录屏软件。其支持 Windows、macOS 和 Linux。OBS适用于,有直播需求的人群或录屏需求的人群。 Stars 数64,323Forks 数8413 主要特点 推流:OBS Studio 支持将视频实时推流至多个平台,如 YouTube、…...
[ 计算机网络 ] | 宏观谈谈计算机网络
(目录占位) 网络间通信,本质是不同的两个用户通信;本质是两个不同主机上的两个进程间通信。 因为物理距离的提升,就衍生出了很多问题。TCP/IP协议栈 / OSI七层模型,将协议分层,每一层都是为了…...

经典面试题:TCP 三次握手、四次挥手详解
在网络通信的复杂架构里,“三次握手”与“四次挥手”仿若一座无形的桥梁,它们是连接客户端与服务器的关键纽带。这座“桥梁”不仅确保了连接的稳固建立,还保障了连接的有序结束,使得网络世界中的信息能够顺畅、准确地流动。 在面…...

高光谱数据处理技术相关
一、Savitzky-Golay(SG)平滑 1. 基本概念 Savitzky-Golay(SG)平滑是一种基于局部多项式拟合的卷积算法,主要用于信号处理(如光谱、色谱数据)的去噪和平滑。其核心思想是通过滑动窗口内的多项式拟合来保留信号的原始特征(如峰形、宽度),同时抑制高频噪声。 2. 技术原…...
【动态规划】P10988 [蓝桥杯 2023 国 Python A] 走方格|普及+
本文涉及知识点 C动态规划 P10988 [蓝桥杯 2023 国 Python A] 走方格 题目描述 给定一个 N N N 行 N N N 列的方格,第 i i i 行第 j j j 列的方格坐标为 ( i , j ) (i, j) (i,j),高度为 H i , j H_{i,j} Hi,j。小蓝从左上角坐标 ( 0 , 0 ) …...
Rocketmq leader选举机制,通过美国大选解释
通过2020年美国大选的比喻,可以形象地解释RocketMQ的Leader选举机制(以DLedger模式为例)。我们将美国大选中的关键角色和流程映射到RocketMQ的集群中,帮助理解其工作原理。 1. 角色类比 美国大选RocketMQ DLedger集群说明选民&am…...

机器视觉的PVC卷对卷丝印应用
在现代工业制造领域,PVC卷对卷丝印工艺凭借其高效、灵活的特点,广泛应用于广告制作、包装印刷、电子产品装饰等多个行业。然而,在高速连续的丝印过程中,如何确保印刷图案的精准定位、色彩一致性以及质量稳定性,一直是困…...
利用 SQL Server 作业实现异步任务处理,简化系统架构
在现代企业系统中,异步任务是不可或缺的组成部分,例如: 电商系统中的订单超时取消; 报表系统中的异步数据导出; CRM 系统中的客户积分计算。 传统的实现方式通常涉及引入消息队列(如 RabbitMQ、Kafka&a…...

LabVIEW数据库使用说明
介绍LabVIEW如何在数据库中插入记录以及执行 SQL 查询,适用于对数据库进行数据管理和操作的场景。借助 Database Connectivity Toolkit,可便捷地与指定数据库交互。 各 VI 功能详述 左侧 VI 功能概述:实现向数据库表中插入数据的操作。当输入…...
MATLAB实现GAN用于图像分类
生成对抗网络(GAN)是一种强大的生成模型,由生成器(Generator)和判别器(Discriminator)组成。生成器用于生成图像,判别器用于判断图像是真实的还是生成的。在MATLAB中实现GAN用于图像…...

25考研经验贴(11408)
声明:以下内容都仅代表个人观点 数学一(130) 25考研数学一难度介绍:今年数学一整体不难,尤其是选填部分,大题的二型线面和概率论大题个人感觉比较奇怪,其他大题还是比较容易的。.26如何准备&a…...

java中的Filter使用详解
Filter(过滤器)是 Java Web 开发的核心组件之一,用于在请求到达 Servlet 或响应返回客户端之前进行拦截和处理。以下是其核心功能、使用方法和实际场景的详细解析: 一、Filter 的作用与原理 核心作用 Filter 充当请求与响应之间的…...
PostgreSQL初体验
目录 一、PostgreSQL 核心概述 (一)定义与定位 (二)核心特性与优势 (三)应用场景 二、PostgreSQL 安装实战(基于 OpenEuler 24) (一)编译安装ÿ…...

css使用clip-path属性切割显示可见内容
1. 需求 想要实现一个渐变的箭头Dom,不想使用svg、canvas去画,可以考虑使用css的clip-path属性切割显示内容。 2. 实现 <div class"arrow">箭头 </div>.arrow{width: 200px;height: 60px;background-image: linear-gradient(45…...

新京东,正在成为一种生活方式
出品|何玺排版|叶媛 一个新京东,正在从“心”诞生。 2025年2月11日之前,如果问京东是做什么的,相信大多数人会回答京东是电商平台,卖家电数码日用百货的。现在,如果问京东是做什么的,相信大家的回答不在是…...

Linux 文件(2)
文章目录 1. 文件描述符1.1 文件描述符是什么1.2 文件描述符如何分配 2 重定向2.1 输出重定向2.2 输入重定向2.3 使用dup2进行重定向 3. 文件、父子进程和进程替换 1. 文件描述符 1.1 文件描述符是什么 什么是文件描述符呢? 我们先来看之前所介绍的系统级别的文件…...
分析 redis 的 exists 命令有一个参数和多个参数的区别
在 redis 中,exists 命令是用来查询某个或多个 key 是否存在的,返回存在的 key 的个数。 由于 redis 是按照键值对方式存储数据的,于是一个 key 只能对应一组数据,那么上述的 key 的个数指的即是需要查询的 key 中有几个 key 是存…...