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

QT QML实现音频波形图进度条,可点击定位或拖动进度

前言

本项目实现了使用QT QML创建一个音频波形图进度条的功能。用户可以在界面上看到音频波形图,并且可以点击进度条上的位置进行定位,也可以拖动进度条来调整播放进度。可以让用户更方便地控制音频的播放进度,并且通过音频波形图可以直观地了解音频的节奏和节奏变化,为音频播放功能增添了更多的交互性和用户体验。

效果图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

正文

本文使用QAudioDecoder进行音频解码,将解码数据计算后存储到数组中,解码完成后统一在QML中进行绘制。

关键代码:

#include "audiowaveform.h"
#include <QDebug>
#include <QUrl>
#include <QTime>

AudioWaveform::AudioWaveform(QObject *parent)
    : QObject(parent)
    , m_decoder(new QAudioDecoder(this))
    , m_sampleCount(0)
{
    connect(m_decoder, &QAudioDecoder::bufferReady,
            this, &AudioWaveform::handleBufferReady);
    connect(m_decoder, &QAudioDecoder::finished,
            this, &AudioWaveform::handleFinished);
    connect(m_decoder, QOverload<QAudioDecoder::Error>::of(&QAudioDecoder::error),
            this, &AudioWaveform::handleError);
}

AudioWaveform::~AudioWaveform()
{
    m_decoder->stop();
}

QString AudioWaveform::source() const
{
    return m_source;
}

void AudioWaveform::setSource(const QString &source)
{
    if (m_source != source) {
        m_source = source;
        emit sourceChanged();
        processAudioFile();
    }
}

QVector<qreal> AudioWaveform::waveformData() const
{
    return m_waveformData;
}

void AudioWaveform::processAudioFile()
{
    clearWaveformData();
    
    if (m_source.isEmpty()) {
        return;
    }

    m_decoder->setSourceFilename(m_source);
    
    QAudioFormat desiredFormat;
    desiredFormat.setChannelCount(1);
    desiredFormat.setCodec("audio/pcm");
    desiredFormat.setSampleRate(SAMPLE_RATE);
    desiredFormat.setSampleSize(16);
    desiredFormat.setSampleType(QAudioFormat::SignedInt);
    
    m_decoder->setAudioFormat(desiredFormat);
    m_decoder->start();
    qDebug() <<__FUNCTION__<< __LINE__<< QTime::currentTime().toString("hh:mm:ss.zzz");
}

void AudioWaveform::handleBufferReady()
{
    QAudioBuffer buffer = m_decoder->read();
    if (!buffer.isValid())
        return;

    const qint16 *data = buffer.constData<qint16>();
    int sampleCount = buffer.sampleCount();

    // 计算这个缓冲区的最大振幅
    qreal maxAmplitude = 0;
    for (int i = 0; i < sampleCount; ++i) {
        qreal amplitude = qAbs(data[i]) / 32768.0; // 将16位整数转换为0-1范围
        maxAmplitude = qMax(maxAmplitude, amplitude);
    }

    m_waveformData.append(maxAmplitude);
}

void AudioWaveform::handleFinished()
{
    qDebug() <<__FUNCTION__<< __LINE__ << QTime::currentTime().toString("hh:mm:ss.zzz");
    // 对波形数据进行重采样,使其具有固定的点数
//    if (m_waveformData.size() > WAVEFORM_POINTS) {
//        QVector<qreal> resampledData;
//        resampledData.reserve(WAVEFORM_POINTS);
        
//        qreal step = m_waveformData.size() / static_cast<qreal>(WAVEFORM_POINTS);
//        for (int i = 0; i < WAVEFORM_POINTS; ++i) {
//            int index = static_cast<int>(i * step);
//            resampledData.append(m_waveformData.at(index));
//        }
//        m_waveformData = resampledData;
//    }

    emit waveformDataChanged();
    emit waveformProcessingFinished();
}

void AudioWaveform::handleError(QAudioDecoder::Error error)
{
    QString errorMessage;
    switch (error) {
        case QAudioDecoder::NoError:
            return;
        case QAudioDecoder::ResourceError:
            errorMessage = "Resource error";
            break;
        case QAudioDecoder::FormatError:
            errorMessage = "Format error";
            break;
        case QAudioDecoder::AccessDeniedError:
            errorMessage = "Access denied error";
            break;
        case QAudioDecoder::ServiceMissingError:
            errorMessage = "Service missing error";
            break;
        default:
            errorMessage = "Unknown error";
    }
    
    emit this->error(errorMessage);
}

void AudioWaveform::clearWaveformData()
{
    m_waveformData.clear();
    m_sampleCount = 0;
    emit waveformDataChanged();
}

波形绘制部分:

Canvas {
       id: waveformCanvas
       anchors.fill: parent
       anchors.margins: 2

       onPaint: {
           var ctx = getContext("2d");
           var width = waveformCanvas.width;
           var height = waveformCanvas.height;

           // 清除画布
           ctx.clearRect(0, 0, width, height);

           // 如果没有波形数据,直接返回
           if (!waveformModel || waveformModel.length === 0) return;

           // 设置波形样式
           ctx.strokeStyle = "#4a90e2";
           ctx.lineWidth = 2;

           // 计算每个数据点的宽度
           var pointWidth = width / waveformModel.length;

           // 绘制波形
           ctx.beginPath();
           waveformModel.forEach(function(amplitude, index) {
               var x = index * pointWidth;
               var centerY = height / 2;
               var waveHeight = amplitude * (height * 0.8);

               ctx.moveTo(x, centerY - waveHeight / 2);
               ctx.lineTo(x, centerY + waveHeight / 2);
           });
           ctx.stroke();

           // 绘制已播放部分的遮罩
           if (duration > 0) {
               var progress = currentPosition / duration;
               ctx.fillStyle = "rgba(74, 144, 226, 0.3)";
               ctx.fillRect(0, 0, width * progress, height);
           }
       }
}

本文Demo下载

相关文章:

  • 单目3d detection算法记录
  • 24集《不负美食不负卿》联合出品制作签约仪式成功举行
  • 【运维自动化-标准运维】如何实现一个最简单的流程编排
  • 【Redis】Redis的数据删除(过期)策略,数据淘汰策略。
  • [Nowruz 1404] 2025 Crypto/PWN部分
  • 三月九次前端面试复盘:当场景题成为通关密钥
  • 使用 EchoAPI 实现 API 断言的全面指南
  • vulhub/Billu_b0x靶机----练习攻略
  • c盘清理宝藏小工具
  • 使用Trainer传入自定义的compute_metrics函数时,oom报错
  • Diffusion Transformers (DiTs) - 用Transformer革新Diffusion模型
  • 构建高可靠NFS存储:自动化挂载保障机制的设计与优势
  • 【Vuex:在带命名空间的模块内访问全局内容】
  • Docker运行postgreSQL,由于异常启动或者退出后,提示could not locate a valid checkpoint record
  • JS—事件委托:3分钟掌握事件委托
  • vlan初学的总结
  • NLP高频面试题(四)——BN和LN的区别与联系,为什么attention要用LN
  • Visual Studio2022 中的键盘注释快捷方式
  • 多线程(四)----线程安全
  • 力扣刷题994. 腐烂的橘子
  • 新城市志|上海再攻坚,营商环境没有最好只有更好
  • 五粮液董事长:茅台1935已脱离千元价位带,五粮液在千元价位已逐步摆脱其他竞品纠缠
  • 东方红资管官宣:41岁原国信资管董事长成飞出任新总经理
  • 安徽亳州涡阳县司法局党组书记刘兴连落马
  • 47本笔记、2341场讲座,一位普通上海老人的阅读史
  • 欧洲承诺投资6亿欧元吸引外国科学家