当前位置: 代码网 > it编程>编程语言>C/C++ > 使用Qt+SDL2实现WAV音频播放功能

使用Qt+SDL2实现WAV音频播放功能

2026年02月04日 C/C++ 我要评论
前言本文将手把手教大家用 qt + sdl2 实现 wav 音频文件的播放功能。核心思路是:把音频播放逻辑放在 qt 子线程中(避免阻塞 ui),通过 sdl2 的 “拉取(pull)模式

前言

本文将手把手教大家用 qt + sdl2 实现 wav 音频文件的播放功能。核心思路是:把音频播放逻辑放在 qt 子线程中(避免阻塞 ui),通过 sdl2 的 “拉取(pull)模式” 实现音频播放,点击主窗口的按钮就能切换 “播放 / 停止” 状态。

一、环境准备

1. 基础环境

  • 安装 qt(推荐 qt 5/6,mingw 编译器,本文以 qt 5.15 为例);
  • 下载 sdl2 开发库:sdl2 官网,选择对应系统的开发包(比如 windows 下的 mingw 版本)。

2. sdl2 配置

下载后解压 sdl2,在 qt 项目的.pro文件中添加 sdl2 的头文件和库路径(替换成你的 sdl2 路径):

# sdl2头文件路径
includepath += d:/sdl2-2.28.0/x86_64-w64-mingw32/include
# sdl2库文件路径
libs += -ld:/sdl2-2.28.0/x86_64-w64-mingw32/lib -lsdl2

二、整体流程梳理

先给大家讲清楚核心逻辑,避免看代码时懵圈:

  1. 主窗口(mainwindow)放一个 “播放 / 停止” 按钮,点击按钮触发槽函数;
  2. 点击按钮时,创建 / 停止播放线程(playthread),播放逻辑全在子线程里(防止 ui 卡);
  3. 子线程中用 sdl2 初始化音频子系统、加载 wav 文件、打开音频设备;
  4. sdl 采用 “pull 模式”:音频设备会主动调用回调函数,我们在回调中给设备喂音频数据;
  5. 停止播放时,中断线程、释放 sdl 资源,保证程序不崩溃。

三、逐文件解析代码

1. 程序入口:main.cpp

这是 qt 程序的标准入口,没复杂逻辑,就是创建应用、显示主窗口。

#include "mainwindow.h"
#include <qapplication>

int main(int argc, char *argv[])
{
    // 创建qt应用对象,管理程序生命周期
    qapplication a(argc, argv);
    // 创建主窗口对象
    mainwindow w;
    // 显示主窗口
    w.show();
    // 进入qt的事件循环(等待用户操作,比如点击按钮)
    return a.exec();
}

2. 播放线程头文件:playthread.h

定义一个继承自qthread的播放线程类,重写run方法(线程的核心执行逻辑)。

#ifndef playthread_h
#define playthread_h

// qt线程头文件
#include <qthread>

// 播放线程类,继承qthread
class playthread : public qthread
{
    // qt信号槽必须的宏
    q_object

private:
    // 重写qthread的run方法,线程启动后会执行这里的逻辑
    void run();

public:
    // 构造函数,parent是父对象(qt的父子机制自动管理内存)
    explicit playthread(qobject *parent = nullptr);
    // 析构函数,释放资源
    ~playthread();

signals:
    // 暂时没定义信号,后续可扩展(比如播放完成信号)
};

#endif // playthread_h

3. 主窗口头文件:mainwindow.h

主窗口类,包含播放按钮的槽函数、播放线程的指针。

#ifndef mainwindow_h
#define mainwindow_h

#include <qmainwindow>
// 包含播放线程的头文件
#include "playthread.h"

// qt的ui命名空间(.ui文件自动生成的代码)
qt_begin_namespace
namespace ui { class mainwindow; }
qt_end_namespace

class mainwindow : public qmainwindow
{
    q_object

public:
    mainwindow(qwidget *parent = nullptr);
    ~mainwindow();

private slots:
    // 播放按钮点击的槽函数(和ui里的按钮关联)
    void on_playbtn_clicked();

private:
    // 指向ui界面的指针(自动生成)
    ui::mainwindow *ui;
    // 播放线程的指针,初始化为nullptr
    playthread *_playthread = nullptr;
};
#endif // mainwindow_h

4. 主窗口实现:mainwindow.cpp

核心是按钮点击逻辑,处理 “播放 / 停止” 的切换,以及线程的创建和销毁。

#include "mainwindow.h"
#include "ui_mainwindow.h"
// sdl2头文件(音频相关)
#include <sdl2/sdl.h>
// qt调试输出
#include <qdebug>

// 主窗口构造函数
mainwindow::mainwindow(qwidget *parent)
    : qmainwindow(parent)
    , ui(new ui::mainwindow)
{
    // 初始化ui界面(加载按钮等控件)
    ui->setupui(this);
}

// 主窗口析构函数
mainwindow::~mainwindow()
{
    // 释放ui资源
    delete ui;
}

// 播放按钮点击触发的槽函数
void mainwindow::on_playbtn_clicked()
{
    // 如果线程已存在(正在播放),则停止播放
    if(_playthread)
    {
        // 向线程发送“中断请求”
        _playthread->requestinterruption();
        // 清空线程指针
        _playthread = nullptr;
        // 按钮文字改回“开始播放”
        ui->playbtn->settext("开始播放");
    }
    else // 线程不存在(未播放),则开始播放
    {
        // 创建播放线程,父对象设为主窗口(自动管理内存)
        _playthread = new playthread(this);
        // 启动线程(会执行playthread的run方法)
        _playthread->start();
        
        // 监听线程结束信号:线程播放完成后,重置状态
        connect(_playthread,&playthread::finished,
                [this]()
                {
                    _playthread = nullptr;
                    ui->playbtn->settext("开始播放");
                });
        
        // 按钮文字改成“停止播放”
        ui->playbtn->settext("停止播放");
    }
}

5. 播放线程实现:playthread.cp

这是音频播放的核心逻辑,包含 sdl2 的初始化、wav 加载、音频回调、资源释放等。

#include "playthread.h"
#include <sdl2/sdl.h>
#include <qdebug>
#include <qfile>

// !!!替换成你自己的wav文件路径!!!
#define filename "d:/in.wav"

// 音频缓冲区结构体:给sdl回调函数传递pcm数据和长度
typedef struct
{
    int len = 0;      // 剩余未播放的pcm数据长度
    int pulllen = 0;  // 本次要填充的pcm数据长度
    uint8 *data = nullptr; // 指向pcm数据的指针
}audiobuffer;

// 播放线程构造函数
playthread::playthread(qobject *parent):qthread{parent}
{
    // 线程结束后自动销毁(避免内存泄漏)
    connect(this,&playthread::finished,this,&playthread::deletelater);
}

// 播放线程析构函数(关键:安全停止线程+释放资源)
playthread::~playthread()
{
    // 断开所有信号槽
    disconnect();
    // 发送中断请求
    requestinterruption();
    // 退出线程事件循环
    quit();
    // 等待线程结束(防止线程还在运行就销毁)
    wait();

    qdebug() << this << "析构了";
}

// sdl音频回调函数(音频设备会主动调用这个函数“拉取”数据)
// userdata:自定义数据(这里传audiobuffer)
// stream:音频设备的缓冲区,需要往里面填pcm数据
// len:音频设备希望填充的字节数
void pull_audio_data(void *userdata, uint8 *stream, int len)
{
    qdebug() << "音频设备拉取数据,期望长度:" << len;

    // 第一步:清空stream(静音处理,避免填充数据前有杂音)
    sdl_memset(stream,0,len);

    // 取出我们传递的audiobuffer
    audiobuffer *buffer = (audiobuffer*)userdata;

    // 如果没有可用的pcm数据,直接返回(静音)
    if(buffer->len <= 0)    return;

    // 第二步:确定本次要填充的长度(取“设备期望长度”和“剩余数据长度”的最小值)
    buffer->pulllen = (len > buffer->len) ? buffer->len : len;

    // 第三步:填充pcm数据到音频设备缓冲区
    // sdl_mixaudio:混合音频(这里直接用最大音量)
    sdl_mixaudio(stream, buffer->data, buffer->pulllen, sdl_mix_maxvolume);
    
    // 第四步:更新缓冲区状态(已播放的数据要跳过)
    buffer->data += buffer->pulllen;  // 指针后移,指向剩余数据
    buffer->len -= buffer->pulllen;   // 剩余长度减少
}

// 线程的核心执行函数(播放逻辑全在这里)
void playthread::run()
{
    // 1. 初始化sdl的音频子系统
    if(sdl_init(sdl_init_audio)) // 非0表示失败
    {
        qdebug() << "sdl初始化失败:" << sdl_geterror();
        return;
    }

    // 2. 加载wav文件
    sdl_audiospec spec;    // 存储wav文件的音频规格(采样率、声道数等)
    uint8 *data = nullptr; // 指向wav文件的pcm原始数据
    uint32 len = 0;        // pcm数据的总长度
    if(!sdl_loadwav(filename,&spec,&data,&len)) // 加载失败返回0
    {
        qdebug() << "加载wav文件失败:" << sdl_geterror();
        sdl_quit(); // 失败则释放sdl资源
        return;
    }

    // 3. 配置音频播放参数
    spec.samples = 1024;          // 音频缓冲区的样本数(常用1024)
    spec.callback = pull_audio_data; // 设置音频回调函数(设备拉数据时调用)
    audiobuffer buffer;           // 创建音频缓冲区
    buffer.data = data;           // 绑定pcm数据
    buffer.len = len;             // 绑定pcm数据长度
    spec.userdata = &buffer;      // 给回调函数传自定义数据(audiobuffer)

    // 4. 打开音频设备
    if(sdl_openaudio(&spec,nullptr)) // 失败返回非0
    {
        qdebug() << "打开音频设备失败:" << sdl_geterror();
        sdl_freewav(data); // 释放wav数据
        sdl_quit();        // 释放sdl资源
        return;
    }

    // 5. 开始播放(0=取消暂停,1=暂停)
    sdl_pauseaudio(0);

    // 6. 计算音频参数(用于后续等待播放完成)
    int samplesize = sdl_audio_bitsize(spec.format); // 每个样本的位数(比如16位)
    // 每个样本的字节数 = (位数 * 声道数) / 8(8位=1字节)
    int bytespersample = (samplesize * spec.channels) >> 3;

    // 7. 主线程循环:等待播放完成/收到中断请求
    while(!isinterruptionrequested()) // 只要没收到“停止”请求,就循环
    {
        // 如果还有未播放的pcm数据,继续等待(回调函数会自动处理)
        if(buffer.len > 0) continue;

        // 8. 播放完成:等待最后一批数据播放完毕
        if(buffer.len <= 0)
        {
            // 计算最后一批数据的播放时长(毫秒)
            int samples = buffer.pulllen / bytespersample; // 样本数
            int ms = samples * 1000 / spec.freq;           // 时长=样本数/采样率*1000
            sdl_delay(ms); // 等待播放完成
            break; // 退出循环
        }
    }

    // 9. 释放资源(重中之重!)
    sdl_freewav(data);    // 释放wav文件的pcm数据
    sdl_closeaudio();     // 关闭音频设备
    sdl_quit();           // 释放sdl所有子系统
}

四、ui 设计

在 qt designer 中给主窗口拖一个qpushbutton,命名为playbtn,文字默认设为 “开始播放”,无需其他控件。

五、运行效果

  1. 替换代码中filename为你的 wav 文件路径(必须是 wav 格式,sdl_loadwav 不支持 mp3);
  2. 编译运行程序,点击 “开始播放” 按钮,就能听到音频播放,按钮文字变成 “停止播放”;
  3. 播放过程中点击 “停止播放”,音频会停止,按钮文字恢复;
  4. 音频播放完成后,按钮会自动恢复为 “开始播放”。

六、注意事项

  1. 线程安全:音频播放必须放在子线程!如果直接在 ui 线程执行 sdl 逻辑,播放时 ui 会卡死;
  2. 资源释放:析构函数中一定要requestinterruption()+quit()+wait(),否则线程可能野跑;
  3. 文件路径:wav 文件路径用绝对路径(比如d:/in.wav),路径中不要有中文和空格;
  4. sdl 版本:一定要选和 qt 编译器匹配的 sdl 库(比如 qt 用 mingw 64 位,sdl 也要下 mingw 64 位);
  5. 格式限制:sdl_loadwav 只支持 wav 格式,想播放 mp3 需要额外解码(比如 ffmpeg);
  6. 静音处理:回调函数中先sdl_memset(stream,0,len),否则填充数据前会有杂音。

七、总结

本文通过 qt 子线程 + sdl2 的 pull 模式实现了 wav 音频播放,核心是理解:

  • qt 线程:避免 ui 阻塞;
  • sdl pull 模式:音频设备主动拉取数据,回调函数填充 pcm;
  • 资源管理:线程和 sdl 资源的正确释放。

以上就是使用qt+sdl2实现wav音频播放功能的详细内容,更多关于qt sdl2实现wav音频播放的资料请关注代码网其它相关文章!

(0)

相关文章:

  • C++中包装器的使用示例

    C++中包装器的使用示例

    在 c++ 中,包装器(wrapper) 也常被称为适配器(adapter),是一种设计模式(结构型模式)的实现——核心作用是包装一个已... [阅读全文]
  • Qt中TCP Socket的实现

    Qt中TCP Socket的实现

    1 -> 概述tcp(transmission control protocol,传输控制协议)是一种面向连接、可靠、基于字节流的传输层通信协议。在 qt... [阅读全文]
  • Qt中foreach的实现示例

    Qt中foreach的实现示例

    在 qt 中,foreach 是一个 qt 扩展的关键字(宏定义),用于遍历容器类元素,语法简洁、使用方便,底层基于容器的迭代器实现,但屏蔽了迭代器的复杂操作,... [阅读全文]
  • C++中预编译指令的实现

    C++中预编译指令的实现

    在 c++ 中,预编译指令(preprocessing directive)是编译器在编译阶段之前(预处理阶段)执行的特殊指令,用于控制代码的预处理过程(如文件... [阅读全文]
  • Qt中QKeySequence的实现示例

    Qt中QKeySequence的实现示例

    一、揭开 qkeysequence 的神秘面纱在 qt 框架的庞大体系中,qkeysequence 是一个处理键盘快捷键序列的类,在构建交互友好的应用程序时扮演... [阅读全文]
  • Qt中QShortcut的高效键盘开发

    Qt中QShortcut的高效键盘开发

    在图形用户界面(gui)开发中,键盘快捷方式是提升用户体验的关键元素之一。它允许用户通过简单的键盘组合快速执行常用操作,避免了频繁鼠标操作的繁琐。qt框架作为跨... [阅读全文]

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com