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

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...