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

news2024/11/25 7:28:24

文章目录

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

效果图

在这里插入图片描述


概述

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络之快重传和快恢复以及TCP连接与释放的握手

快重传和快恢复 快重传可以让发送方尽早得知丢失消息&#xff0c; 当发送消息M1,M2&#xff0c;M3,M4,M5后,假如消息M2丢失&#xff0c;那么按照算法会发送对M2报文前一个报文M1的重复确认&#xff08;M1正常接受到&#xff0c;已经发送了确认),然后之后收到M4,M5,也会发送两…

Linux网络编程:传输层协议|UDP|TCP

知识引入&#xff1a; 端口号&#xff1a; 当应用层获得一个传输过来的报文时&#xff0c;这时数据包需要知道&#xff0c;自己应该送往哪一个应用层的服务&#xff0c;这时就引入了“端口号”&#xff0c;通过区分同一台主机不同应用程序的端口号&#xff0c;来保证数据传输…

el-date-picker选择开始日期的近半年

<el-date-pickerv-model"form[val.key]":type"val.datePickerType || daterange":clearable"val.clearable && true"range-separator"~"start-placeholder"开始日期"end-placeholder"结束日期"style&q…

DataRockMan洛克先锋OZON选品工具

随着全球电子商务的飞速发展&#xff0c;跨境电商平台已成为越来越多企业和个人追逐市场红利的重要战场。在众多跨境电商平台中&#xff0c;OZON以其独特的市场定位和强大的用户基础&#xff0c;吸引了无数卖家的目光。然而&#xff0c;如何在OZON平台上成功选品&#xff0c;成…

结构体(C保姆级讲解)

前言&#xff1a; 为什么会有结构体&#xff0c;结构体可以用来面熟一个复杂对象&#xff0c;我们知道C语言中有哪些数据类型&#xff0c;有整型&#xff0c;有浮点型&#xff0c;有字符型&#xff0c;但是在生活中&#xff0c;我们需要描述一些比较复杂的东西&#xff0c;比如…

java调用科大讯飞离线语音合成SDK --内附完整项目

科大讯飞语音开放平台基础环境搭建 1.用户注册 注册科大讯飞开放平台账号 2.注册好后先创建一个自己的应用 创建完成后进入应用选择离线语音合成&#xff08;普通版&#xff09;可以看到我们开发需要的SDK,选择windows MSC点击下载。 3.选择你刚刚创建的应用&#xff0c;选择…

磁盘配额的具体操作

磁盘配额&#xff1a; linux的磁盘空间有两个方面&#xff1a;第一个是物理空间&#xff0c;也就是磁盘的容量 第二个inode号耗尽&#xff0c;也无法写入 linux根分区&#xff1a;根分区的空间完全耗尽&#xff0c;服务程序崩溃&#xff0c;系统也无法启动了。 为了防止有人…

Vue2 + Element UI 封装 Table 递归多层级列表头动态

1、在 components 中创建 HeaderTable 文件夹&#xff0c;在创建 ColumnItem.vue 和 index.vue。 如下&#xff1a; 2、index.vue 代码内容&#xff0c;如下&#xff1a; <template><div><el-table:data"dataTableData"style"width: 100%"…

神经网络与深度学习——第3章 线性模型

本文讨论的内容参考自《神经网络与深度学习》https://nndl.github.io/ 第3章 线性模型 线性模型 线性模型&#xff08;Linear Model&#xff09;是机器学习中应用最广泛的模型&#xff0c;指通过样本特征的线性组合来进行预测的模型&#xff0c;给定一个 D D D维样本 x [ x …

定时器与PWM的LED控制

目录 一、基础概念定时器定时器类型定时器特性 PWM定义占空比原理 二、实验1.LED周期性亮灭定时器TIM2配置GPIO引脚设置工程相关参数配置Keil编写程序 2.LED呼吸灯(PWM)呼吸灯原理Keil编写程序Keil虚拟示波器&#xff0c;观察 PWM输出波形设置点击setup&#xff0c;并设置观察引…

贪心算法拓展(反悔贪心)

相信大家对贪心算法已经见怪不怪了&#xff0c;但是一旦我们的决策条件会随着我们的步骤变化&#xff0c;我们该怎么办呢&#xff1f;有没有什么方法可以反悔呢&#xff1f; 今天就来讲可以后悔的贪心算法&#xff0c;反悔贪心。 https://www.luogu.com.cn/problem/CF865Dhttp…

[图的搜索]5.图解狄克斯特拉算法及其代码演示

狄克斯特拉算法 与前面提到的贝尔曼-福特算法类似&#xff0c;狄克斯特拉&#xff08;Dijkstra&#xff09;算法也是求解最短路径问题的算法&#xff0c;使用它可以求得从起点到终点的路径中权重总和最小的那条路径路径。 图解 01 这里我们设A为起点、G为终点&#xff0c;来讲…

“揭秘乐园通行证:Spring JWT的魔法之旅

嗨&#xff0c;我将带你深入了解如何利用JWT打造一个既安全又高效的网络乐园。从基础概念到实战技巧&#xff0c;再到安全策略&#xff0c;每一步都充满惊喜。你将学会如何为乐园设置无状态的门票系统&#xff0c;如何通过RBAC和ABAC确保游客安全&#xff0c;以及如何在微服务架…

统计信号处理-匹配滤波器实现与验证(matlab仿真)

什么是匹配滤波器 匹配滤波器是一种信号处理技术&#xff0c;它用于从噪声中提取信号&#xff0c;特别是在信号与噪声比率较低的情况下。匹配滤波器之所以存在&#xff0c;是因为它在信号检测和估计方面具有几个关键的优势&#xff1a; 最大化信噪比&#xff1a;匹配滤波器设计…

数字化校园建设让学习更加广阔

校园构建数字化校园的亮点是什么&#xff1f;校园以智能服务、才智办理、数字讲堂为中心内容的智慧校园建造&#xff0c;不只使师生作业和日子更高效&#xff0c;并且使他们有更多的时刻投入到智能教育和智能学习中去&#xff0c;进步教育质量&#xff0c;使学生走出校门时紧跟…

项目管理主要文档介绍

1、商业论证&#xff1a;一般由项目发起人创建&#xff0c;用于论证项目是否对组织有财务方面的收益。商业论证创建于项日开始之前&#xff0c;用于判断项目是否需要被开展。 2、项目章程&#xff1a;一般由项日经理创建,并由发起入和关键相关力提供输人&#xff0c;最后经项目…

MATLAB函数模块光显示zeros/poles怎么办?

出现下面这种图了怎么办&#xff1f;是做错了吗&#xff1f; 这种图就是它显示不完整了&#xff0c;把它拉大点就可以完全显示了。

【机器学习】深入探索机器学习:利用机器学习探索股票价格预测的新路径

❀机器学习 &#x1f4d2;1. 引言&#x1f4d2;2. 多种机器学习算法的应用&#x1f4d2;3. 机器学习在股票价格预测中的应用现状&#x1f389;数据收集与预处理&#x1f389;模型构建与训练&#x1f308;模型评估与预测&#x1f31e;模型评估&#x1f319;模型预测⭐注意事项 &…

检定记录内容解析:非红外二氧化硫气体检测仪的维护与验证

在工业生产与环境保护中&#xff0c;二氧化硫作为一种常见的有害气体&#xff0c;其浓度的监测和控制显得尤为重要。 非红外二氧化硫气体检测仪以其独特的检测原理和高灵敏度&#xff0c;在二氧化硫监测领域发挥着不可或缺的作用。 在这篇文章中&#xff0c;佰德将详细介绍非…

【原创】springboot+mysql校园通讯录管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…