当前位置: 首页 > 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…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

Golang——7、包与接口详解

包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...

【UE5 C++】通过文件对话框获取选择文件的路径

目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 &#xff0c;这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器&#xff0c;右键点击 .uproject 文件&#xff0c;选择 "Generate Visual Studio project files"&#xff0c;重…...

2.3 物理层设备

在这个视频中&#xff0c;我们要学习工作在物理层的两种网络设备&#xff0c;分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间&#xff0c;需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质&#xff0c;假设A节点要给…...

字符串哈希+KMP

P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...