当前位置: 首页 > news >正文

c++ QT 实现QMediaPlayer播放音频显示音频级别指示器

文章目录

      • 效果图
      • 概述
      • 代码
      • 总结

效果图

在这里插入图片描述


概述

  • QMediaPlayer就不介绍了,就提供了一个用于播放音频和视频的媒体播放器

  • QAudioProbe 它提供了一个探针,用于监控音频流。当音频流被捕获或播放时,QAudioProbe 可以接收到音频数据。这个类在需要访问音频数据以进行分析或处理的情况下非常有用,而不需要直接与音频设备交互。

  • audioBufferProbedQAudioProbe 的一个信号,当音频数据可用时这个信号会被发射。这个信号的参数是一个 QAudioBuffer 对象,它包含了音频数据的详细信息,比如采样率、通道数、格式以及音频数据本身。当 QAudioProbe 与一个 QMediaPlayer,它可以探测到这个媒体对象的音频输出。当媒体对象播放音频时,音频数据会通过 audioBufferProbed 信号传递槽函数,通过槽函数处理音频缓冲区,更新音频级别显示器。

        player = new QMediaPlayer(this);auto m_audioHistogram = new HistogramWidget(this);auto probe = new QAudioProbe(this);connect(probe, &QAudioProbe::audioBufferProbed, m_audioHistogram, &HistogramWidget::processBuffer);probe->setSource(player);
    
  • 还有一个关键点就是分析给定的QAudioBuffer对象,计算每个通道的峰值电平,从而获取音频缓冲区的电平值。

  • 通过得到的电平值利用paintEvent将其绘制出来,并采用QLinearGradient实现渐变色使得更加美观。


代码

  • 直接把cpp代码都贴出来,做了很详细的注释,篇幅限制就不把类声明贴出,没有特殊处理。
  #include "HistogramWidget.h"#include <QPainter>#include <QHBoxLayout>template <class T>static QVector<qreal> getBufferLevels(const T *buffer, int frames, int channels);/*** 获取音频格式的最大峰值值。** 此函数根据给定的QAudioFormat对象参数,计算并返回一个表示该音频格式下理论上的最大值。* 主要用于支持PCM格式的音频,对非PCM格式或无效的格式,函数将返回0。** @param format QAudioFormat对象,包含待检查的音频格式信息。* @return 返回一个qreal类型值,表示音频格式的最大峰值。对于不支持的格式或无效的参数,返回0。*/qreal getPeakValue(const QAudioFormat &format){// 检查音频格式是否有效if (!format.isValid())return qreal(0);// 检查音频编码是否为PCMif (format.codec() != "audio/pcm")return qreal(0);// 根据样本类型计算峰值值switch (format.sampleType()){case QAudioFormat::Unknown:break;case QAudioFormat::Float:// 对于浮点样本,只支持32位,且返回一个略大于1的值if (format.sampleSize() != 32)return qreal(0);return qreal(1.00003);case QAudioFormat::SignedInt:// 对于有符号整数样本,根据样本大小返回相应的最大值if (format.sampleSize() == 32)return qreal(INT_MAX);if (format.sampleSize() == 16)return qreal(SHRT_MAX);if (format.sampleSize() == 8)return qreal(CHAR_MAX);break;case QAudioFormat::UnSignedInt:// 对于无符号整数样本,根据样本大小返回相应的最大值if (format.sampleSize() == 32)return qreal(UINT_MAX);if (format.sampleSize() == 16)return qreal(USHRT_MAX);if (format.sampleSize() == 8)return qreal(UCHAR_MAX);break;}// 如果没有匹配到任何已知情况,返回0return qreal(0);}template <class T>/*** 获取缓冲区中每个通道的最大值。** @param buffer 指向音频帧数据的指针,数据类型为T,假设为原始音频样本。* @param frames 音频帧的数量。* @param channels 音频的通道数。* @return QVector<qreal> 返回一个包含每个通道最大值的向量。*/QVector<qreal> getBufferLevels(const T *buffer, int frames, int channels){// 初始化一个向量来保存每个通道的最大值,初始值为0。QVector<qreal> max_values;max_values.fill(0, channels);// 遍历所有帧for (int i = 0; i < frames; ++i){// 遍历当前帧中的每个通道for (int j = 0; j < channels; ++j){// 计算当前样本的绝对值qreal value = qAbs(qreal(buffer[i * channels + j]));// 如果当前样本值大于当前通道的最大值,则更新最大值if (value > max_values.at(j))max_values.replace(j, value);}}return max_values;}/*** 获取音频缓冲区的电平值。** 该函数分析给定的QAudioBuffer对象,计算每个通道的峰值电平,并返回一个包含每个通道当前电平值的向量。* 电平值是相对于缓冲区中找到的峰值电平的标准化值,使得缓冲区中的最大值为1。** @param buffer QAudioBuffer对象,包含要分析的音频数据。* @return QVector<qreal> 包含每个通道电平值的向量。如果无法分析缓冲区,则返回空向量。*/QVector<qreal> getBufferLevels(const QAudioBuffer &buffer){QVector<qreal> values;// 如果缓冲区无效,则直接返回空向量if (!buffer.isValid())return values;// 检查音频格式是否有效,且是否为小端序if (!buffer.format().isValid() || buffer.format().byteOrder() != QAudioFormat::LittleEndian)return values;// 检查音频编解码器是否为PCMif (buffer.format().codec() != "audio/pcm")return values;int channelCount = buffer.format().channelCount();values.fill(0, channelCount);qreal peak_value = getPeakValue(buffer.format());// 如果无法计算峰值电平,则返回空向量if (qFuzzyCompare(peak_value, qreal(0)))return values;// 根据样本类型和大小,计算每个通道的电平值switch (buffer.format().sampleType()){case QAudioFormat::Unknown:case QAudioFormat::UnSignedInt:// 处理无符号整型样本,支持32位、16位和8位if (buffer.format().sampleSize() == 32)values = getBufferLevels(buffer.constData<quint32>(), buffer.frameCount(), channelCount);if (buffer.format().sampleSize() == 16)values = getBufferLevels(buffer.constData<quint16>(), buffer.frameCount(), channelCount);if (buffer.format().sampleSize() == 8)values = getBufferLevels(buffer.constData<quint8>(), buffer.frameCount(), channelCount);// 标准化电平值for (int i = 0; i < values.size(); ++i)values[i] = qAbs(values.at(i) - peak_value / 2) / (peak_value / 2);break;case QAudioFormat::Float:// 处理浮点型样本,支持32位if (buffer.format().sampleSize() == 32){values = getBufferLevels(buffer.constData<float>(), buffer.frameCount(), channelCount);// 标准化电平值for (int i = 0; i < values.size(); ++i)values[i] /= peak_value;}break;case QAudioFormat::SignedInt:// 处理有符号整型样本,支持32位、16位和8位if (buffer.format().sampleSize() == 32)values = getBufferLevels(buffer.constData<qint32>(), buffer.frameCount(), channelCount);if (buffer.format().sampleSize() == 16)values = getBufferLevels(buffer.constData<qint16>(), buffer.frameCount(), channelCount);if (buffer.format().sampleSize() == 8)values = getBufferLevels(buffer.constData<qint8>(), buffer.frameCount(), channelCount);// 标准化电平值for (int i = 0; i < values.size(); ++i)values[i] /= peak_value;break;}return values;}QAudioLevel::QAudioLevel(QWidget *parent): QWidget(parent){setMinimumHeight(15);setMaximumHeight(50);}void QAudioLevel::setLevel(qreal level){if (m_level != level){m_level = level;update();}}void QAudioLevel::paintEvent(QPaintEvent *event){Q_UNUSED(event);QPainter painter(this);// 渐变色QLinearGradient gradient(0, 0, width(), height());int hue = static_cast<int>(m_level * 360.0);gradient.setColorAt(m_level, QColor::fromHsl(hue, 255, 127));// 定义每个小矩形的间距const int padding = 1;// 定义总共有多少个小矩形const int numRects = 50;// 计算每个矩形的宽度qreal singleRectWidth = (width() - (numRects + 1) * padding) / numRects;// 使用m_level计算需要绘制多少个小矩形int activeRects = qRound(m_level * numRects);painter.setBrush(QBrush(gradient));for (int i = 0; i < activeRects; ++i){qreal rectLeft = i * (singleRectWidth + padding) + padding;QRectF rect(rectLeft, 0, singleRectWidth, height());painter.drawRect(rect);}// 绘制剩余的小矩形(不活跃部分)painter.setBrush(Qt::black);for (int i = activeRects; i < numRects; ++i){qreal rectLeft = i * (singleRectWidth + padding) + padding;QRectF rect(rectLeft, 0, singleRectWidth, height());painter.drawRect(rect);}}HistogramWidget::HistogramWidget(QWidget *parent) : QWidget(parent){setLayout(new QVBoxLayout);}HistogramWidget::~HistogramWidget(){}/*** 处理音频缓冲区,更新音频级别显示器。** @param buffer QAudioBuffer对象,包含待处理的音频数据。*/void HistogramWidget::processBuffer(const QAudioBuffer &buffer){// 检查音频级别计数是否与音频缓冲区的声道数匹配if (m_audioLevels.count() != buffer.format().channelCount()){// 如果不匹配,则删除现有音频级别对象,并根据声道数创建新的音频级别对象qDeleteAll(m_audioLevels);m_audioLevels.clear();for (int i = 0; i < buffer.format().channelCount(); ++i){QAudioLevel *level = new QAudioLevel(this);m_audioLevels.append(level);layout()->addWidget(level);}}// 计算音频缓冲区的级别并更新音频级别显示器QVector<qreal> levels = getBufferLevels(buffer);for (int i = 0; i < levels.count(); ++i){m_audioLevels.at(i)->setLevel(levels.at(i));}}void HistogramWidget::paintEvent(QPaintEvent *event){Q_UNUSED(event);if (!m_audioLevels.isEmpty())return;QPainter painter(this);painter.fillRect(0, 0, width(), height(), QColor::fromRgb(0, 0, 0));}

总结

  • 学习Qt demo中的Media Player Example改动而来,把音频图处理单独实现,并加以优化。

相关文章:

c++ QT 实现QMediaPlayer播放音频显示音频级别指示器

文章目录 效果图概述代码总结 效果图 概述 QMediaPlayer就不介绍了&#xff0c;就提供了一个用于播放音频和视频的媒体播放器 QAudioProbe 它提供了一个探针&#xff0c;用于监控音频流。当音频流被捕获或播放时&#xff0c;QAudioProbe 可以接收到音频数据。这个类在需要访问…...

失之毫厘差之千里之load和loads

起源 最近在读pandas库的一些文档的时候&#xff0c;顺便也会将文档上的一些demo在编辑器中进行运行测试&#xff0c;其中在读到pandas处理Json数据这一节的时候&#xff0c;我还是像往常一样&#xff0c;将文档提供的demo写一遍&#xff0c;结果在运行的时候&#xff0c;直接…...

element ui在移动端的适配问题

element ui在移动端的适配问题 问题1&#xff1a; 给el-table表头添加背景色&#xff0c;使用以下代码 :header-row-style“{ background: ‘linear-gradient(90deg, #0079FA 0%, #00C7DD 100%)’ }” 在安卓手机上显示正常&#xff0c;在ios手机上显示背景色添加到每一个th中…...

堆排序详细理解

目录 一、前备知识 二、建堆 2.2.1 向上调整算法建堆 2.2.2 向下调整算法建堆 三、排序 3.1 常见问题 3.2 思路 3.3 源码 一、前备知识 详细图解请点击&#xff1a;二叉树的顺序实现-堆-CSDN博客 本文只附上向上/向下调整算法的源码 //交换 void Swap(int* p, int* …...

RK3588+FPGA+AI高性能边缘计算盒子,应用于视频分析、图像视觉等

搭载RK3588&#xff08;四核 A76四核 A55&#xff09;&#xff0c;CPU主频高达 2.4GHz &#xff0c;提供1MB L2 Cache 和 3MB L3 &#xff0c;Cache提供更强的 CPU运算能力&#xff0c;具备6T AI算力&#xff0c;可扩展至38T算力。 产品规格 系统主控CPURK3588&#xff0c;四核…...

07-操作元素(键盘和鼠标事件)

在前面的文章中重点介绍了一些元素的定位方法&#xff0c;定位到元素后&#xff0c;就需要操作元素了。本篇总结了web页面常用的一些操作元素方法&#xff0c;可以统称为行为事件。 一、简单操作 点击按钮&#xff08;鼠标左键&#xff09;&#xff1a;click()清空输入框&…...

3389,为了保障3389端口的安全,我们可以采取的措施

3389端口&#xff0c;作为远程桌面协议&#xff08;RDP&#xff09;的默认端口&#xff0c;广泛应用于Windows操作系统中&#xff0c;以实现远程管理和控制功能。然而&#xff0c;正因为其广泛使用&#xff0c;3389端口也成为许多潜在安全威胁的入口。因此&#xff0c;确保3389…...

Java集合【超详细】2 -- Map、可变参数、Collections类

文章目录 一、Map集合1.1 Map集合概述和特点【理解】1.2 Map集合的基本功能【应用】1.3 Map集合的获取功能【应用】1.4 Map集合的两种遍历方式 二、HashMap集合2.1 HashMap集合概述和特点【理解】2.2 HashMap的组成、构造函数2.3 put、查找方法2.4 HashMap集合应用案例【应用】…...

最佳 Mac 数据恢复:恢复 Mac 上已删除的文件

尝试过许多 Mac 数据恢复工具&#xff0c;但发现没有一款能达到宣传的效果&#xff1f;我们重点介绍最好的 Mac 数据恢复软件 没有 Mac 用户愿意担心数据丢失&#xff0c;但您永远不知道什么时候会发生这种情况。无论是意外删除 Mac 上的重要文件、不小心弄湿了 Mac、感染病毒…...

芋道系统,springboot+vue3+mysql实现地址的存储与显示

1.效果图 2.前端实现&#xff1a; <el-form-item label"地址" prop"entrepriseAddress"><el-cascaderv-model"formData.entrepriseAddress"size"large":options"region"/></el-form-item> //导入组件 im…...

【C++】C++11新特性:列表初始化、声明、新容器、右值引用、万能引用和完美转发

目录 一、列表初始化 1.1 { } 初始化 1.2 std::initializer_list 二、声明 2.1 auto 2.2 decltype 2.3 nullptr 三、新容器 四、右值引用和移动语义 4.1 左值和左值引用 4.2 右值和右值引用 4.3 左值引用与右值引用比较 4.4 右值引用使用场景和意义&#xff1a;移…...

【IB Protocal Serial--WQE】

IB Protocal Serial--WQE 1 Intro1.1 What1.2 IBA WQE 本系列文章介绍RDMA技术的具体实现–InfiniBand Protocal&#xff1b; Introduce the features, capalities,components, and elements of IBA. the principles of operation. 1 Intro 1.1 What 理解IB协议下面这三句话对…...

C++ 混合运算的类型转换

一 混合运算和隐式转换 257 整型2 浮点5 行吗&#xff1f;成吗&#xff1f;中不中&#xff1f; C 中允许相关的数据类型进行混合运算。 相关类型。 尽管在程序中的数据类型不同&#xff0c;但逻辑上进行这种运算是合理的相关类型在混合运算时会自动进行类型转换&#xff0c;再…...

线性时间选择

给定线性序集中n个元素和一个整数k&#xff0c;1≤k≤n&#xff0c;要求找出这n个元素中第k小的元素 #include<iostream> #include<cstdlib> #include<time.h> using namespace std; int a[100]; int Random(int left,int right) {srand(time(NULL));return …...

【对算法期中卷子的解析和反思】

一、程序阅读并回答问题&#xff08;共30分&#xff09; #include<cstdio>#include<cstring>#include<iostream>using namespace std;char chess[10][10];int sign[10];int n, k, ans;void dfs(int x, int k) { if (k 0){ans;return; } if (xk-1 >…...

sudo apt update sudo: apt: command not found

CentOS或RHEL&#xff08;Red Hat Enterprise Linux&#xff09;系统上&#xff0c;包管理器是yum或dnf&#xff0c;而不是apt。您可以使用yum或dnf来安装软件包。以下是如何在CentOS或RHEL上安装Git的详细步骤&#xff1a; 1. 使用yum安装Git 首先&#xff0c;更新软件包列表&…...

ios:文本框默认的copy、past改成中文复制粘贴

问题 ios 开发&#xff0c;对于输入框的一些默认文案展示&#xff0c;如复制粘贴是英文的&#xff0c;那么如何改为中文的呢 解决 按照路径找到这个文件 ios/项目/Info.plist&#xff0c;增加 <key>CFBundleAllowMixedLocalizations</key> <true/> <…...

Qt moc系统的黑魔法?

Qt的元对象系统&#xff08;Meta-Object System&#xff09;是Qt框架的核心功能之一&#xff0c;为C语言增加了一些动态特性&#xff0c;借助元对象系统Qt可以实现以下功能 信号与槽机制&#xff08;Signals and Slots&#xff09;运行时类型信息&#xff08;Run-Time Type In…...

MyBatis开发中常用总结

文章目录 常用MyBatis参数映射单个参数多个参数使用索引【不推荐】Param注解Map传参POJO【推荐】List数组 动态标签\<if>标签\<trim>标签\<where>标签\<set>标签\<foreach>标签 MyBatis查询一对一一对多 常用MyBatis参数映射 单个参数 XML中可…...

Git基本使用教程(学习记录)

参考文章链接&#xff1a; Git教程&#xff08;超详细&#xff0c;一文秒懂&#xff09; RUNOOB Git教程 Git学习记录 1Git概述 1.1版本控制软件功能 版本管理&#xff1a;更新或回退到历史上任何版本&#xff0c;数据备份共享代码&#xff1a;团队间共享代码&#xff0c;…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...