一、使用命令行播放
$ ffplay test.wav
二、使用 SDL2 API 播放
1、创建 Qt 工程,参考 Mac开发环境搭建
2、在 .pro 文件添加 SDL2 头文件和静态库路径,
macx {
INCLUDEPATH += /usr/local/Cellar/sdl2/2.0.14_1/include
LIBS += -L /usr/local/Cellar/sdl2/2.0.14_1/lib -lSDL2
}
3、导入 SDL 头文件
#include <SDL2/SDL.h>
4、初始化 SDL2 子系统,我们仅仅播放音频,参数只传入 SDL_INIT_AUDIO 即可。
if (SDL_Init(SDL_INIT_AUDIO)) {
qDebug() << "初始化音频子系统失败";
return;
}
5、加载 WAV 文件,SDL2 提供了加载 WAV 的方法
if (!SDL_LoadWAV(WAV_FILE_NAME, &spec, &data, &len)) {
qDebug() << "加载 WAV 文件失败:" << WAV_FILE_NAME << SDL_GetError();
SDL_Quit();
return;
}
SDL_LoadWAV 是一个便利宏,作用是读取音频数据格式并且把全部音频数据加载进内存:
#define SDL_LoadWAV(file, spec, audio_buf, audio_len) \
SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"),1, spec,audio_buf,audio_len)
参数:
file:加载的 WAV 文件名称;
spec:SDL_AudioSpec 结构体地址,函数调用成功 spec 的 freq、channels、format等成员变量会被赋值成 WAV 文件中音频数据的参数;
audio_buffer:加载进内存的全部音频数据;
audio_len:音频数据长度,单位字节;
实际调用的函数是 SDL_LoadWAV_RW( ):
SDL_AudioSpec* SDL_LoadWAV_RW(SDL_RWops * src,
int freesrc,
SDL_AudioSpec * spec,
Uint8 ** audio_buf,
Uint32 * audio_len);
参数:
src:加载的 WAV 文件名称;
freesrc:如果非0,函数返回前自动关闭释放资源文件,此处传1即可;
spec:同上;
audio_buffer:同上;
audio_len:同上;
6、打开音频设备
if (SDL_OpenAudio(&spec, nullptr)) {
qDebug() << "打开音频设备失败";
SDL_FreeWAV(data);
SDL_Quit();
return;
}
7、开始播放
SDL_PauseAudio(0);
参数:
pause_on:打开音频设备后传入0开始播放音频,1暂停播放;
参数 pause_on 设置为0开始调用回调函数:
// 等待音频设备回调,调用多次,
void fill_audio(void *userdata, Uint8 * stream, int len)
{
qDebug() << "fill_audio len:" << len;
AudioBuffer *buffer = (AudioBuffer *) userdata;
SDL_memset(stream, 0, len);
if (buffer->len <= 0) 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;
}
8、播放结束释放音频数据,关闭音频设备,清除 SDL 子系统:
// 释放 WAV 数据
SDL_FreeWAV(data);
// 关闭音频设备
SDL_CloseAudio();
// 清除所有子系统
SDL_Quit();
完整代码:
#include <SDL2/SDL.h>
#include <QFile>
#include <QDebug>
#define WAV_FILE_NAME "/Users/mac/Downloads/music/test.wav"
typedef struct AudioBuffer {
int len = 0;
int pullLen = 0;
uint8_t *data = nullptr;
} AudioBuffer;
// 等待音频设备回调,调用多次,
void fill_audio(void *userdata, Uint8 * stream, int len)
{
qDebug() << "fill_audio len:" << len;
AudioBuffer *buffer = (AudioBuffer *) userdata;
SDL_memset(stream, 0, len);
if (buffer->len <= 0) 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;
}
PlayThread::PlayThread(QObject *parent) : QThread(parent)
{
connect(this, &PlayThread::finished, this, &PlayThread::deleteLater);
}
PlayThread::~PlayThread()
{
requestInterruption();
quit();
wait();
qDebug() << "PlayThread 析构了~";
}
void PlayThread::run()
{
// 初始化 SDL 音频子系统
if (SDL_Init(SDL_INIT_AUDIO)) {
qDebug() << "初始化音频子系统失败";
return;
}
Uint8 *data;
Uint32 len;
SDL_AudioSpec spec;
// 加载 WAV 文件
if (!SDL_LoadWAV(WAV_FILE_NAME, &spec, &data, &len)) {
qDebug() << "加载 WAV 文件失败:" << WAV_FILE_NAME << SDL_GetError();
SDL_Quit();
return;
}
AudioBuffer buffer;
buffer.data = data;
buffer.len = len;
spec.userdata = &buffer;
spec.callback = fill_audio;
// 打开音频设备
if (SDL_OpenAudio(&spec, nullptr)) {
qDebug() << "打开音频设备失败";
SDL_FreeWAV(data);
SDL_Quit();
return;
}
// 开始播放
SDL_PauseAudio(0);
while (!isInterruptionRequested()) {
if (buffer.len > 0) continue;
// 每一个样本大小
int size = SDL_AUDIO_BITSIZE(spec.format) * spec.channels / 8;
// 剩余样本数量
int leftSample = buffer.pullLen / size;
// 剩余样本播放时间ms
int ms = leftSample * 1000 / spec.freq;
SDL_Delay(ms);
break;
}
// 释放 WAV 数据
SDL_FreeWAV(data);
// 关闭音频设备
SDL_CloseAudio();
// 清除所有子系统
SDL_Quit();
}
网友评论