最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Qt+SDL2實(shí)現(xiàn)WAV音頻播放功能

 更新時(shí)間:2026年02月04日 09:54:37   作者:xmRao  
本文介紹了如何使用Qt和SDL2實(shí)現(xiàn)WAV音頻文件的播放功能,通過將音頻播放邏輯放在Qt子線程中,避免了UI阻塞,并使用SDL2的拉取模式實(shí)現(xiàn)音頻播放,需要的朋友可以參考下

前言

本文將手把手教大家用 Qt + SDL2 實(shí)現(xiàn) WAV 音頻文件的播放功能。核心思路是:把音頻播放邏輯放在 Qt 子線程中(避免阻塞 UI),通過 SDL2 的 “拉?。≒ull)模式” 實(shí)現(xiàn)音頻播放,點(diǎn)擊主窗口的按鈕就能切換 “播放 / 停止” 狀態(tài)。

一、環(huán)境準(zhǔn)備

1. 基礎(chǔ)環(huán)境

  • 安裝 Qt(推薦 Qt 5/6,MinGW 編譯器,本文以 Qt 5.15 為例);
  • 下載 SDL2 開發(fā)庫(kù):SDL2 官網(wǎng),選擇對(duì)應(yīng)系統(tǒng)的開發(fā)包(比如 Windows 下的 MinGW 版本)。

2. SDL2 配置

下載后解壓 SDL2,在 Qt 項(xiàng)目的.pro文件中添加 SDL2 的頭文件和庫(kù)路徑(替換成你的 SDL2 路徑):

# SDL2頭文件路徑
INCLUDEPATH += D:/SDL2-2.28.0/x86_64-w64-mingw32/include
# SDL2庫(kù)文件路徑
LIBS += -LD:/SDL2-2.28.0/x86_64-w64-mingw32/lib -lSDL2

二、整體流程梳理

先給大家講清楚核心邏輯,避免看代碼時(shí)懵圈:

  1. 主窗口(MainWindow)放一個(gè) “播放 / 停止” 按鈕,點(diǎn)擊按鈕觸發(fā)槽函數(shù);
  2. 點(diǎn)擊按鈕時(shí),創(chuàng)建 / 停止播放線程(playThread),播放邏輯全在子線程里(防止 UI 卡);
  3. 子線程中用 SDL2 初始化音頻子系統(tǒng)、加載 WAV 文件、打開音頻設(shè)備;
  4. SDL 采用 “Pull 模式”:音頻設(shè)備會(huì)主動(dòng)調(diào)用回調(diào)函數(shù),我們?cè)诨卣{(diào)中給設(shè)備喂音頻數(shù)據(jù);
  5. 停止播放時(shí),中斷線程、釋放 SDL 資源,保證程序不崩潰。

三、逐文件解析代碼

1. 程序入口:main.cpp

這是 Qt 程序的標(biāo)準(zhǔn)入口,沒復(fù)雜邏輯,就是創(chuàng)建應(yīng)用、顯示主窗口。

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

int main(int argc, char *argv[])
{
    // 創(chuàng)建Qt應(yīng)用對(duì)象,管理程序生命周期
    QApplication a(argc, argv);
    // 創(chuàng)建主窗口對(duì)象
    MainWindow w;
    // 顯示主窗口
    w.show();
    // 進(jìn)入Qt的事件循環(huán)(等待用戶操作,比如點(diǎn)擊按鈕)
    return a.exec();
}

2. 播放線程頭文件:playthread.h

定義一個(gè)繼承自QThread的播放線程類,重寫run方法(線程的核心執(zhí)行邏輯)。

#ifndef PLAYTHREAD_H
#define PLAYTHREAD_H

// Qt線程頭文件
#include <QThread>

// 播放線程類,繼承QThread
class playThread : public QThread
{
    // Qt信號(hào)槽必須的宏
    Q_OBJECT

private:
    // 重寫QThread的run方法,線程啟動(dòng)后會(huì)執(zhí)行這里的邏輯
    void run();

public:
    // 構(gòu)造函數(shù),parent是父對(duì)象(Qt的父子機(jī)制自動(dòng)管理內(nèi)存)
    explicit playThread(QObject *parent = nullptr);
    // 析構(gòu)函數(shù),釋放資源
    ~playThread();

signals:
    // 暫時(shí)沒定義信號(hào),后續(xù)可擴(kuò)展(比如播放完成信號(hào))
};

#endif // PLAYTHREAD_H

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

主窗口類,包含播放按鈕的槽函數(shù)、播放線程的指針。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
// 包含播放線程的頭文件
#include "playthread.h"

// Qt的UI命名空間(.ui文件自動(dòng)生成的代碼)
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    // 播放按鈕點(diǎn)擊的槽函數(shù)(和UI里的按鈕關(guān)聯(lián))
    void on_playBtn_clicked();

private:
    // 指向UI界面的指針(自動(dòng)生成)
    Ui::MainWindow *ui;
    // 播放線程的指針,初始化為nullptr
    playThread *_playThread = nullptr;
};
#endif // MAINWINDOW_H

4. 主窗口實(shí)現(xiàn):mainwindow.cpp

核心是按鈕點(diǎn)擊邏輯,處理 “播放 / 停止” 的切換,以及線程的創(chuàng)建和銷毀。

#include "mainwindow.h"
#include "ui_mainwindow.h"
// SDL2頭文件(音頻相關(guān))
#include <SDL2/SDL.h>
// Qt調(diào)試輸出
#include <QDebug>

// 主窗口構(gòu)造函數(shù)
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    // 初始化UI界面(加載按鈕等控件)
    ui->setupUi(this);
}

// 主窗口析構(gòu)函數(shù)
MainWindow::~MainWindow()
{
    // 釋放UI資源
    delete ui;
}

// 播放按鈕點(diǎn)擊觸發(fā)的槽函數(shù)
void MainWindow::on_playBtn_clicked()
{
    // 如果線程已存在(正在播放),則停止播放
    if(_playThread)
    {
        // 向線程發(fā)送“中斷請(qǐng)求”
        _playThread->requestInterruption();
        // 清空線程指針
        _playThread = nullptr;
        // 按鈕文字改回“開始播放”
        ui->playBtn->setText("開始播放");
    }
    else // 線程不存在(未播放),則開始播放
    {
        // 創(chuàng)建播放線程,父對(duì)象設(shè)為主窗口(自動(dòng)管理內(nèi)存)
        _playThread = new playThread(this);
        // 啟動(dòng)線程(會(huì)執(zhí)行playThread的run方法)
        _playThread->start();
        
        // 監(jiān)聽線程結(jié)束信號(hào):線程播放完成后,重置狀態(tài)
        connect(_playThread,&playThread::finished,
                [this]()
                {
                    _playThread = nullptr;
                    ui->playBtn->setText("開始播放");
                });
        
        // 按鈕文字改成“停止播放”
        ui->playBtn->setText("停止播放");
    }
}

5. 播放線程實(shí)現(xiàn):playthread.cp

這是音頻播放的核心邏輯,包含 SDL2 的初始化、WAV 加載、音頻回調(diào)、資源釋放等。

#include "playthread.h"
#include <SDL2/SDL.h>
#include <QDebug>
#include <QFile>

// ?。?!替換成你自己的WAV文件路徑?。?!
#define FILENAME "D:/in.wav"

// 音頻緩沖區(qū)結(jié)構(gòu)體:給SDL回調(diào)函數(shù)傳遞PCM數(shù)據(jù)和長(zhǎng)度
typedef struct
{
    int len = 0;      // 剩余未播放的PCM數(shù)據(jù)長(zhǎng)度
    int pullLen = 0;  // 本次要填充的PCM數(shù)據(jù)長(zhǎng)度
    Uint8 *data = nullptr; // 指向PCM數(shù)據(jù)的指針
}AudioBuffer;

// 播放線程構(gòu)造函數(shù)
playThread::playThread(QObject *parent):QThread{parent}
{
    // 線程結(jié)束后自動(dòng)銷毀(避免內(nèi)存泄漏)
    connect(this,&playThread::finished,this,&playThread::deleteLater);
}

// 播放線程析構(gòu)函數(shù)(關(guān)鍵:安全停止線程+釋放資源)
playThread::~playThread()
{
    // 斷開所有信號(hào)槽
    disconnect();
    // 發(fā)送中斷請(qǐng)求
    requestInterruption();
    // 退出線程事件循環(huán)
    quit();
    // 等待線程結(jié)束(防止線程還在運(yùn)行就銷毀)
    wait();

    qDebug() << this << "析構(gòu)了";
}

// SDL音頻回調(diào)函數(shù)(音頻設(shè)備會(huì)主動(dòng)調(diào)用這個(gè)函數(shù)“拉取”數(shù)據(jù))
// userdata:自定義數(shù)據(jù)(這里傳AudioBuffer)
// stream:音頻設(shè)備的緩沖區(qū),需要往里面填PCM數(shù)據(jù)
// len:音頻設(shè)備希望填充的字節(jié)數(shù)
void pull_audio_data(void *userdata, Uint8 *stream, int len)
{
    qDebug() << "音頻設(shè)備拉取數(shù)據(jù),期望長(zhǎng)度:" << len;

    // 第一步:清空stream(靜音處理,避免填充數(shù)據(jù)前有雜音)
    SDL_memset(stream,0,len);

    // 取出我們傳遞的AudioBuffer
    AudioBuffer *buffer = (AudioBuffer*)userdata;

    // 如果沒有可用的PCM數(shù)據(jù),直接返回(靜音)
    if(buffer->len <= 0)    return;

    // 第二步:確定本次要填充的長(zhǎng)度(取“設(shè)備期望長(zhǎng)度”和“剩余數(shù)據(jù)長(zhǎng)度”的最小值)
    buffer->pullLen = (len > buffer->len) ? buffer->len : len;

    // 第三步:填充PCM數(shù)據(jù)到音頻設(shè)備緩沖區(qū)
    // SDL_MixAudio:混合音頻(這里直接用最大音量)
    SDL_MixAudio(stream, buffer->data, buffer->pullLen, SDL_MIX_MAXVOLUME);
    
    // 第四步:更新緩沖區(qū)狀態(tài)(已播放的數(shù)據(jù)要跳過)
    buffer->data += buffer->pullLen;  // 指針后移,指向剩余數(shù)據(jù)
    buffer->len -= buffer->pullLen;   // 剩余長(zhǎng)度減少
}

// 線程的核心執(zhí)行函數(shù)(播放邏輯全在這里)
void playThread::run()
{
    // 1. 初始化SDL的音頻子系統(tǒng)
    if(SDL_Init(SDL_INIT_AUDIO)) // 非0表示失敗
    {
        qDebug() << "SDL初始化失敗:" << SDL_GetError();
        return;
    }

    // 2. 加載WAV文件
    SDL_AudioSpec spec;    // 存儲(chǔ)WAV文件的音頻規(guī)格(采樣率、聲道數(shù)等)
    Uint8 *data = nullptr; // 指向WAV文件的PCM原始數(shù)據(jù)
    Uint32 len = 0;        // PCM數(shù)據(jù)的總長(zhǎng)度
    if(!SDL_LoadWAV(FILENAME,&spec,&data,&len)) // 加載失敗返回0
    {
        qDebug() << "加載WAV文件失敗:" << SDL_GetError();
        SDL_Quit(); // 失敗則釋放SDL資源
        return;
    }

    // 3. 配置音頻播放參數(shù)
    spec.samples = 1024;          // 音頻緩沖區(qū)的樣本數(shù)(常用1024)
    spec.callback = pull_audio_data; // 設(shè)置音頻回調(diào)函數(shù)(設(shè)備拉數(shù)據(jù)時(shí)調(diào)用)
    AudioBuffer buffer;           // 創(chuàng)建音頻緩沖區(qū)
    buffer.data = data;           // 綁定PCM數(shù)據(jù)
    buffer.len = len;             // 綁定PCM數(shù)據(jù)長(zhǎng)度
    spec.userdata = &buffer;      // 給回調(diào)函數(shù)傳自定義數(shù)據(jù)(AudioBuffer)

    // 4. 打開音頻設(shè)備
    if(SDL_OpenAudio(&spec,nullptr)) // 失敗返回非0
    {
        qDebug() << "打開音頻設(shè)備失?。? << SDL_GetError();
        SDL_FreeWAV(data); // 釋放WAV數(shù)據(jù)
        SDL_Quit();        // 釋放SDL資源
        return;
    }

    // 5. 開始播放(0=取消暫停,1=暫停)
    SDL_PauseAudio(0);

    // 6. 計(jì)算音頻參數(shù)(用于后續(xù)等待播放完成)
    int sampleSize = SDL_AUDIO_BITSIZE(spec.format); // 每個(gè)樣本的位數(shù)(比如16位)
    // 每個(gè)樣本的字節(jié)數(shù) = (位數(shù) * 聲道數(shù)) / 8(8位=1字節(jié))
    int bytesPerSample = (sampleSize * spec.channels) >> 3;

    // 7. 主線程循環(huán):等待播放完成/收到中斷請(qǐng)求
    while(!isInterruptionRequested()) // 只要沒收到“停止”請(qǐng)求,就循環(huán)
    {
        // 如果還有未播放的PCM數(shù)據(jù),繼續(xù)等待(回調(diào)函數(shù)會(huì)自動(dòng)處理)
        if(buffer.len > 0) continue;

        // 8. 播放完成:等待最后一批數(shù)據(jù)播放完畢
        if(buffer.len <= 0)
        {
            // 計(jì)算最后一批數(shù)據(jù)的播放時(shí)長(zhǎng)(毫秒)
            int samples = buffer.pullLen / bytesPerSample; // 樣本數(shù)
            int ms = samples * 1000 / spec.freq;           // 時(shí)長(zhǎng)=樣本數(shù)/采樣率*1000
            SDL_Delay(ms); // 等待播放完成
            break; // 退出循環(huán)
        }
    }

    // 9. 釋放資源(重中之重!)
    SDL_FreeWAV(data);    // 釋放WAV文件的PCM數(shù)據(jù)
    SDL_CloseAudio();     // 關(guān)閉音頻設(shè)備
    SDL_Quit();           // 釋放SDL所有子系統(tǒng)
}

四、UI 設(shè)計(jì)

在 Qt Designer 中給主窗口拖一個(gè)QPushButton,命名為playBtn,文字默認(rèn)設(shè)為 “開始播放”,無(wú)需其他控件。

五、運(yùn)行效果

  1. 替換代碼中FILENAME為你的 WAV 文件路徑(必須是 WAV 格式,SDL_LoadWAV 不支持 MP3);
  2. 編譯運(yùn)行程序,點(diǎn)擊 “開始播放” 按鈕,就能聽到音頻播放,按鈕文字變成 “停止播放”;
  3. 播放過程中點(diǎn)擊 “停止播放”,音頻會(huì)停止,按鈕文字恢復(fù);
  4. 音頻播放完成后,按鈕會(huì)自動(dòng)恢復(fù)為 “開始播放”。

六、注意事項(xiàng)

  1. 線程安全:音頻播放必須放在子線程!如果直接在 UI 線程執(zhí)行 SDL 邏輯,播放時(shí) UI 會(huì)卡死;
  2. 資源釋放:析構(gòu)函數(shù)中一定要requestInterruption()+quit()+wait(),否則線程可能野跑;
  3. 文件路徑:WAV 文件路徑用絕對(duì)路徑(比如D:/in.wav),路徑中不要有中文和空格;
  4. SDL 版本:一定要選和 Qt 編譯器匹配的 SDL 庫(kù)(比如 Qt 用 MinGW 64 位,SDL 也要下 MinGW 64 位);
  5. 格式限制:SDL_LoadWAV 只支持 WAV 格式,想播放 MP3 需要額外解碼(比如 FFmpeg);
  6. 靜音處理:回調(diào)函數(shù)中先SDL_memset(stream,0,len),否則填充數(shù)據(jù)前會(huì)有雜音。

七、總結(jié)

本文通過 Qt 子線程 + SDL2 的 Pull 模式實(shí)現(xiàn)了 WAV 音頻播放,核心是理解:

  • Qt 線程:避免 UI 阻塞;
  • SDL Pull 模式:音頻設(shè)備主動(dòng)拉取數(shù)據(jù),回調(diào)函數(shù)填充 PCM;
  • 資源管理:線程和 SDL 資源的正確釋放。

以上就是使用Qt+SDL2實(shí)現(xiàn)WAV音頻播放功能的詳細(xì)內(nèi)容,更多關(guān)于Qt SDL2實(shí)現(xiàn)WAV音頻播放的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 6個(gè)變態(tài)的C語(yǔ)言Hello World程序

    6個(gè)變態(tài)的C語(yǔ)言Hello World程序

    這篇文章主要介紹了6個(gè)變態(tài)的C語(yǔ)言Hello World程序,需要的朋友可以參考下
    2016-05-05
  • c語(yǔ)言中字符串分割函數(shù)及實(shí)現(xiàn)方法

    c語(yǔ)言中字符串分割函數(shù)及實(shí)現(xiàn)方法

    下面小編就為大家?guī)?lái)一篇c語(yǔ)言中字符串分割函數(shù)及實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧
    2016-05-05
  • vscode使用cmake時(shí)將命令行參數(shù)傳遞給調(diào)試目標(biāo)的方法

    vscode使用cmake時(shí)將命令行參數(shù)傳遞給調(diào)試目標(biāo)的方法

    這篇文章主要介紹了vscode使用cmake時(shí)將命令行參數(shù)傳遞給調(diào)試目標(biāo),下面介紹了一個(gè)示例,將參數(shù)first_arg, second-arg和third arg傳遞給程序(此處需要注意,third arg中間雖然存在空格,但是仍然被視作一個(gè)參數(shù)),需要的朋友參考下吧
    2024-03-03
  • Opencv繪制最小外接矩形、最小外接圓

    Opencv繪制最小外接矩形、最小外接圓

    這篇文章主要為大家詳細(xì)介紹了Opencv繪制最小外接矩形、最小外接圓的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • c++中string類成員函數(shù)c_str()的用法

    c++中string類成員函數(shù)c_str()的用法

    c_str()函數(shù)返回一個(gè)指向正規(guī)c字符串的指針,內(nèi)容和string類的本身對(duì)象是一樣的,通過string類的c_str()函數(shù)能夠把string對(duì)象轉(zhuǎn)換成c中的字符串的樣式
    2013-09-09
  • 基于C語(yǔ)言實(shí)現(xiàn)三子棋游戲的示例代碼

    基于C語(yǔ)言實(shí)現(xiàn)三子棋游戲的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用C語(yǔ)言數(shù)組實(shí)現(xiàn)簡(jiǎn)單的三子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • C語(yǔ)言之sizeof與strlen的使用及區(qū)別

    C語(yǔ)言之sizeof與strlen的使用及區(qū)別

    這篇文章主要介紹了C語(yǔ)言之sizeof與strlen的使用及區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • c++編寫String類代碼實(shí)例

    c++編寫String類代碼實(shí)例

    這篇文章主要介紹了c++編寫String類,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • xxx_cast類型轉(zhuǎn)換的實(shí)現(xiàn)方法

    xxx_cast類型轉(zhuǎn)換的實(shí)現(xiàn)方法

    下面小編就為大家?guī)?lái)一篇xxx_cast類型轉(zhuǎn)換的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧
    2016-12-12
  • Qt 元對(duì)象系統(tǒng)中QMetaEnum的應(yīng)用

    Qt 元對(duì)象系統(tǒng)中QMetaEnum的應(yīng)用

    本文主要介紹了Qt 元對(duì)象系統(tǒng)中QMetaEnum的應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-11-11

最新評(píng)論

濮阳市| 商城县| 长泰县| 正蓝旗| 蒲江县| 霸州市| 六盘水市| 克东县| 屯留县| 启东市| 西安市| 宁南县| 博兴县| 井陉县| 定安县| 鹤壁市| 揭阳市| 肥乡县| 平和县| 卓资县| 鹰潭市| 曲靖市| 华宁县| 栾川县| 平果县| 苍南县| 肇东市| 奇台县| 文化| 长沙县| 镇平县| 奎屯市| 岳普湖县| 四子王旗| 马公市| 象州县| 望都县| 鸡泽县| 深泽县| 徐水县| 寻乌县|