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
在音频数据不足时被调用,用于填充更多的音频数据,从而实现连续播放。