SDL2
我的在win下,下载的是SDL2-devel-2.0.14-mingw.tar.gz (MinGW 32/64-bit)
如果是MAC就不用自己配置环境变量这么麻烦
Win环境下配:
项目配置:
macx {
SDL_HOME = /usr/local/Cellar/sdl2/2.0.14_1
}
win32 {
SDL_HOME = G:\SDL2-2.0.14\x86_64-w64-mingw32
}
INCLUDEPATH += $${SDL_HOME}/include
LIBS += -L $${SDL_HOME}/lib \
-lSDL2 \
-lSDL2_test \
-lSDL2main
地址路径随自己改
配置完毕就差不多可以整理代码了.
PS:可能遇到(我遇到)的问题,
一点击运行就提示, 程序运行奔溃。 这个时候需要检查环境变量是否已经配置好。 在项目中清除所有的已经构建的, 再次运行。
Demo
导入SDL2
extern "C" {
#include
}
配置基本信息
#ifdef Q_OS_WIN
#define FILEPATH "G:/Resource/"
#define FILENAME "record_to_pcm.pcm"
#define FORMAT AUDIO_S16SYS
#else
#define FILEPATH "/Users/lumi/Desktop/"
#define FILENAME "record_to_pcm.pcm"
#define FORMAT AUDIO_F32
#endif
//采样率
#define SAMPLE_RATE 44100
//位深度
#define SAMPLE_BIT_DEPTH 16
//声道数
#define CHANNELS 2
//音频缓存区的样本大小
#define SAMPLES 4096
//每个样本占多少字节 (1byte = 8bit)
#define SAMPLE_PER_BYTES ((SAMPLE_BIT_DEPTH * CHANNELS) / 8)
//文件缓冲区的大小 (样本大小 * 每个样本占多少字节)
#define BUFFER_SIZE (SAMPLES * SAMPLE_PER_BYTES)
配置回调函数
int bufferLen = 0;
Uint8 *bufferData = nullptr;
// 等待音频设备回调(会回调多次)
void pull_audio_data (void *userdata,
// 需要往stream中填充PCM数据
Uint8 *stream,
// 希望填充的大小(samples * format * channels / 8)
int len) {
qDebug() << "pull_audio_data" << len;
if (stream == nullptr) {
return;
}
SDL_memset(stream, 0, len);
// 数据还没有准备好
if(bufferLen <= 0) {
return;
}
len = len > bufferLen ? bufferLen : len;
SDL_MixAudio(stream, bufferData, len, SDL_MIX_MAXVOLUME);
bufferData += len;
bufferLen -= len;
}
SDL播放PCM
void Playthread::run() {
if(SDL_Init(SDL_INIT_EVERYTHING)) {
qDebug() << "SDL_Init error : " << SDL_GetError();
return;
}
SDL_AudioSpec audioSpec;
audioSpec.freq = SAMPLE_RATE;
audioSpec.format = FORMAT;
audioSpec.channels = CHANNELS;
audioSpec.samples = SAMPLES;
audioSpec.callback = pull_audio_data;
audioSpec.userdata = nullptr;
if(SDL_OpenAudio(&audioSpec, nullptr)) {
qDebug() << "SDL_OpenAudio err" << SDL_GetError();
SDL_Quit();
return;
}
// 打开文件
QString filename = FILEPATH;
filename += FILENAME;
QFile file(filename);
if (!file.open(QFile::ReadOnly)) {
qDebug() << "file open fail : " << filename;
SDL_CloseAudio();
SDL_Quit();
return;
}
// 开始播放(0是取消暂停)
SDL_PauseAudio(0);
// 存放从文件中读取的数据
Uint8 data[BUFFER_SIZE];
while(!isInterruptionRequested()) {
if (bufferLen > 0) {
continue;
}
bufferLen = file.read((char *)data, BUFFER_SIZE);
//文件数据读取完毕
if (bufferLen <= 0) {
break;
}
bufferData = data;
}
// 关闭文件
file.close();
// 关闭设备
SDL_CloseAudio();
// 清除所有子系统
SDL_Quit();
}
学习研究
SDL_AudioSpec
/**
* 此结构中的计算值由 SDL_OpenAudio() 计算。
*
* 对于多声道音频,默认的 SDL 声道映射为:
* 2:FL FR(立体声)
* 3:FL FR LFE(2.1 环绕声)
* 4:FL FR BL BR(四边形)
* 5:FL FR FC BL BR(四边形 + 中心)
* 6:FL FR FC LFE SL SR(5.1环绕声-最后两个也可以是BL BR)
* 7:FL FR FC LFE BC SL SR(6.1 环绕声)
* 8:FL FR FC LFE BL BR SL SR(7.1 环绕声)
*/
typedef struct SDL_AudioSpec
{
int freq; /**< DSP 频率 -- 每秒采样数 */
SDL_AudioFormat format; /**< 音频数据格式 */
Uint8 channels; /**< 声道数:1个单声道,2个立体声*/
Uint8 silence; /**< 音频缓冲静音值(计算)*/
Uint16 samples; /**< 样本帧中的音频缓冲区大小(总样本除以通道数)*/
Uint16 padding; /**< 某些编译环境需要 */
uint32 size; /**< 以字节为单位的音频缓冲区大小(计算)*/
SDL_AudioCallback callback; /**< 提供音频设备的回调(NULL 以使用 SDL_QueueAudio())。 */
void *userdata;; /**< 传递给回调的用户数据(NULL 回调时忽略)。 */
} SDL_AudioSpec;
SDL_AudioCallback
/**
* 当音频设备需要更多数据时调用此函数。
*
* param userdata 保存在 SDL_AudioSpec 结构中的应用程序特定参数
* param stream 指向音频数据缓冲区的指针。
* 参数 len 该缓冲区的长度(以字节为单位)。
*
* 一旦回调返回,缓冲区将不再有效。立体声样本存储在 LRLRLR 排序中。
*
* 如果您愿意,您可以选择避免回调并改用 SDL_QueueAudio()。 只需使用 NULL 回调打开您的音频设备。
*/
typedef void (SDLCALL * SDL_AudioCallback) (void *userdata,
Uint8 * stream,
int len);
SDL_QueueAudio()
SDL offers two ways to feed audio to the device:
`you can either supply a callback that SDL triggers with some frequency to obtain more audio (pull method)`,
or you can supply no callback, and then SDL will expect you to supply data at regular intervals (push method) with this function.
SDL 提供两种向设备馈送音频的方法:
`您可以提供一个回调,SDL 以某种频率触发以获取更多音频(拉动方法)`,
或者您可以不提供回调,然后 SDL 将期望您提供数据具有此功能的定期间隔(推送方法)。
extern DECLSPEC int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID dev, const void *data, Uint32 len);
SDL播放音频有2种模式:
Push(推):【程序】主动推送数据给【音频设备】
Pull(拉):【音频设备】主动向【程序】拉取数据
所以关于SDL_AudioCallback callback; 我们需要使用pull的方式, 也就是设置callback回调。 这个回调会以某种频率触发更多的音频。
SDL_OpenAudio
该函数打开所需参数的音频设备,成功返回0,将实际的硬件参数放入得到的参数指向的obtained中。
`如果obtained 为NULL,则传递给回调函数的音频数据将保证为请求的格式,并在必要时自动转换为硬件音频格式。`
如果无法打开音频设备或无法设置音频线程,则此函数返回 -1.
...
音频设备在打开时开始播放静音,当您准备好调用音频回调函数时,应通过调用 \c SDL_PauseAudio(0) 启用播放。
由于音频驱动程序可能会修改请求的音频缓冲区大小,因此您应该在打开音频设备后分配所有本地混合缓冲区
extern DECLSPEC int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired,
SDL_AudioSpec * obtained);
对应的有打开就有关闭
extern DECLSPEC void SDLCALL SDL_CloseAudio(void);
简单粗暴理解,
第一个SDL_AudioSpec * desired是我们需要打开的文件对应的相关的SDL_AudioSpec 格式配置,
第二个SDL_AudioSpec * obtained是我们对应的音频设备(硬件)的格式配置, 传null自动获取
SDL_PauseAudio
/**
* \name 暂停音频功能
*
* 这些函数暂停和取消暂停音频回调处理。
* 打开音频后,应使用参数 0 调用它们
* 设备开始播放声音。 这样你就可以安全地初始化
* 打开音频设备后回调函数的数据。
* 在暂停期间静音将写入音频设备。
*/
extern DECLSPEC void SDLCALL SDL_PauseAudio(int pause_on);
也就是打开了之后, 使用参数 0 调用它们, 开始播放
SDL_MixAudio
#define SDL_MIX_MAXVOLUME 128
/**
* This takes two audio buffers of the playing audio format and mixes
* them, performing addition, volume adjustment, and overflow clipping.
* The volume ranges from 0 - 128, and should be set to ::SDL_MIX_MAXVOLUME
* for full audio volume. Note this does not change hardware volume.
* This is provided for convenience -- you can mix your own audio data.
*/
* 这需要播放音频格式的两个音频缓冲区并将它们混合,执行添加、音量调整和溢出剪辑。
* 音量范围为 0 - 128,应设置为 ::SDL_MIX_MAXVOLUME 以获得完整的音频音量。 请注意,这不会更改硬件音量。
* 这是为了方便而提供的——您可以混合自己的音频数据。
extern DECLSPEC void SDLCALL SDL_MixAudio(Uint8 * dst,
const Uint8 * src,
Uint32 len,
int volume);
总结:
1.SDL_Init(SDL_INIT_AUDIO)
2.file.open
3.SDL_OpenAudio
4.SDL_PauseAudio(0)pull_audio_data
SDL_memset
SDL_MixAudio5.file.close
6.SDL_CloseAudio
7.SDL_Quit
播放WAV
播放wav和播放PCM差不多, 整体流程总结为
1.SDL_Init(SDL_INIT_AUDIO)
2.SDL_LoadWAV
3.SDL_OpenAudio
4.SDL_PauseAudio(0)pull_audio_data
SDL_memset
SDL_MixAudio5.SDL_FreeWAV
6.SDL_CloseAudio
7.SDL_Quit
就是打开pcm文件和打开wav不同的方式。
void Playthread::run() {
if(SDL_Init(SDL_INIT_AUDIO)) {
qDebug() << "SDL_Init error : " << SDL_GetError();
return;
}
SDL_AudioSpec audioSpec;
audioSpec.freq = SAMPLE_RATE;
audioSpec.format = FORMAT;
audioSpec.channels = CHANNELS;
audioSpec.samples = SAMPLES;
audioSpec.callback = pull_audio_data;
audioSpec.userdata = (void *)3;
if(SDL_OpenAudio(&audioSpec, nullptr)) {
qDebug() << "SDL_OpenAudio err" << SDL_GetError();
SDL_Quit();
return;
}
// 打开文件
QString filename = FILEPATH;
filename += FILENAME;
QFile file(filename);
if (!file.open(QFile::ReadOnly)) {
qDebug() << "file open fail : " << filename;
SDL_CloseAudio();
SDL_Quit();
return;
}
// 开始播放(0是取消暂停)
SDL_PauseAudio(0);
// 存放从文件中读取的数据
Uint8 data[BUFFER_SIZE];
while(!isInterruptionRequested()) {
if (bufferLen > 0) {
continue;
}
bufferLen = file.read((char *)data, BUFFER_SIZE);
//文件数据读取完毕
if (bufferLen <= 0) {
break;
}
bufferData = data;
}
// 关闭文件
file.close();
// 关闭设备
SDL_CloseAudio();
// 清除所有子系统
SDL_Quit();
}
void pull_audio_data (void *userdata,
// 需要往stream中填充PCM数据
Uint8 *stream,
// 希望填充的大小(samples * format * channels / 8)
int len) {
qDebug() << "pull_audio_data userdata : " << userdata;
qDebug() << "pull_audio_data stream : " << stream;
qDebug() << "pull_audio_data len : " << len;
if (stream == nullptr) {
return;
}
SDL_memset(stream, 0, len);
// 数据还没有准备好
if(bufferLen <= 0) {
return;
}
len = len > bufferLen ? bufferLen : len;
SDL_MixAudio(stream, bufferData, len, SDL_MIX_MAXVOLUME);
bufferData += len;
bufferLen -= len;
}