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

devtools/2025/3/26 4:07:43/

前言

本项目实现了使用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: waveformCanvasanchors.fill: parentanchors.margins: 2onPaint: {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下载


http://www.ppmy.cn/devtools/169468.html

相关文章

数据结构篇——线索二叉树

一、引入 遍历二叉树是按一定规则将二叉树结点排成线性序列&#xff0c;得到先序、中序或后序序列&#xff0c;本质是对非线性结构线性化&#xff0c;使结点&#xff08;除首尾&#xff09;在线性序列中有唯一前驱和后继&#xff1b;但以二叉链表作存储结构时&#xff0c;只能获…

Redis解决缓存击穿问题——两种方法

目录 引言 解决办法 互斥锁&#xff08;强一致&#xff0c;性能差&#xff09; 逻辑过期&#xff08;高可用&#xff0c;性能优&#xff09; 设计逻辑过期时间 引言 缓存击穿&#xff1a;给某一个key设置了过期时间&#xff0c;当key过期的时候&#xff0c;恰好这个时间点对…

Linux:用 runc 构建 ARM 平台容器

文章目录 1. 前言2. 构建 runc2.1 准备 C 交叉编译器2.2 编译 libseccomp 库2.3 编译 runc2.3.1 安装 go 编译器2.3.2 编译 runc 3. 构建 runc 镜像包 测试运行3.1 OCI 规范3.2 手工构建 OCI 镜像3.3 运行 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;…

Flink 通过 Chunjun Oracle LogMiner 实时读取 Oracle 变更日志并写入 Doris 的方案

文章目录 一、 技术背景二、 关键技术1、 Oracle LogMiner2、 Chunjun 的 LogMiner 关键流程3、修复 Chunjun Oracle LogMiner 问题 一、 技术背景 在大数据实时同步场景中&#xff0c;需要将 Oracle 数据库的变更数据&#xff08;CDC&#xff09; 采集并写入 Apache Doris&am…

网络编程中客户端与服务器的搭建与协议包应用

1.客户端的搭建 2.服务器搭建 3.TCP中的粘包现象 tcp协议为了提高发送的效率&#xff0c;会将短时间连续发送的小数据&#xff0c;当做一组数据统一发送 原理是&#xff1a; tcp协议本身存在一个1500字节的缓存区&#xff0c;tcp协议每次write发送数据的时候&#xff0c;总是…

快速入手-基于Django的主子表间操作mysql(五)

1、如果该表中存在外键&#xff0c;结合实际业务情况&#xff0c;那可以这么写&#xff1a; 2、针对特殊的字典类型&#xff0c;可以这么定义 3、获取元组中的字典值和子表中的value值方法 4、对应的前端页面写法

llama源码学习·model.py[3]ROPE旋转位置编码(4)ROPE的应用

一、源码注释 def apply_rotary_emb(xq: torch.Tensor, # 查询矩阵xk: torch.Tensor, # 键矩阵freqs_cis: torch.Tensor, # 旋转嵌入 ) -> Tuple[torch.Tensor, torch.Tensor]:# 首先将xq和xk张量转换为浮点数# 然后使用reshape将最后一个维度拆分为两个维度&#xff0c;每…

《南京日报》专题报道 | 耘瞳科技“工业之眼”加码“中国智造”

在江宁开发区&#xff0c;机器人已不再是科幻电影里的遥远想象&#xff0c;他们就像人类的“同事”&#xff0c;在工地上忙着贴砖、刷墙、搬运、检测&#xff1b; 在体育训练场上帮助运动员矫正姿势&#xff1b; 在医院里帮助医生发现帕金森早期征兆&#xff0c;在智慧工厂里…