使用的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项目
修改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();
}