QT 音乐播放器【一】 显示音频级别指示器

文章目录

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

效果图

在这里插入图片描述


概述

  • 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);
  
      // 检查音频编码是否为PCM
      if (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;
      }
  
      // 如果没有匹配到任何已知情况,返回0
      return 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;
  
      // 检查音频编解码器是否为PCM
      if (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改动而来,把音频图处理单独实现,并加以优化。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python下绘制地形晕染(shading)图

python可以利用rasterio&#xff0c;cartopy&#xff0c;matplotlib等库绘制地形晕染图。 1.获取高程数据 高程数据可以从GEBCO网站下载&#xff1a;&#xff08;https://www.gebco.net/data_and_products/gridded_bathymetry_data/&#xff09;。 选择raster&#xff08;栅…

flask 之JWT认证实现

目录 1、JWT 1.1、JWT概述 1.2、token的生成 1.3、token校验 1.4、flask项目中实现JWT认证 1、JWT 1.1、JWT概述 JWT&#xff08;JSON Web Token&#xff09;是一种用于身份验证和授权的开放标准。它由三部分组成&#xff0c;分别是头部、负载和签名。 头部&#xff0…

24、Linux网络端口

Linux网络端口 1、查看网络接口信息ifconfig ens33 eth0 文件 ifconfig 当前设备正在工作的网卡&#xff0c;启动的设备。 ifconfig -a 查看所有的网络设备。 ifconfig ens33 查看指定网卡设备。 ifconfig ens33 up/down 对指定网卡设备进行开关 基于物理网卡设备虚拟的…

搭建 3D 智慧农场可视化

运用图扑自主研发的 HT 产品&#xff0c;全程零代码搭建 3D 轻量化 Low Poly 风格的智慧农场可视化解决方案&#xff0c;无缝融合 2D、3D 技术&#xff0c;1&#xff1a;1 还原农场的区域规划&#xff0c;展开对农作物间的网格化管理。

真国色码上赞,科技流量双剑合璧,商家获客新纪元开启

在数字化浪潮汹涌的今天,真国色研发团队依托红玉房网络科技公司的雄厚实力,凭借科技领先的核心竞争力,推出了创新性的商家曝光引流工具——码上赞。这款工具借助微信支付与视频号已有功能,为实体商家提供了一种全新的引流获客方式,实现了科技与商业的完美融合。 科技领先,流量黑…

MATLAB format

在MATLAB中&#xff0c;format 是一个函数&#xff0c;用于控制命令窗口中数值的显示格式。这个函数可以设置数值的精度、显示的位数等。以下是一些常用的 format 命令&#xff1a; format long&#xff1a;以默认的长格式显示数值&#xff0c;通常显示15位有效数字。format s…

图像处理之基于标记的分水岭算法(C++)

图像处理之基于标记的分水岭算法&#xff08;C&#xff09; 文章目录 图像处理之基于标记的分水岭算法&#xff08;C&#xff09;前言一、基于标记点的分水岭算法应用1.实现步骤&#xff1a;2.代码实现 总结 前言 传统分水岭算法存在过分割的不足&#xff0c;OpenCV提供了一种…

CTFHUB-密码口令-弱口令

目录 题干介绍 密码字典 找flag过程 尾声 题干介绍 通常认为容易被别人&#xff08;他们有可能对你很了解&#xff09;猜测到或被破解工具破解的口令均为弱口令。 密码字典 下载地址&#xff1a;GitHub - NepoloHebo/Commonly-used-weak-password-dictionary: 常用弱密码字…

川北医学院与爱尔眼科医院集团签署战略合作协议共谋医学发展新篇章

为深入贯彻落实党的二十大精神&#xff0c;统筹校、企、医、政多方资源&#xff0c;服务“健康中国”战略&#xff0c;推动眼健康产业发展&#xff0c;打造国家及区域级眼科医学中心&#xff0c;2024年5月31日&#xff0c;川北医学院与爱尔眼科医院集团在成都举行战略合作协议签…

腾讯云 TDMQ for Apache Pulsar 多地区高可用容灾实践

作者介绍 林宇强 腾讯云高级工程师 专注于消息队列、API网关、微服务、数据同步等 PaaS 领域。有多年的开发和维护经验&#xff0c;目前在腾讯云从事 TDMQ Pulsar 商业化产品方向的研发工作。 导语 本文将从四个维度&#xff0c;深入剖析 Pulsar 在多可用区高可用领域的容…

单实例11.2.0.4迁移到11.2.0.4RAC_使用rman异机恢复

保命法则&#xff1a;先备份再操作&#xff0c;磁盘空间紧张无法备份就让满足&#xff0c;给自己留退路。 场景说明&#xff1a; 1.本文档的环境为同平台、不同版本&#xff08;操作系统版本可以不同&#xff0c;数据库版本相同&#xff09;&#xff0c;源机器和目标机器部分…

QML信号连接到c++的槽函数(五)

文章目录 前言一、QML Signal and Handler Event System二、QML信号连接到c++的槽函数代码实例1. 创建一个QML 工程2. 用C++ 实现一个QML Types3. 代码实例4. 运行结果总结参考资料前言 本文主要介绍,如何将QML 中的信号连接到C++ 中的槽函数 软硬件环境: 硬件:PC 软件:wi…

MDK5.10 安装手册

1.MDK5.10 安装 打开开发板光盘&#xff1a; 6 &#xff0c;软件资料 \ 软件 \MDK5 &#xff0c;双击 mdk_510.exe &#xff0c;进行安装。这里我们 将其安装到 D 盘&#xff0c; MDK5.10 文件夹下&#xff0c;需要设置安装路径&#xff0c;如图 1.1 所示&#xff1a; …

上传图片并显示#Vue3#后端接口数据

上传图片并显示#Vue3#后端接口数据 效果&#xff1a; 上传并显示图片 代码&#xff1a; <!-- 上传图片并显示 --> <template><!-- 上传图片start --><div><el-form><el-form-item><el-uploadmultipleclass"avatar-uploader&quo…

独立游戏开发的 6 个步骤

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

计算机网络⑩ —— Linux系统如何收发网络包

转载于小林coding&#xff1a;https://www.xiaolincoding.com/network/1_base/how_os_deal_network_package.html 1. OSI七层模型 应用层&#xff0c;负责给应用程序提供统一的接口&#xff1b;表示层&#xff0c;负责把数据转换成兼容另一个系统能识别的格式&#xff1b;会话…

【Python】 如何将 datetime 转换为 date?

基本原理 在 Python 中&#xff0c;我们经常需要处理日期和时间。datetime 模块提供了丰富的功能来处理日期和时间。datetime 类型和 date 类型是 datetime 模块中的两个不同的类型。datetime 类型包含了日期和时间的信息&#xff0c;而 date 类型只包含日期信息。 当你需要将…

运筹学_7.博弈论(对策略)

文章目录 引言7.1 博弈论(对策论)的基本概念对策论有三个基本假设对策论的三个要素零和对策二人有限零和对策 7.2 矩阵对策矩阵对策数学模型 7.3 最优纯策略基本定理和性质最优纯策略基本定理最优纯策略基本性质 7.4 混合策略定义和性质混合策略的定义混合策略的性质 7.5 矩阵对…

德国RS SMA100A原装二手sma100a信号发生器6G

罗德与施瓦茨 SMA100A信号发生器&#xff0c;9 kHz 至 3 GHz 或 6 GHz R&S SMA100A 提供信号质量、速度和灵活性。R&S SMA100A 是一款高级模拟发生器&#xff0c;因其出色的特性而树立了标准。 它结合了卓越的信号质量和极高的设置速度。无论是在开发、生产、服务还是维…

GSEA的算法只考虑排序吗

其实这个问题很好回答&#xff0c;只需要运行如下代码&#xff0c;如下的基因列表是顺序是完全相同&#xff0c;并且我们只是做了最基础的变换 library(clusterProfiler) library(org.Hs.eg.db)data(geneList, package"DOSE")ego1 <- gseGO(geneList geneLi…