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改动而来,把音频图处理单独实现,并加以优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/337313.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java面试】六、Spring框架相关

文章目录 1、单例Bean不是线程安全的2、AOP3、Spring中事务的实现4、Spring事务失效的场景4.1 情况一&#xff1a;异常被捕获4.2 情况二&#xff1a;抛出检查异常4.3 注解加在非public方法上 5、Bean的生命周期6、Bean的循环引用7、Bean循环引用的解决&#xff1a;Spring三级缓…

结构体相关习题的补充

结构体相关习题的补充 题目1&#xff1a; 如有以下代码&#xff1a; struct student {int num;char name[32];float score; }stu;则下面的叙述不正确的是&#xff1a;( ) A.struct 是结构体类型的关键字 B.struct student 是用户定义的结构体类型 C.num, score 都是结构体…

Python中Web开发-Django框架

大家好&#xff0c;本文将带领大家进入 Django 的世界&#xff0c;探索其强大的功能和灵活的开发模式。我们将从基础概念开始&#xff0c;逐步深入&#xff0c;了解 Django 如何帮助开发人员快速构建现代化的 Web 应用&#xff0c;并探讨一些最佳实践和高级技术。无论是初学者还…

身份认证与口令攻击

身份认证与口令攻击 身份认证身份认证的五种方式口令认证静态口令动态口令(一次性口令)动态口令分类 密码学认证一次性口令认证S/KEY协议改进的S/KEY协议 其于共享密钥的认证 口令行为规律和口令猜测口令规律口令猜测 口令破解操作系统口令破解Windows密码存储机制Windows密码破…

数据结构-堆排序问题

需要在数组里面进行排序&#xff0c;我们可以采取堆排序对其解决问题 版本1&#xff1a; 创建一个数组等大的堆&#xff0c;把数组里面的数值输入到堆里面进行堆排序&#xff0c;但是这样的弊端就是&#xff0c;不是顺序排序 版本2&#xff1a; 每次我们取堆顶然后打印&#xf…

举个栗子!Tableau 技巧(275):散点图的数值重合怎么办?抖动图来咯

散点图是大家经常使用的分析图表&#xff0c;但是如果出现多个数据点具有完全相同的 X 和 Y 值&#xff0c;多个散点重叠并隐藏后&#xff0c;查看数据就很不方便了。 遇到这种情况&#xff0c;该怎么办&#xff1f;其实可以尝试将数据点稍微抖动一下&#xff01;如下图&#…

MT3045 松鼠接松果

思路&#xff1a; 求x的一个区间&#xff0c;使区间中的松果的最大y坐标和最小y坐标的差至少为D。若有多个区间&#xff0c;则取最小的那个。 即使用单调队列不断维护最大值和最小值。 首先L固定不动&#xff0c;R不断右移&#xff1a; 即若函数f(R)max[L,R]-min[L,R] >…

探秘Flask中的表单数据处理

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、Flask中的表单处理机制 三、Flask表单处理实战 四、处理表单数据的注意事项…

万字解析线控底盘技术

文章出处&#xff1a;汽车学堂Automooc 引言 在当今这个由科技驱动的时代&#xff0c;汽车电动化、智能化已成为汽车行业的热门话题。特斯拉的自动驾驶功能、蔚来的换电模式、以及比亚迪的刀片电池技术&#xff0c;这些创新不仅引领着市场趋势&#xff0c;也推动着消费者对智…

Java常用API(三)

一、Arrays类 1.定义 Arrays是一个用于操作数组的工具类。 2.常用方法 1.toString方法 public class Demo {public static void main(String[] args) {//toString 将数组变成字符串int[] arr {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};System.out.println(Arrays.toString(arr));…

DNS 解析过程

文章目录 简介特点查询方式⚡️1. 浏览器缓存2. 系统缓存&#xff08;hosts文件&#xff09;3. 路由器缓存4. 本地域名服务器5. 根域名服务器6. 顶级域名服务器7. 权限域名服务器8. 本地域名服务器缓存并返回9. 操作系统缓存并返回10. 浏览器缓存并访问流程图 总结 简介 DNS&a…

springboot2+mybatis-plus+vue3创建入门小项目[学生管理系统]02[实战篇]

01学习篇 创建一个 vue 项目 创建这个新的文件夹 创建前端项目 eggbox 数据库 SQL CREATE DATABASE IF NOT EXISTS egg DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; USE egg;CREATE TABLE stu (id INT AUTO_INCREMENT, -- 自增主键name VARCHAR(64) NOT NULL, -- 非空…

如何使用前端表格控件实现多数据源整合?

前言 作为表格产品的典型应用场景之一&#xff0c;几乎所有的行业都会存在类 Excel 报表开发这样的应用场景&#xff0c;而在这些应用场景中&#xff0c;经常会遇见下面的这些痛点&#xff1a; 报表数据往往来自多个不同的数据源&#xff0c;需要报表系统能够同时连接多个数据源…

反VC情绪:加密市场需要新的分布式代币发行方式

GME事件 GME事件反应了社交媒体在金融决策中的影响力&#xff0c;散户投资者群体通过集体行动&#xff0c;改变了很多人对股市的看法和参与方式。 GME事件中&#xff0c;meme扮演了核心角色。散户投资者使用各种meme来沟通策略、激励持股行为&#xff0c;创造了一种反对华尔街…

5. MySQL运算符和函数

文章目录 【 1. 算术运算符 】【 2. 逻辑运算符 】2.1 逻辑非 (NOT 或者 !)2.2 逻辑与运算符 (AND 或者 &&)2.3 逻辑或 (OR 或者 ||)2.4 异或运算 (XOR) 【 3. 比较运算符 】3.1 等于 3.2 安全等于运算符 <>3.3 不等于运算符 (<> 或者 !)3.4 小于等于运算符…

AdroitFisherman模块安装日志(2024/5/31)

安装指令 pip install AdroitFisherman-0.0.29.tar.gz -v 安装条件 1:Microsoft Visual Studio Build Tools 2:python 3.10.x 显示输出 Using pip 24.0 from C:\Users\12952\AppData\Local\Programs\Python\Python310\lib\site-packages\pip (python 3.10) Processing c:\u…

QT加载CAD文件(二)LibreCAD源码编译

一、LibreCAD LibreCAD是一个开源软件&#xff0c;不用破解激活&#xff0c;可以打开编辑DXF格式的文档&#xff0c;软件大小只有二十多M&#xff0c;对于一些比较简单的图纸还是可以胜任的。本文主要讲该软件源码编译。如果了解软件的基本使用可以参考https://blog.csdn.net/…

参数高效微调PEFT(一)快速入门BitFit、Prompt Tuning、Prefix Tuning

参数高效微调PEFT(一)快速入门BitFit、Prompt Tuning、Prefix Tuning 目前&#xff0c;模型最全的网站是HuggingFace&#xff0c;但是国内需要魔法流量才能访问。另外&#xff0c;现在大模型权重文件都较大&#xff0c;也会浪费不少流量&#xff0c;因此这里推荐使用魔搭社区下…

一文学懂Base64编码原理

前言 Base64编码与ASCII编码一样&#xff0c;也是一种编码方式。不同的是ASCII码采用7位二进制数表示&#xff08;包括大小写字母、数字、标点符号和一些不可见字符&#xff09;&#xff0c;而Base64采用6位二进制数表示&#xff08;包括大小写字母、0~9数字、和/&#xff09;…

Java | Leetcode Java题解之第120题三角形最小路径和

题目&#xff1a; 题解&#xff1a; class Solution {public int minimumTotal(List<List<Integer>> triangle) {int n triangle.size();int[] f new int[n];f[0] triangle.get(0).get(0);for (int i 1; i < n; i) {f[i] f[i - 1] triangle.get(i).get(i…