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

发布于:2025-03-19 ⋅ 阅读:(16) ⋅ 点赞:(0)

前言

本项目实现了使用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下载