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

Qt SDL2播放Wav音频

这里介绍两种方法来实现Qt播放Wav音频数据。

方法一:使用QAudioOutput

pro文件中加入multimedia模块。

#include <QApplication>
#include <QFile>
#include <QAudioFormat>
#include <QAudioOutput>int main(int argc, char *argv[])
{QApplication a(argc, argv);QFile inputFile;inputFile.setFileName("test.wav");inputFile.open(QIODevice::ReadOnly);//设置采样格式QAudioFormat audioFormat;//设置采样率audioFormat.setSampleRate(44100);//设置通道数audioFormat.setChannelCount(2);//设置采样大小,一般为8位或16位audioFormat.setSampleSize(16);//设置编码方式audioFormat.setCodec("audio/pcm");//设置字节序audioFormat.setByteOrder(QAudioFormat::LittleEndian);//设置样本数据类型audioFormat.setSampleType(QAudioFormat::UnSignedInt);QAudioOutput *audio = new QAudioOutput( audioFormat, 0);audio->start(&inputFile);return a.exec();
}

注意这里采样率、通道数和采样大小的设置,本例只能用来播放无损的WAV。


方法二:使用SDL2来播放

接下来演示一下如何使用SDL播放WAV文件。

初始化子系统:

// 初始化Audio子系统
if (SDL_Init(SDL_INIT_AUDIO)) {qDebug() << "SDL_Init error:" << SDL_GetError();return;
}

 加载WAV文件:

// 存放WAV的PCM数据和数据长度
typedef struct {Uint32 len = 0;int pullLen = 0;Uint8 *data = nullptr;
} AudioBuffer;// WAV中的PCM数据
Uint8 *data;
// WAV中的PCM数据大小(字节)
Uint32 len;
// 音频参数
SDL_AudioSpec spec;// 加载wav文件
if (!SDL_LoadWAV(FILENAME, &spec, &data, &len)) {qDebug() << "SDL_LoadWAV error:" << SDL_GetError();// 清除所有的子系统SDL_Quit();return;
}// 回调
spec.callback = pull_audio_data;
// 传递给回调函数的userdata
AudioBuffer buffer;
buffer.len = len;
buffer.data = data;
spec.userdata = &buffer;

打开音频设备:

// 打开设备
if (SDL_OpenAudio(&spec, nullptr)) {qDebug() << "SDL_OpenAudio error:" << SDL_GetError();// 释放文件数据SDL_FreeWAV(data);// 清除所有的子系统SDL_Quit();return;
}

开始播放:

		// 每一个样本大小int size = SDL_AUDIO_BITSIZE(m_spec.format) * m_spec.channels / 8;// 最后一次播放的样本数量int leftSample = m_buffer.pullLen / size;// 最后一次播放的时长msint ms = leftSample * 1000 / m_spec.freq;SDL_Delay(ms);

回调:

static void fill_audio(void *userdata, Uint8 *stream, int len)
{AudioPlayThread::AudioBuffer *buffer = (AudioPlayThread::AudioBuffer *)userdata;SDL_memset(stream, 0, len);if (buffer->len <= 0) return;if (buffer->thread->m_isPause)return;buffer->pullLen = buffer->len > len ? len : buffer->len;SDL_MixAudio(stream, buffer->data, buffer->pullLen, SDL_MIX_MAXVOLUME);buffer->data += buffer->pullLen;buffer->len -= buffer->pullLen;//未播放的时间int unPlayTime = (buffer->thread->m_totalTime) - (buffer->len / buffer->thread->m_perByte);buffer->thread->m_timeFunc(unPlayTime,buffer->thread->m_totalTime);
}

释放资源:

// 释放WAV文件数据
SDL_FreeWAV(data);// 关闭设备
SDL_CloseAudio();// 清除所有的子系统
SDL_Quit();

运行效果图:

音频界面构造:

音频播放界面:AudioPlayWidget类。

#ifndef AUDIOPLAYWIDGET_H
#define AUDIOPLAYWIDGET_H#include <QWidget>namespace Ui {
class AudioPlayWidget;
}class AudioPlayThread;
class AudioPlayWidget : public QWidget
{Q_OBJECTpublic:explicit AudioPlayWidget(const QString &name, const QString &url, QWidget *parent = 0);~AudioPlayWidget();private slots:void on_btnPlay_clicked();void slotFinished();void slotShowTime(int playTime, int totalTime);private:Ui::AudioPlayWidget *ui;private:bool m_play = false;AudioPlayThread *m_thread = nullptr;bool m_isExistPlay = false;QString m_url;
};#endif // AUDIOPLAYWIDGET_H#include "AudioPlayWidget.h"
#include "ui_AudioPlayWidget.h"
#include "BaseHelper.h"
#include "AudioPlayThread.h"
#include <QTime>
#include "mymessagebox.h"const QString playStyle = "QPushButton#btnPlay\
{\border-image: url(\":/image/audioPlay.png\");\
}";const QString pauseStyle = "QPushButton#btnPlay\
{\border-image: url(\":/image/audioPause.png\");\
}";AudioPlayWidget::AudioPlayWidget(const QString &name, const QString &url,QWidget *parent) :QWidget(parent),ui(new Ui::AudioPlayWidget),m_url(url)
{ui->setupUi(this);m_thread = new AudioPlayThread(this);connect(m_thread,&AudioPlayThread::finished,this,&AudioPlayWidget::slotFinished);BaseHelper::load(":/qss/AudioPlayWidget.qss",this);
}AudioPlayWidget::~AudioPlayWidget()
{delete ui;if(m_thread){m_thread->stop();m_thread->wait();}
}void AudioPlayWidget::on_btnPlay_clicked()
{if (!BaseHelper::fileExist(m_url)){MyMessageBox::showMyMessageBox(this, QString("文件丢失"),QString("打开的音频文件丢失?"), MESSAGE_WARNNING, BUTTON_OK, true);return;}m_play = !m_play;if(m_play){ui->btnPlay->setStyleSheet(pauseStyle);if (!m_isExistPlay){TimeFunc totalTimeFunc = std::bind(&AudioPlayWidget::slotShowTime, this,std::placeholders::_1, std::placeholders::_2);m_thread->open(m_url, totalTimeFunc);m_thread->start();m_isExistPlay = true;}else{m_thread->resume();}}else{ui->btnPlay->setStyleSheet(playStyle);m_thread->pause();}
}void AudioPlayWidget::slotFinished()
{if (m_isExistPlay)m_isExistPlay = false;m_play = !m_play;ui->lbPlay->setText("00:00:00");ui->lbTotal->setText("00:00:00");ui->horizontalSlider->setValue(0);ui->btnPlay->setStyleSheet(playStyle);
}void AudioPlayWidget::slotShowTime(int playTime, int totalTime)
{QString strPlayTime = QTime::fromMSecsSinceStartOfDay(playTime* 1000).toString("hh:mm:ss");QString strTotalTime = QTime::fromMSecsSinceStartOfDay(totalTime*1000).toString("hh:mm:ss");ui->lbPlay->setText(strPlayTime);ui->lbTotal->setText(strTotalTime);ui->horizontalSlider->setMaximum(totalTime);ui->horizontalSlider->setMinimum(0);ui->horizontalSlider->setValue(playTime);
}

音频播放线程:

#ifndef AUDIOPLAYTHREAD_H
#define AUDIOPLAYTHREAD_H#include <QThread>
#include "global.h"class AudioPlayThread : public QThread
{
public:AudioPlayThread(QObject *parent = nullptr);typedef struct AudioBuffer {int len = 0;int pullLen = 0;uint8_t *data = nullptr;AudioPlayThread *thread = nullptr;} AudioBuffer;public:int open(const QString &fileName, TimeFunc timeFunc);void stop();void pause();void resume();protected:void run();public:TimeFunc m_timeFunc;int m_totalTime = 0; //单位sint m_perByte = 0;//1s字节数bool m_isPause = false;private:bool m_isExit = false; Uint8 *m_data = nullptr;Uint32 m_len = 0;AudioBuffer m_buffer;SDL_AudioSpec m_spec;
};#endif // AUDIOPLAYTHREAD_H#include "AudioPlayThread.h"static void fill_audio(void *userdata, Uint8 *stream, int len)
{AudioPlayThread::AudioBuffer *buffer = (AudioPlayThread::AudioBuffer *)userdata;SDL_memset(stream, 0, len);if (buffer->len <= 0) return;if (buffer->thread->m_isPause)return;buffer->pullLen = buffer->len > len ? len : buffer->len;SDL_MixAudio(stream, buffer->data, buffer->pullLen, SDL_MIX_MAXVOLUME);buffer->data += buffer->pullLen;buffer->len -= buffer->pullLen;//未播放的时间int unPlayTime = (buffer->thread->m_totalTime) - (buffer->len / buffer->thread->m_perByte);buffer->thread->m_timeFunc(unPlayTime,buffer->thread->m_totalTime);
}AudioPlayThread::AudioPlayThread(QObject *parent): QThread(parent)
{
}int AudioPlayThread::open(const QString &fileName, TimeFunc timeFunc)
{m_timeFunc = timeFunc;// 加载 WAV 文件if (!SDL_LoadWAV(fileName.toStdString().c_str(), &m_spec, &m_data, &m_len)){printf("load wav error \n");return -1;}//计算1s钟字节大小m_perByte = (SDL_AUDIO_BITSIZE(m_spec.format) * m_spec.channels / 8 * m_spec.freq);m_totalTime = m_len / m_perByte;m_timeFunc(0, m_totalTime);printf("===============Wav params=============\n");printf("======size = %d\n", m_len);printf("======channels = %d\n", m_spec.channels);printf("======samples = %d\n", m_spec.samples);printf("======freq = %d\n", m_spec.freq);printf("======time = %d\n", m_totalTime);printf("===============Wav params=============\n");m_buffer.data = m_data;m_buffer.len = m_len;m_buffer.thread = this;m_spec.userdata = &m_buffer;m_spec.callback = fill_audio;// 打开音频设备if (SDL_OpenAudio(&m_spec, nullptr)){printf("SDL_OpenAudio error \n");SDL_FreeWAV(m_data);return  -1;}return 0;
}void AudioPlayThread::stop()
{m_isExit = true;
}void AudioPlayThread::pause()
{m_isPause = true;
}void AudioPlayThread::resume()
{m_isPause = false;
}void AudioPlayThread::run()
{SDL_PauseAudio(0);while(!m_isExit){if (m_buffer.len > 0) continue;// 每一个样本大小int size = SDL_AUDIO_BITSIZE(m_spec.format) * m_spec.channels / 8;// 最后一次播放的样本数量int leftSample = m_buffer.pullLen / size;// 最后一次播放的时长msint ms = leftSample * 1000 / m_spec.freq;SDL_Delay(ms);break;}// 释放 WAV 数据SDL_FreeWAV(m_data);SDL_CloseAudio();printf("=======Play Wav thread exit===\n");
}

相关文章:

Qt SDL2播放Wav音频

这里介绍两种方法来实现Qt播放Wav音频数据。 方法一&#xff1a;使用QAudioOutput pro文件中加入multimedia模块。 #include <QApplication> #include <QFile> #include <QAudioFormat> #include <QAudioOutput>int main(int argc, char *argv[]) {…...

[ACM学习] 动态规划基础之一二三维dp

课内学习的动态规划 有记忆的迭代 优化解的结构&#xff1a;原始问题的一部分解是子问题的解 三要素&#xff1a;1.子问题 2.状态的定义 3.状态转移方程 定义 线性dp的一道例题 dp[i]表示以位置 i 结尾的方案总数&#xff0c;dp[4]2&#xff0c;因为&#xff1a;首先只放一…...

Qt点击按钮在其附近弹出一个窗口

效果 FS_PopupWidget.h #ifndef FS_POPUPWIDGET_H #define FS_POPUPWIDGET_H#pragma once#include <QToolButton> #include <QWidgetAction> #include <QPointer>class QMenu;class FS_PopupWidget : public QToolButton {Q_OBJECTpublic:FS_PopupWidget(QW…...

Springboot注解@Configuration和@Bean注解作用,生命周期

简介&#xff1a; Configuration 类是定义 bean 配置的地方&#xff0c;而 Bean 方法是具体创建 bean 实例的方法。 Configuration 作用&#xff1a; Configuration 注解用于定义配置类&#xff0c;表明该类包含一个或多个 bean 定义的方法。Spring 容器在启动时会自动扫描这些…...

30天精通Nodejs--第十五天:Websocket

引言 这里我们将继续深入探讨另一项强大且实时性极高的网络通信技术——WebSocket。通过本篇文章,将全面了解如何在Node.js环境中利用WebSocket实现服务端与客户端之间双向、低延迟的数据传输,并掌握其基础用法以及一些高级应用场景。 基础用法 安装WebSocket库 在Node.j…...

C++深入学习之STL:2、适配器、迭代器与算法部分

适配器概述 C标准模板库(STL)中提供了几种适配器&#xff0c;这些适配器主要用于修改或扩展容器类的功能。STL中的适配器主要包括以下几种&#xff1a; 1、迭代器适配器&#xff1a;迭代器适配器提供了一种机制&#xff0c;可以将非迭代器对象转换为迭代器对象。比如back_ins…...

Tiktok/抖音旋转验证码识别

一、引言 在数字世界的飞速发展中&#xff0c;安全防护成为了一个不容忽视的课题。Tiktok/抖音&#xff0c;作为全球最大的短视频平台之一&#xff0c;每天都有数以亿计的用户活跃在其平台上。为了保护用户的账号安全&#xff0c;Tiktok/抖音引入了一种名为“旋转验证码”的安…...

【Java 设计模式】设计原则

文章目录 ✨单一职责原则&#xff08;SRP&#xff09;✨开放/封闭原则&#xff08;OCP&#xff09;✨里氏替换原则&#xff08;LSP&#xff09;✨依赖倒置原则&#xff08;DIP&#xff09;✨接口隔离原则&#xff08;ISP&#xff09;✨合成/聚合复用原则&#xff08;CARP&#…...

Druid连接池工具公式化SQL附踩坑记录

1. 需求 使用Druid连接池工具格式化sql用于回显时候美观展示 2. 代码示例 2.1 依赖 <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.6</version> </dependency> 2.2 ParseUtils…...

Linux内核--网络协议栈(二)UDP数据包发送

目录 一、引言 二、数据包发送 ------>2.1、数据发送流程 三、协议层注册 ------>3.1、socket系统调用 ------>3.2、socket创建 ------>3.3、协议族初始化 ------>3.4、对应协议的socket创建 ------>3.5、协议注册 四、通过套接字发送网络数据 --…...

基于深度学习的时间序列算法总结

1.概述 深度学习方法是一种利用神经网络模型进行高级模式识别和自动特征提取的机器学习方法&#xff0c;近年来在时序预测领域取得了很好的成果。常用的深度学习模型包括循环神经网络&#xff08;RNN&#xff09;、长短时记忆网络&#xff08;LSTM&#xff09;、门控循环单元&a…...

nginx中多个server块共用upstream会相互影响吗

背景 nginx中经常有这样的场景&#xff0c;多个server块共用一个域名。 如&#xff1a;upstream有2个以上的域名&#xff0c;nginx配置两个server块&#xff0c;共用一个upstream配置。 那么&#xff0c;如果其中一个域名发生"no live upstreams while connecting to ups…...

基于信号完整性的一些PCB设计建议

最小化单根信号线质量的一些PCB设计建议 1. 使用受控阻抗线&#xff1b; 2. 理想情况下&#xff0c;所有信号都应该使用完整的电源或地平面作为其返回路径&#xff0c;关键信号则使用地平面作为返回路径&#xff1b; 3. 信号的返回参考面发生变化时&#xff0c;在尽可能接近…...

《BackTrader量化交易图解》第8章:plot 绘制金融图

文章目录 8. plot 绘制金融图8.1 金融分析曲线8.2 多曲线金融指标8.3 Observers 观测子模块8.4 plot 绘图函数的常用参数8.5 买卖点符号和色彩风格8.6 vol 成交参数8.7 多图拼接模式8.8 绘制 HA 平均 K 线图 8. plot 绘制金融图 8.1 金融分析曲线 BackTrader内置的plot绘图函…...

什么是欧拉筛??

欧拉筛&#xff08;Eulers Sieve&#xff09;&#xff0c;又称线性筛法或欧拉线性筛&#xff0c;是一种高效筛选素数的方法。它的核心思想是从小到大遍历每个数&#xff0c;同时标记其倍数为合数&#xff0c;但每个合数只被其最小的质因数标记一次&#xff0c;从而避免了重复标…...

2023年全国职业院校技能大赛软件测试赛题—单元测试卷⑩

单元测试 一、任务要求 题目1&#xff1a;根据下列流程图编写程序实现相应处理&#xff0c;程序根据两个输入参数iRecordNum和IType计算x的值并返回。编写程序代码&#xff0c;使用JUnit框架编写测试类对编写的程序代码进行测试&#xff0c;测试类中设计最少的测试数据满足基路…...

使用WAF防御网络上的隐蔽威胁之SSRF攻击

服务器端请求伪造&#xff08;SSRF&#xff09;攻击是一种常见的网络安全威胁&#xff0c;它允许攻击者诱使服务器执行恶意请求。与跨站请求伪造&#xff08;CSRF&#xff09;相比&#xff0c;SSRF攻击针对的是服务器而不是用户。了解SSRF攻击的工作原理、如何防御它&#xff0…...

Redis基础系列-哨兵模式

Redis基础系列-哨兵模式 文章目录 Redis基础系列-哨兵模式1. 引言2. 什么是哨兵模式&#xff1f;3. 哨兵模式的配置4. 哨兵模式的启动和验证4.1 主master宕机&#xff0c;看会出现什么问题4.2 重启6379主机 5. 哨兵模式的工作原理和选举原理5.1. SDown主观下线&#xff08;Subj…...

【angular教程240112】09(完) Angular中的数据请求 与 路由

【angular教程240112】09(完) Angular中的数据请求 与 路由 目录标题 一、 Angular 请求数据简介0 使用Angular内置模块HttpClientModule和HttpClientJsonpModule:1 Angular中的GET请求:2 Angular中的POST请求:3 Angular中的JSONP请求:4使用Axios进行数据请求: 二、 详解 Angul…...

go中拷贝文件操作

一. 拷贝文件内容到另一个文件位置 // 拷贝文件内容到另一个文件里面 func copyContent() {filepath1 : "d:/abc.txt"filepath2 : "e:/eee.txt"// 读取内容data, err : os.ReadFile(filepath1) // 使用os.ReadFile函数读取指定路径的文件内容if err ! nil…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...