前言
本项目实现了使用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);
}
}
}