使用SDL播放PCM文件

59 阅读3分钟

使用的Linux发行版

➜  ~ lsb_release -a
No LSB modules are available.
Distributor ID:	Linuxmint
Description:	Linux Mint 22
Release:	22
Codename:	wilma

下载SDL

SDL简介

SDL(Simple DirectMedia Layer)是一个跨平台的多媒体库,用于访问音频、键盘、鼠标和图形硬件。它提供了一种简单的方式来创建2D游戏和多媒体应用程序。

开始安装

# 基本开发包
sudo apt-get install libsdl2-dev

#图像开发包
sudo apt-get install libsdl2-image-dev

#音频开发包
sudo apt-get install libsdl2-mixer-dev

准备PCM文件

脉冲编码调制(Pulse-code modulation,简称PCM)是一种非压缩的音频编码格式。此格式是CD-DA的标准。在计算机中,使用 PCM 编码的音频可以直接以原始音频格式 储存。

# 我们可以使用FFmpeg将其他音频文件转为PCM文件

ffmpeg -i test.mp3 -f s16le -ar 44100 -ac 2 -acodec pcm_s16le output.pcm

创建一个Qt项目

image.png

image.png

修改CMakeLists.txt

添加SDL库

target_link_libraries(play_pcm PRIVATE Qt${QT_VERSION_MAJOR}::Widgets SDL2)

代码

播放

创建一个文件里面保存播放的逻辑,用来开启新的一个线程进行播放。

// playthread.h

#ifndef PLAYTHREAD_H
#define PLAYTHREAD_H

#include <QThread>

class PlayThread : public QThread
{
    Q_OBJECT
private:
    void run();
public:
    explicit PlayThread(QObject *parent = nullptr);
    ~PlayThread();
signals:
};

#endif // PLAYTHREAD_H


实现文件

// playthread.cpp

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

#include "playthread.h"

PlayThread::PlayThread(QObject *parent)
    : QThread{parent}
{
    // 监听线程结束,如果结束调用析构函数
    connect(this, &PlayThread::finished, this, &PlayThread::deleteLater);
}



PlayThread::~PlayThread() {
    disconnect();
    requestInterruption();
    quit();
    wait();

    qDebug() << "调用了析构函数";

}

int bufferLen;
char* bufferdata;

// 等待音频设备回调
void pull_audio_data(void *userdata,
                     Uint8 * stream,  // 往stream指向的地址填充PCM数据
                     int len   // 填充的数据大小
                     ) {
    // 清空stream, 用0填充,填充len大小
    SDL_memset(stream, 0, len);

    if(bufferLen <= 0) return;  // 没有数据

    // 取len  bufferLen的最小值,实际传给SDL的长度
    int realLen = (len > bufferLen) ? bufferLen : len;

    // 填充数据

    SDL_MixAudio(stream, (Uint8 *)bufferdata, realLen, SDL_MIX_MAXVOLUME);

    bufferdata += realLen;  // 指针往后走消耗的长度,后面是SDL没读取的文件缓冲区数
    bufferLen -= realLen;  // SDL每消耗一部分就减去消耗的长度


}

void PlayThread::run() {
    // 打印下SDL的版本号
    SDL_version version;
    SDL_VERSION(&version);
    qDebug() << version.major << version.minor << version.patch;


    // 初始化SDL的Audio子系统
    if (SDL_Init(SDL_INIT_AUDIO) < 0) {
        // 小于0有错误。等于0初始化成功

        qDebug() << "SDL_Init error:" << SDL_GetError();

        return;
    }

    // 音频参数

    SDL_AudioSpec spec;
    spec.freq = 44100;  // 设置采样率
    spec.format = AUDIO_S16;  // 格式
    spec.channels = 2;  // 声道数
    spec.callback = pull_audio_data;  // 回调函数,当播放时候SDL会调用这个函数
    spec.samples = 1024;   // 音频缓冲区的样本数   16bit * 2 * 1024 缓冲区大小

    // 打开设备

    if (SDL_OpenAudio(&spec, nullptr) < 0) {
        // 小于0有错误。等于0成功
        qDebug() << "SDL_OpenAudio error:" << SDL_GetError();
        // 清除所有的子系统
        SDL_Quit();
        return;
    }


    // 打开文件
    QFile file("/home/gillbert/Music/output.pcm");

    if(!file.open(QFile::ReadOnly)) {
        // 小于0有错误。等于0成功
        qDebug() << "文件打开失败";
        // 关闭设备
        SDL_CloseAudio();

        // 清除所有的子系统
        SDL_Quit();
        return;
    }




    // 开始播放
    SDL_PauseAudio(0);  // 0取消暂停表示开始播放

    char data[4096 * 4];
    while(!isInterruptionRequested()) {
        bufferLen = file.read(data, 4096 * 4);  // 和SDL缓冲区大小一致

        if (bufferLen <= 0) {
            break;
        }

        bufferdata = data;

        while (bufferLen > 0) {  // 只要SDL没有消耗完开始读取的数据,就延迟等待
            SDL_Delay(1);  // 延迟一段时间
        }

    }


    // 关闭文件
    file.close();

    // 关闭设备
    SDL_CloseAudio();

    // 清除所有的子系统
    SDL_Quit();
}



点击播放的槽函数代码

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

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_playButton_clicked()
{
    PlayThread* playThread = new PlayThread(this);

    playThread->start();

}

image.png

测试

image.png

参考文章