本文介绍了如何在不使用SDL_mixer的情况下,只使用SDL2进行声音的播放和录制。
我在移植我的引擎到安卓平台的时候发现SDL_mixer的CMake有问题,而且这个玩意经常整些bug。于是翻了翻SDL的wiki,百度了之后决定使用SDL2进行一个音频的播放。
播放音频的原理
SDL2播放音频的原理非常底层:首先需要打开一个音频设备,然后对这个设备写入音频数据就可以播放了。
示例
在进行SDL_Init(SDL_INIT_AUDIO)
初始化Audio模块后,我们首先载入一个WAV文件:
|
|
SDL本身只能通过SDL_LoadWAV
函数载入WAV格式文件。如果你想要加载Ogg可以使用libvorbis或者stb_vorbis。其他的格式找对应的库就行了。
读入之后我们获得了声音的数据soundData
以及数据的大小soundLen
,和音频的格式soundSpec
。不要忘记SDL_FreeWAV()
来释放soundData
。
接下来我们打开一个音频输出设备。这里有两种方法:
使用SDL_OpenAudio
打开
这是推荐的方法,这个方法的优点是不易用错,缺点是只能打开一个音频设备,如果你电脑上有多个音频设备你想要打开,可以使用SDL_OpenAudioDevice
。
|
|
第一个参数是我们希望的AudioSpec,第二个参数是它有的AudioSpec,是一个输出参数。我们这里不需要这个参数,直接给NULL。
SDL会自己帮我们从soundSpec转换为设备的格式,所以不需要担心。
在文件结束的时候不要忘记SDL_CloseAudio()
关闭设备。
使用SDL_OpenAudioDevice
打开
使用SDL_OpenAudioDevice
的话,可以打卡多个音频设备。但是也会打开一些不存在的音频设备。在我的Mac电脑上就打开了不存在的音频设备,导致没办法输出声音。但是我看别人的视频确实是有成功的。
这个函数原型如下:
|
|
device
:要打开的设备的名字,为NULL就是打开一个默认的。iscapture
:要打开的设备是否是录音设备。在SDL2.0.5及之后,SDL可以打开录音设备来录音。desired
和obtained
:和SDL_OpenAudio()
的参数一样,一个是我们希望打开的设备格式,一个是设备真正的格式allowed_changes
:是否允许改变设备的某些格式。一般直接给0表示不允许。
这个函数返回的是一个设备ID,如果ID < 0就是打开失败了。其实ID总是大于等于2的。
使用这个方法打开设备后,后面所有对设备操作的函数都要加上Device
,比如SDL_CloseAudioDevice()
,SDL_PauseAudioDevice()
等。
不要忘记使用SDL_CloseAudioDevice(id)
来关闭。
SDL_AudioSpec
打开音频我们需要一个SDL_AudioSpec
,这里我建议你直接传通过SDL_LoadWAV
得到的spec,这样不需要进行音频的格式转换。
不过我们这里还是看一下初始化它时需要填充的各个成员,以便于后面讲格式转换:
channels
:声道数- 1: 单声道
- 2: 双声道(立体声)
- 4: 四声道
- 6: 5.1声道,是用于影院的那种。
format
:设备接收的音频数据的格式,这里AUDIO_F32
是指32位浮点数格式freq
:播放频率,即每秒送往音频设备的声音帧数。44100是CD频率,48000是DVD频率。不建议高于48000,这会造成更多的内存和CPU损耗。samples
:音频采样帧中的音频缓冲区大小,只能是2的倍数,一般给个4096就行了。callback
:回调函数,当音频设备没有音频播放的时候就会调用这个函数。我们可以在这个函数里面给他音频数据userdata
:用户自定义数据。
在回调函数中写入音频数据
接下来我们要配置回调函数,让其在音频空闲的时候写入数据。为此,我们需要一个结构体来封装我们需要的音频数据信息:
|
|
然后配置AudioSpec的userdata
:
|
|
然后编写回调函数:
|
|
这里我是让音频只播放一遍。你也可以选择让他循环播放。
最后,我们要让音频设备开始工作:
|
|
这个函数参数如果是1则是暂停音频设备。
至此,所有的工作就完成了。然后你可以选择Delay个1.5秒来听听播放的声音。
整个代码实例在这里
音频格式的转换
当你打开的音频设备的格式和你的音频文件格式不一样的时候,你需要进行音频格式的转换。
SDL2新推出了SDL_AudioStream
,而老的SDL_AudioCVT
不再推荐使用。
这里有SDL的官方教程
这里就简单说一下吧。首先创建一个AudioStream:
|
|
前三个参数是关于音频的各种格式,后三个是音频设备的各种格式。
然后我们要把音频数据送给stream进行转换:
|
|
然后得到转换后的数据大小(字节为单位):
|
|
然后我们拿出数据:
|
|
这样数据就拿出来了。如果你分多次放入数据,你必须多次拿出数据,因为每次使用SDL_AudioStreamPut
时SDL会将你的数据大小记下来,等到拿出来时也只是给你这一块数据的转换结果。
录制声音
原理和播放声音一样:打开录音设备,然后从设备中读取音频信息即可。
打开录音设备要使用SDL_OpenAudioDevice()
。
这里给个录音的回调函数作为例子:
|
|
然后你就可以对这个音频数据为所欲为了,比如传给播放设备播放出来,或者保存到本地等。