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改动而来,把音频图处理单独实现,并加以优化。
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Qt和SDL是两种不同的程序库,均可用于实现音频播放。下面分别介绍一下在这两种程序库中实现音频播放的方法: Qt通过QMediaPlayer类来实现音频播放。QMediaPlayer类是Qt Multimedia模块的一部分,它可以播放各种媒体文件,包括音频和视频。在使用QMediaPlayer类时,需要先创建一个QMediaPlayer对象,然后调用setMedia方法设置音频文件的路径,最后调用play方法来开始播放音频文件。例如,以下代码可以播放一个MP3文件: ```c++ #include <QtMultimedia/QMediaPlayer> int main(int argc, char *argv[]) { QMediaPlayer player; player.setMedia(QUrl::fromLocalFile("/path/to/file.mp3")); player.play(); return 0; } ``` SDL也提供了音频播放功能。具体来说,可以通过SDL的音频子系统来实现。在使用SDL的音频子系统时,需要先调用SDL_Init函数初始化音频子系统,然后设置音频参数,注册回调函数,最后在回调函数中处理音频数据。以下是一个简单的示例代码,可以播放WAV文件: ```c #include <SDL2/SDL.h> // 回调函数,用于处理音频数据 void audio_callback(void *userdata, Uint8 *stream, int len) { static Uint32 pos = 0; // 当前播放位置 SDL_memcpy(stream, userdata + pos, len); pos += len; } int main(int argc, char *argv[]) { SDL_Init(SDL_INIT_AUDIO); SDL_AudioSpec wav_spec; Uint8 *wav_data; Uint32 wav_length; // 加载WAV文件 SDL_LoadWAV("/path/to/file.wav", &wav_spec, &wav_data, &wav_length); // 设置音频参数 SDL_AudioSpec want, have; SDL_memset(&want, 0, sizeof(want)); want.freq = wav_spec.freq; want.format = AUDIO_S16SYS; want.channels = wav_spec.channels; want.samples = 1024; want.callback = audio_callback; want.userdata = wav_data; // 打开音频设备并开始播放 SDL_OpenAudio(&want, &have); SDL_PauseAudio(0); // 等待音频播放结束 SDL_Delay(wav_length/wav_spec.freq*1000); // 关闭音频设备和释放资源 SDL_CloseAudio(); SDL_FreeWAV(wav_data); return 0; } ``` 综上所述,无论是Qt还是SDL都可以实现音频播放。要根据具体情况选择适合自己的程序库和方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值