SDL播放音频
代码
1  | 
  | 
(一)
SDL_AudioSpec 是 SDL 库中的一个结构体,用于描述音频设备的配置和格式。它包含了音频播放和录制所需的各种参数,包括采样率、音频格式、通道数、样本大小、回调函数等。
以下是 SDL_AudioSpec 结构体的主要成员及其解释:
- 
int freq:音频采样率,表示每秒钟播放或录制的样本数。常见的值是 44100(CD 音质)、48000(DVD 音质)等。- 代码中示例:
spec.freq = 44100;。 
 - 代码中示例:
 - 
SDL_AudioFormat format:音频格式,用于指定每个音频样本的格式(例如 8 位、16 位等)和字节序。常用的值有AUDIO_U8(无符号 8 位 PCM 数据)、AUDIO_S16SYS(有符号 16 位 PCM 数据,系统字节序)等。- 代码中示例:
spec.format = AUDIO_S16SYS;。 
 - 代码中示例:
 - 
Uint8 channels:音频通道数,1 表示单声道(Mono),2 表示立体声(Stereo)。- 代码中示例:
spec.channels = 2;。 
 - 代码中示例:
 - 
Uint8 silence:静音值。当音频缓冲区为空时,SDL 用于填充静音的值。对于 8 位音频格式,这通常是 128,对于 16 位音频格式,这通常是 0。- 代码中示例:
spec.silence = 0;。 
 - 代码中示例:
 - 
Uint16 samples:每个音频缓冲区的样本数,通常是 2 的幂。这个值决定了音频回调函数的调用频率,值越大,延迟越高,值越小,延迟越低,但会增加回调的调用频率。- 代码中示例:
spec.samples = 1024;。 
 - 代码中示例:
 - 
SDL_AudioCallback callback:音频回调函数指针。当音频设备需要更多数据时,SDL 会调用这个回调函数,以便用户提供数据。- 代码中示例:
spec.callback = AudioCallBack;。 
 - 代码中示例:
 
- 类似于
Qt中的TimerEvent,会每时每刻调用 
void *userdata:用户数据指针。在回调函数中传递给用户的自定义数据,用户可以用它来传递上下文信息或者其他所需的数据。- 代码中示例:
spec.userdata = &ifs;,其中ifs是指向输入文件流的指针。 
- 代码中示例:
 
SDL_AudioSpec 结构体的这些成员允许开发者自定义音频设备的行为,使得 SDL 可以灵活地支持各种音频硬件和格式。通过适当配置这些参数,可以确保音频的播放和录制与期望的音频质量和性能相符。
【关于PCM 8位和16位的区别】
8位和16位 PCM 数据的主要区别在于 量化精度,即每个样本使用多少位来表示声音的振幅。
- 8位 PCM 数据:
- 使用 8 位(1 字节)来表示每个音频样本。
 - 由于每个样本只有 8 位,量化的数值范围是 0 到 255(无符号)或 -128 到 127(有符号)。
 - 8 位音频的精度较低,噪声较大,音质相对较差,通常用于简单的声音效果或需要降低数据量的场景。
 - 文件大小相对较小,因为每个样本的字节数较少。
 
 - 16位 PCM 数据:
- 使用 16 位(2 字节)来表示每个音频样本。
 - 量化的数值范围是 -32768 到 32767(有符号整数),这提供了更大的动态范围和更高的音频精度。
 - 16 位音频的音质明显优于 8 位音频,因为它能更精确地表示声音的细节和动态变化,通常用于高质量的音频文件,如音频 CD(44.1 kHz, 16-bit PCM)。
 - 文件大小较大,因为每个样本需要 2 个字节。
 
 
总结
- 8位 PCM:音质较低,适合用于简单音效或数据传输速率较低的场景。
 - 16位 PCM:音质较高,适合用于音乐、视频和其他对音质有较高要求的场合。
 
(二)
SDL_OpenAudio 是 SDL(Simple DirectMedia Layer)库中的一个函数,用于打开音频设备并进行音频播放的初始化。这个函数配置音频硬件的播放参数,并分配资源,使音频设备能够开始工作。
SDL_OpenAudio 函数的作用
- 配置音频设备:它使用一个 
SDL_AudioSpec结构体来设置音频设备的参数,比如采样率、音频格式、通道数和缓冲区大小等。 - 初始化音频设备:根据提供的配置参数,SDL 会初始化音频硬件或虚拟音频设备,使其准备好播放音频。
 - 注册回调函数:在初始化过程中,SDL 注册一个回调函数(
SDL_AudioSpec中的callback成员),当音频设备需要新的音频数据时,SDL 会调用这个回调函数。 
函数签名
1  | int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained);  | 
参数说明
desired(指针):指向SDL_AudioSpec结构体,该结构体描述了你希望音频设备采用的音频参数(如采样率、格式、通道数、缓冲区大小等)。- 你在这个结构体中指定你所期望的音频设置,包括回调函数。
 - 这是一个输入参数,SDL 使用这些信息来尝试匹配最接近的硬件配置。
 
obtained(指针):指向一个SDL_AudioSpec结构体,用于接收实际音频设备支持的参数。- 如果你不关心实际的音频参数,可以传入 
nullptr。 - 这是一个输出参数,SDL 将音频设备实际使用的参数写入该结构体中。通常用于检测音频设备的实际配置与期望配置之间的差异。
 
- 如果你不关心实际的音频参数,可以传入 
 
(三)
【关于callback函数什么时候调用】
- 初始化 SDL 音频子系统(
SDL_Init(SDL_INIT_AUDIO);)。 - 配置并打开音频设备(
SDL_OpenAudio(&spec, nullptr);)。 - 开始音频播放(
SDL_PauseAudio(0);):这是启动音频播放的关键点。此时,SDL 开始工作,音频设备开始请求数据,触发AudioCallBack回调函数。 
(四)
这个回调函数 AudioCallBack 是在 SDL 音频设备需要新的音频数据时调用的。它的主要任务是从音频文件中读取数据并填充到音频缓冲区(stream),以确保音频播放的连续性。以下是这个回调函数的详细解释:
函数参数解释
1  | void AudioCallBack(void* userdata, Uint8* stream, int len)  | 
void* userdata:用户自定义的数据指针。在设置音频设备时(SDL_AudioSpec的userdata成员),你可以传递任何需要在回调函数中使用的数据。在这个例子中,它是一个指向ifstream的指针,用于读取音频文件数据。Uint8* stream:指向音频缓冲区的指针。这个缓冲区是 SDL 用来播放音频的。当 SDL 需要更多数据时,它会调用回调函数并传递这个缓冲区指针。回调函数的任务是将新的音频数据填充到这个缓冲区。int len:需要填充到缓冲区stream中的音频数据长度(字节数)。这个长度由 SDL 提供,通常由SDL_AudioSpec结构体中的samples字段决定。
函数执行流程
- 
清零音频缓冲区:
1
SDL_memset(stream, 0, len);
SDL_memset函数将音频缓冲区stream的所有字节设置为 0。这个步骤是为了确保缓冲区在填充新数据之前是清零的,防止之前的数据残留。对于一些应用场景,这可以避免噪声或不期望的声音出现。 - 
读取音频数据:
1
2auto ifs = (ifstream *)userdata;
ifs->read((char*)stream, len);auto ifs = (ifstream *)userdata;:将userdata转换为ifstream指针。这里userdata是在初始化SDL_AudioSpec时传入的,指向一个打开的音频文件流(ifstream)。ifs->read((char*)stream, len);:从音频文件中读取len字节的数据并填充到stream缓冲区。这个操作将音频文件中的 PCM 数据读入到 SDL 的播放缓冲区中。
 - 
检查文件是否读完:
1
2
3
4
5if (ifs->gcount() <= 0)
{
cout << "end" << endl;
SDL_PauseAudio(1); // 暂停播放
}ifs->gcount():返回read操作实际读取的字节数。如果返回值小于或等于 0,表示文件已经读完或者没有更多数据可以读取。- 如果文件读取完毕(
ifs->gcount() <= 0),打印 “end” 表示音频文件已结束,然后调用SDL_PauseAudio(1);暂停音频播放。暂停播放是为了避免播放空的缓冲区,产生噪音或无效的音频输出。 
 
总结
- 回调函数的主要任务是从音频文件读取数据,并填充 SDL 的音频缓冲区,确保音频的连续播放。
 userdata用于传递自定义数据(如音频文件流),以便在回调函数中使用。- 当音频数据用尽时,回调函数暂停音频播放,防止播放空数据。
 
这确保了音频播放的稳定性和数据的准确性。
(五)
【播放过程的详细解释】
- 音频设备开始工作:调用 
SDL_PauseAudio(0)后,SDL 的音频子系统开始工作,音频设备会开始播放缓冲区中的数据。 - 音频数据的填充:当音频设备播放时,它会消耗缓冲区中的音频数据。当缓冲区中剩余的数据不足时,SDL 会调用你在 
SDL_AudioSpec中指定的回调函数AudioCallBack。 - 音频数据的提供:
AudioCallBack函数从音频文件中读取新的音频数据并填充到音频缓冲区中。这些数据就是音频设备播放的内容。 - 持续播放:只要有新的音频数据可用,并且音频设备没有被暂停(调用 
SDL_PauseAudio(1)),音频设备就会持续播放数据。 
总结
声音的实际播放是由 SDL_PauseAudio(0) 启动的,它解除音频设备的暂停状态,让音频设备开始请求和播放数据。回调函数 AudioCallBack 在音频数据不足时被调用,用于填充更多的音频数据,从而实现连续播放。





