Qt 开源音视频框架模块之QtAV播放器实践
Qt 开源音视频框架模块QtAV播放器实践
1 摘要
QtAV是一个基于Qt的多媒体框架,旨在简化音视频播放和处理。它是一个跨平台的库,支持多种音视频格式,并提供了一个简单易用的API来集成音视频功能。QtAV的设计目标是为Qt应用程序提供强大的音视频处理能力,同时保持灵活性和可扩展性。可支持音频、视频播放,并提供了丰富的 API 和功能,让开发者能够轻松地处理多媒体数据。
2 QtAV的特点
•跨平台:支持Windows、Linux、macOS等多个操作系统。
•多媒体格式支持:支持多种音视频格式,包括常见的MP4、AVI、MKV、FLV、MP3、AAC等。
•高性能:基于FFmpeg,提供了高效的音视频解码和播放能力。
•简单易用的API:提供了直观的接口,方便开发者快速上手。
•集成Qt的特性:与Qt的信号和槽机制兼容,支持在Qt应用程序中使用。
3 简单播放器实践
3.1 环境配置
QtAv是基于ffmpeg开发的,因此我们需要下载相关依赖库。QtAV-depends-windows-x86+x64,下载完成后库目录结构如下
新建Qt项目QAVPlayer,在项目中添加QtAVWidgetsd库。
3.2 播放功能实现
•Qt实现简单的播放器功能,主要用到的函数:
•播放控制:play(), pause(), stop()等方法控制播放。
•音量调节:使用setVolume(int volume)方法设置音量。
•进度控制:使用seek(int position)方法跳转到特定时间。
•信号与槽:可以连接信号,例如播放结束、加载完成等事件,以便进行相应的处理。
初始化代码
QtAV::VideoOutput *m_vo;QtAV::AVPlayer *m_player;
QtAV::Widgets::registerRenderers();m_player = new QtAV::AVPlayer(this);m_vo = new QtAV::VideoOutput(this);m_player->setRenderer(m_vo);ui->verticalLayout->addWidget(m_vo->widget());connect(ui->playSlider, SIGNAL(sliderMoved(int)), SLOT(onSliderMoved(int)));connect(ui->playSlider, SIGNAL(sliderReleased()), SLOT(onSliderReleased()));connect(m_player, SIGNAL(positionChanged(qint64)), this, SLOT(onPositionChange(qint64)));connect(m_player, SIGNAL(notifyIntervalChanged()), SLOT(updateSliderUnit()));CommonUtils::loadStyleSheet(ui->playSlider, ":/QSS/QSS/VolumnSlider.css");connect(ui->volumnBtn, &VolumnButton::valueChanged, this, &MainWindow::onValueChanged);
打开文件播放功能代码
fileName= QFileDialog::getOpenFileName(this, tr("Open File"), "", tr("*.avi *.wmv *.mpg *.mpeg *.mov *.mp4 *.flv"));if (fileName.isEmpty())return;
m_player->seekBackward();
m_player->play(fileName);
暂停功能代码
if (m_player->isPaused()){m_player->pause(false);}else{m_player->pause(true);}this->style()->unpolish(ui->abortBtn);this->style()->polish(ui->abortBtn);ui->abortBtn->update();
音量设置功能代码
void MainWindow::onValueChanged(int value)
{if (value == 0 && ui->volumnBtn->property("silence").toBool() == false){ui->volumnBtn->setProperty("silence", true);ui->volumnBtn->setToolTip("静音");}else if (ui->volumnBtn->property("silence").toBool()){ui->volumnBtn->setProperty("silence", false);ui->volumnBtn->setToolTip("音量");}this->style()->unpolish(ui->volumnBtn);this->style()->polish(ui->volumnBtn);ui->volumnBtn->update();setVolume();
}
void MainWindow::setVolume()
{QtAV::AudioOutput *ao = m_player? m_player->audio() : 0;qreal v = qreal(ui->volumnBtn->volumnSlider()->value())*kVolumeInterval;if (ao) {if (qAbs(int(ao->volume() / kVolumeInterval) - ui->volumnBtn->volumnSlider()->value()) >= int(0.1 / kVolumeInterval)) {ao->setVolume(v);}}
}
音量滑竿功能代码
#include "volumnslider.h"
#include "commonutils.h"#include <QStyleOption>
#include <QPainter>
#include <QLabel>
#include <QDebug>VolumnSlider::VolumnSlider(QWidget *parent): QSlider(parent)
{CommonUtils::loadStyleSheet(this, ":/QSS/QSS/VolumnSlider.css");setWindowFlags(Qt::FramelessWindowHint | Qt::SubWindow);setAttribute(Qt::WA_TranslucentBackground);m_label = new QLabel(nullptr);m_label->setStyleSheet("QLabel{color:rgb(179, 179, 179);background:rgb(69,69,69);}");m_label->setMargin(6);m_label->setFixedHeight(24);m_label->setWindowFlags(Qt::FramelessWindowHint | Qt::SubWindow);m_label->hide();this->setRange(0, 100);this->setValue(100);
}VolumnSlider::~VolumnSlider()
{delete m_label;
}void VolumnSlider::paintEvent(QPaintEvent *event)
{QStyleOption opt;opt.init(this);QPainter p(this);style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);QSlider::paintEvent(event);
}void VolumnSlider::showEvent(QShowEvent *event)
{setFocus();QSlider::showEvent(event);
}void VolumnSlider::focusOutEvent(QFocusEvent *event)
{QPoint gloabPoint = this->mapToGlobal(QPoint(0, 0));QRect gloabRect(gloabPoint.x(), gloabPoint.y(), width(), height());if (!gloabRect.contains(QCursor::pos())){this->hide();m_label->hide();}else{this->setFocus();}QSlider::focusOutEvent(event);
}void VolumnSlider::mouseMoveEvent(QMouseEvent *event)
{setLabelText();QSlider::mouseMoveEvent(event);
}void VolumnSlider::mousePressEvent(QMouseEvent *event)
{setLabelText();m_label->show();QSlider::mousePressEvent(event);
}void VolumnSlider::mouseReleaseEvent(QMouseEvent *event)
{m_label->hide();QSlider::mouseReleaseEvent(event);
}void VolumnSlider::setLabelText()
{const QString&& value = QString::number(this->value());m_label->setFixedWidth(m_label->fontMetrics().width(value) + 12);m_label->setText(value);QPoint pos;;QPoint thePos = this->mapToGlobal(QPoint(0, 0));int posY = QCursor::pos().y();pos.setX(thePos.x() + 20);if (posY < thePos.y()){posY = thePos.y();}else if (posY > thePos.y() + this->height() - m_label->height()){posY = thePos.y() + this->height() - m_label->height();}pos.setY(posY);m_label->move(pos);
}
滑竿拖动快进功能核心代码
#include "playtimeslider.h"
//#include "head.h"#include <QPainter>
#include <QVariant>
#include <QTime>
#include <QApplication>const int pixwidth = 200;
const int pixheight = 116;using namespace QtAV;
ImageWidget::ImageWidget(QWidget *parent)
{m_timestr = "00:00:00";m_extractor = new VideoFrameExtractor(this);setWindowFlags(Qt::FramelessWindowHint | Qt::SubWindow);setAttribute(Qt::WA_TranslucentBackground);hide();connect(m_extractor, SIGNAL(frameExtracted(QtAV::VideoFrame)), SLOT(displayFrame(QtAV::VideoFrame)));connect(m_extractor, SIGNAL(error(const QString &)), SLOT(displayNoFrame()));connect(m_extractor, SIGNAL(aborted(const QString &)), SLOT(displayNoFrame()));
}void ImageWidget::displayFrame(const QtAV::VideoFrame &frame){if (!frame.isValid()) {displayNoFrame();return;}m_image = frame.toImage();update();
}void ImageWidget::displayNoFrame()
{m_image = QImage();update();
}void ImageWidget::seekVideoFrame(qint64 value)
{m_timestr = QTime(0, 0, 0).addMSecs(value).toString(QString::fromLatin1("HH:mm:ss"));m_extractor->setPosition(value);m_extractor->extract();update();
}void ImageWidget::setPlayFile(const QString& file)
{m_extractor->setSource(file);
}void ImageWidget::paintEvent(QPaintEvent *event)
{QWidget::paintEvent(event);QPainter painter(this);painter.setPen(Qt::NoPen);painter.setBrush(QColor("333333"));painter.drawRect(0, 0, this->width(), this->height() - 16);painter.setPen(Qt::white);painter.drawText(0, this->height() - 16, this->width(), 16, Qt::AlignCenter, m_timestr);painter.drawText(0, 0, this->width(), this->height() - 16, Qt::AlignCenter, "加载中...");if (!m_image.isNull()){painter.drawImage(QRect(0, 0, this->width(), this->height() - 16), m_image.scaled(this->width(), this->height() - 16, Qt::KeepAspectRatio, Qt::FastTransformation));}
}PlayTimeSlider::PlayTimeSlider(QWidget *parent): QSlider(parent)
{m_pixWidget = new ImageWidget(nullptr);m_pixWidget->setFixedSize(pixwidth, pixheight);
}PlayTimeSlider::~PlayTimeSlider()
{delete m_pixWidget;
}void PlayTimeSlider::stopPlayer()
{this->setValue(minimum());
}void PlayTimeSlider::setPlayervalue(int value)
{if (!isSliderDown())setValue(value);
}void PlayTimeSlider::showEvent(QShowEvent *event)
{setFocus();QSlider::showEvent(event);
}void PlayTimeSlider::focusOutEvent(QFocusEvent *event)
{QPoint gloabPoint = this->mapToGlobal(QPoint(0, 0));QRect gloabRect(gloabPoint.x(), gloabPoint.y(), width(), height());if (!gloabRect.contains(QCursor::pos())){m_pixWidget->hide();}else{this->setFocus();}QSlider::focusOutEvent(event);
}void PlayTimeSlider::mouseMoveEvent(QMouseEvent *event)
{updatePixWidgetPosition();QSlider::mouseMoveEvent(event);
}void PlayTimeSlider::mousePressEvent(QMouseEvent *event)
{QSlider::mousePressEvent(event);
}void PlayTimeSlider::mouseReleaseEvent(QMouseEvent *event)
{m_pixWidget->hide();QSlider::mouseReleaseEvent(event);
}void PlayTimeSlider::setImageWidgetVisibe(bool visible)
{m_pixWidget->setVisible(visible);
}void PlayTimeSlider::seekVideoFrame(qint64 value)
{m_pixWidget->seekVideoFrame(value);
}void PlayTimeSlider::updatePixWidgetPosition()
{QPoint pos;QPoint thePos = this->mapToGlobal(QPoint(0, 0));int mouseX = QCursor::pos().x();int posX = mouseX - pixwidth / 2;pos.setY(thePos.y() - pixheight - 4);if (mouseX < thePos.x()){posX = thePos.x() - pixwidth / 2;}else if (mouseX > thePos.x() + this->width()){posX = thePos.x() + this->width() - pixwidth / 2;}pos.setX(posX);m_pixWidget->move(pos);
}void PlayTimeSlider::setVideoFile(const QString& file)
{m_pixWidget->setPlayFile(file);
}
4 播放器效果
5 总结
本文介绍了Qt中开源音视频框架模块QtAV,通过使用该音视频播放库实现简单的播放器,了解该库的使用方法,可为后续音视频开发提供更丰富的功能,开发出更好用灵活音视频设计。后续则继续总结分享Qt应用开发中的其他应用,开发不易珍惜每一分原创和劳动成果,同时注意平时开发过程中的经验积累总结。
相关文章:

Qt 开源音视频框架模块之QtAV播放器实践
Qt 开源音视频框架模块QtAV播放器实践 1 摘要 QtAV是一个基于Qt的多媒体框架,旨在简化音视频播放和处理。它是一个跨平台的库,支持多种音视频格式,并提供了一个简单易用的API来集成音视频功能。QtAV的设计目标是为Qt应用程序提供强大的音视…...
【前端】XML,XPATH,与HTML的关系
XML与HTML关系 XML(可扩展标记语言)和 HTML(超文本标记语言)是两种常见的标记语言,但它们有不同的目的和用途。它们都使用类似的标记结构(标签),但在设计上存在一些关键的差异。 XML…...

ubuntu服务器安装VASP.6.4.3
ubuntu服务器安装VASP.6.4.3 1 安装Intel OneAPI Base Toolkit和Intel OneAPI HPC Toolkit1.1 更新并安装环境变量1.2 下载Intel OneAPI Base Toolkit和Intel OneAPI HPC Toolkit安装包1.3 安装 Intel OneAPI Base Toolkit1.4 安装 Intel OneAPI HPC Toolkit1.5 添加并激活环境…...

市场加速下跌,但监管「坚冰」正在消融
作者:Techub 热点速递 撰文:Yangz,Techub News 与近日气温逐步回暖不同,自 2 月 25 日比特币跌破 9 万美元以来,加密货币市场行情一路下滑。今日 10 时 50 分左右,比特币更是跌破 8 万美元大关,…...
7.2 - 定时器之计算脉冲宽度实验
文章目录 1 实验任务2 系统框图3 软件设计 1 实验任务 本实验任务是通过CPU私有定时器来计算按键按下的时间长短。 2 系统框图 参见7.1。 3 软件设计 注意事项: 定时器是递减计数的,需要考虑StartCount<EndCount的情况。 /***********…...
Imagination DXTP GPU IP:加速游戏AI应用,全天候畅玩无阻
日前,Imagination 推出了最新产品——Imagination DXTP GPU IP,在智能手机和其他功耗受限设备上加速图形和AI工作负载时,保证全天候的电池续航。它是我们最新D系列GPU的最终产品,集成了自2022年发布以来引入的一系列功能ÿ…...

关于流水线的理解
还是不太理解,我之前一直以为,对axis总线,每一级的寄存器就像fifo一样,一级一级的分级存储最后一级需要的数据。(现在看来,我这个理解应该也是没有问题的) 如下图,一开始是在解析axi…...

采样算法二:去噪扩散隐式模型(DDIM)采样算法详解教程
参考 https://arxiv.org/pdf/2010.02502 一、背景与动机 去噪扩散隐式模型(DDIM) 是对DDPM的改进,旨在加速采样过程同时保持生成质量。DDPM虽然生成效果优异,但其采样需迭代数百至数千次,效率较低。DDIM通过以下关键…...
北京大学DeepSeek提示词工程与落地场景(PDF无套路免费下载)
近年来,大模型技术飞速发展,但许多用户发现:即使使用同一款 AI 工具,效果也可能天差地别——有人能用 AI 快速生成精准方案,有人却只能得到笼统回答。这背后的关键差异,在于提示词工程的应用能力。 北京大…...
Hutool - POI:让 Excel 与 Word 操作变得轻而易举
各位开发者们,在日常的 Java 开发工作里,处理 Excel 和 Word 文件是相当常见的需求。无论是从 Excel 里读取数据进行分析,还是将数据写入 Excel 生成报表,亦或是对 Word 文档进行内容编辑,传统的 Apache POI 库虽然功能…...

IDEAPyCharm安装ProxyAI(CodeGPT)插件连接DeepSeek-R1教程
背景:最近DeepSeek比较火嘛,然后在githup上也看到了GitHub Copilot,就想着现在AI的准确率已经可以提高工作效率了。所以从网上找了一些编程插件,发现Proxy支持的模型比较多,通用性和适配性比较好。所以本文记录一下pro…...
Iceberg Catalog
使用限制 支持 Iceberg V1/V2 表格式。支持 Position Delete。2.1.3 版本开始支持 Equality Delete。支持 Parquet 文件格式2.1.3 版本开始支持 ORC 文件格式。 创建 Catalog 基于 Hive Metastore 创建 Catalog 和 Hive Catalog 基本一致,这里仅给出简单示…...
2025年2月个人工作生活总结
本文为 2025年2月工作生活总结。 工作记录 AI浪潮 AI非常火,春节至今,到处充斥着大量和AI、DeepSeek有关的新闻。领导也一再强调要用AI,甚至纳入到新一年的考核里。再往上,大领导开会的新闻稿里也作出要求,不能停下脚…...
vscode java环境中文乱码的问题
先说我的结论: 由于我的系统是windows的,所以vscode使用的是默认gbk的编码进行的。 但是我的目的是全部都使用utf-8,因为我的程序始终是要去linux上去运行的,总不能在本地是好的,然后到服务器上就不行了吧,…...

Java数据结构第十五期:走进二叉树的奇妙世界(四)
专栏:Java数据结构秘籍 个人主页:手握风云 目录 一、二叉树OJ练习题(续) 1.1. 二叉树的层序遍历 1.2. 二叉树的最近公共祖先 1.3. 从前序与中序遍历序列构造二叉树 1.4. 从中序与后序遍历序列构造二叉树 1.5. 根据二叉树创建…...
【MySQL】CAST()在MySQL中的用法以及其他常用的数据类型转换函数
1. cast() CAST() 在 MySQL 中用于将一个表达式的类型转换为另一个类型。这在处理不同类型的数据时非常有用,比如将字符串转换为数字,或者将浮点数转换为整数等。 1.1 CAST() 函数的基本语法 CAST() 函数的基本语法如下: CAST(expression…...

使用Truffle、Ganache、MetaMask、Vue+Web3完成的一个简单区块链项目
文章目录 概要初始化Truffle项目创建编写合约编译合约配置Ganache修改truffle-config.js文件编写迁移文件部署合约使用Truffle 控制台使用MetaMask和VueWeb3与链交互 概要 使用Truffle、Ganache、MetaMask、VueWeb3完成的一个简单区块链项目。 初始化Truffle项目 安装好truf…...

初出茅庐的小李博客之按键驱动库使用
驱动库介绍 源码地址:https://github.com/jiejieTop/ButtonDrive 使用只需3步,创建按键,按键事件与回调处理函数链接映射,周期检查按键,支持单双击、连按、长按;采用回调处理按键事件(自定义消…...
如何调试Linux内核?
通过创建一个最小的根文件系统,并使用QEMU和GDB进行调试。 1.准备工作环境 确保系统上安装了所有必要的工具和依赖项。 sudo apt-get update //更新一下软件包 sudo apt-get install build-essential git libncurses-dev bison flex libssl-dev qemu-system-x…...
ECharts组件封装教程:Vue3中的实践与探索
在日常的前端开发中,ECharts 作为一款强大且易用的图表库,被广泛应用于数据可视化场景。为了更好地在 Vue3 项目中复用 ECharts 功能,我们可以将其封装成一个组件。本文将带大家一步步实现 ECharts 的 Vue3 组件封装,并演示如何在父组件中调用和使用。 一、封装 ECharts 组…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...