音频子系统(CategoryAudio)
SDL 库的音频功能模块,SDL3 中所有音频相关操作均围绕 SDL_AudioStream(音频流)展开。无论是播放/录制音频、格式转换、流式传输、数据缓冲还是混音,都需要通过音频流来处理。
音频流核心特性
音频流具备高度灵活性:
- 可随时接收任意数量的音频数据,支持所有SDL兼容格式;
- 可按需输出任意目标格式的音频数据,即使在处理过程中输入/输出格式发生变化;
- 应用程序可打开音频设备并绑定多个音频流,按需向流中填充数据;
- 当设备需要音频数据时,会从所有绑定的流中拉取数据并混音后播放;
- 支持通过应用提供的回调函数按需获取数据,与SDL2的音频模型高度兼容。
SDL 还提供了简易的 WAV 文件加载接口:SDL_LoadWAV(从文件加载)和 SDL_LoadWAV_IO(从自定义IO加载),作为程序加载音频数据的基础方式。
逻辑音频设备
在SDL3中,打开物理音频设备(如声卡)会得到一个逻辑设备ID,可绑定音频流使用。核心特性:
- 隔离性:每次打开设备都会生成新的逻辑设备,程序不同模块(如语音库、混音器)的音频操作互不干扰;
- 自动混音:SDL会在底层将所有逻辑设备的音频混合为单个缓冲区,输出到物理设备;
- 资源优化:SDL仅在系统层面打开物理设备一次,内部管理所有逻辑设备;
- 动态迁移:使用默认设备时,SDL可自动将逻辑设备迁移到新的物理硬件(如插入耳机、系统默认设备变更),无需程序干预。
简化音频模型
针对仅需单音频源的场景,SDL提供 SDL_OpenAudioDeviceStream 简化接口:
- 单个函数完成:打开音频设备 → 创建音频流 → 绑定流到设备 → (可选)设置数据回调;
- 核心操作接口为
SDL_AudioStream,设备句柄被隐藏; - 销毁该函数创建的流会自动关闭设备,且无法修改流绑定关系;
- 设备默认处于暂停状态,需显式恢复播放(非简化模型中,设备默认未暂停,绑定流后即播放)。
声道布局
SDL处理的音频数据为非压缩的交错式PCM数据(可通过第三方解码器处理MP3等格式,SDL不内置解码)。各声道数据在内存中有固定排列顺序:
缩写说明
- FRONT = 单声道扬声器
- FL = 前置左声道
- FR = 前置右声道
- FC = 前置中声道
- BL = 后置左声道
- BR = 后置右声道
- SR = 环绕右声道
- SL = 环绕左声道
- BC = 后置中声道
- LFE = 低频声道(重低音)
标准声道布局(内存排列顺序)
- 1声道(单声道):FRONT
- 2声道(立体声):FL, FR
- 3声道(2.1):FL, FR, LFE
- 4声道(四声道):FL, FR, BL, BR
- 5声道(4.1):FL, FR, LFE, BL, BR
- 6声道(5.1):FL, FR, FC, LFE, BL, BR(后两个也可为SL, SR)
- 7声道(6.1):FL, FR, FC, LFE, BC, SL, SR
- 8声道(7.1):FL, FR, FC, LFE, BL, BR, SL, SR
该顺序与DirectSound一致,SDL会自动适配不同平台的声道顺序要求。SDL_AudioStream 也支持自定义声道映射,满足特殊音频处理需求。
函数
- SDL_AudioDevicePaused:检查音频设备是否处于暂停状态(返回布尔值)
- SDL_AudioStreamDevicePaused:检查音频流绑定的设备是否暂停
- SDL_BindAudioStream:将单个音频流绑定到指定音频设备(混音播放)
- SDL_BindAudioStreams:批量绑定多个音频流到指定音频设备
- SDL_ClearAudioStream:清空音频流中的所有缓冲数据
- SDL_CloseAudioDevice:关闭音频设备(释放设备资源,停止播放)
- SDL_ConvertAudioSamples:直接转换音频样本格式(单次转换,无需创建流)
- SDL_CreateAudioStream:创建音频流(指定输入/输出格式、采样率、声道数)
- SDL_DestroyAudioStream:销毁音频流(释放内存,若为简化模型创建则关闭设备)
- SDL_FlushAudioStream:刷新音频流(强制输出所有缓冲数据)
- SDL_GetAudioDeviceChannelMap:获取音频设备的声道映射表
- SDL_GetAudioDeviceFormat:获取音频设备的当前格式(采样率、声道数、格式)
- SDL_GetAudioDeviceGain:获取音频设备的增益(音量,0~1.0)
- SDL_GetAudioDeviceName:通过设备ID获取音频设备名称
- SDL_GetAudioDriver:获取指定索引的音频驱动名称
- SDL_GetAudioFormatName:获取音频格式的可读名称(如 "SDL_AUDIO_F32")
- SDL_GetAudioPlaybackDevices:枚举系统中所有音频播放设备(返回设备ID列表)
- SDL_GetAudioRecordingDevices:枚举系统中所有音频录制设备(返回设备ID列表)
- SDL_GetAudioStreamAvailable:获取音频流中可读取的输出数据字节数
- SDL_GetAudioStreamData:从音频流读取转换后的音频数据
- SDL_GetAudioStreamDevice:获取音频流绑定的音频设备ID
- SDL_GetAudioStreamFormat:获取音频流的输入/输出格式信息
- SDL_GetAudioStreamFrequencyRatio:获取音频流的采样率转换比率
- SDL_GetAudioStreamGain:获取音频流的增益(独立于设备增益)
- SDL_GetAudioStreamInputChannelMap:获取音频流的输入声道映射
- SDL_GetAudioStreamOutputChannelMap:获取音频流的输出声道映射
- SDL_GetAudioStreamProperties:获取音频流的属性集合(延迟、缓冲等)
- SDL_GetAudioStreamQueued:获取音频流中待处理的输入数据字节数
- SDL_GetCurrentAudioDriver:获取当前使用的音频驱动名称
- SDL_GetNumAudioDrivers:获取系统中可用的音频驱动数量
- SDL_GetSilenceValueForFormat:获取指定音频格式的静音值(用于填充静音数据)
- SDL_IsAudioDevicePhysical:检查音频设备是否为物理设备(非逻辑设备)
- SDL_IsAudioDevicePlayback:检查音频设备是否为播放设备(非录制设备)
- SDL_LoadWAV:从文件加载WAV音频数据(返回音频格式和数据缓冲区)
- SDL_LoadWAV_IO:从自定义IO流加载WAV音频数据
- SDL_LockAudioStream:锁定音频流(线程安全操作)
- SDL_MixAudio:混音多个音频缓冲区(按指定格式混合)
- SDL_OpenAudioDevice:打开音频设备(返回逻辑设备ID)
- SDL_OpenAudioDeviceStream:简化接口,一键创建设备+流并绑定
- SDL_PauseAudioDevice:暂停音频设备(停止播放,保留缓冲数据)
- SDL_PauseAudioStreamDevice:暂停音频流绑定的设备
- SDL_PutAudioStreamData:向音频流写入输入数据(进行格式转换)
- SDL_PutAudioStreamDataNoCopy:写入数据但不拷贝(直接使用原缓冲区)
- SDL_PutAudioStreamPlanarData:向音频流写入平面格式音频数据(非交错)
- SDL_ResumeAudioDevice:恢复暂停的音频设备(继续播放)
- SDL_ResumeAudioStreamDevice:恢复音频流绑定设备的播放
- SDL_SetAudioDeviceGain:设置音频设备的增益(调整整体音量)
- SDL_SetAudioPostmixCallback:设置音频后混音回调(最终输出前处理)
- SDL_SetAudioStreamFormat:动态修改音频流的输入/输出格式
- SDL_SetAudioStreamFrequencyRatio:设置音频流的采样率转换比率
- SDL_SetAudioStreamGain:设置音频流的增益(仅影响该流的音量)
- SDL_SetAudioStreamGetCallback:设置音频流的数据源回调(按需获取数据)
- SDL_SetAudioStreamInputChannelMap:设置音频流的输入声道映射
- SDL_SetAudioStreamOutputChannelMap:设置音频流的输出声道映射
- SDL_SetAudioStreamPutCallback:设置音频流的数据输出回调(数据就绪时触发)
- SDL_UnbindAudioStream:解除音频流与设备的绑定
- SDL_UnbindAudioStreams:批量解除多个音频流的绑定
- SDL_UnlockAudioStream:解锁音频流(与SDL_LockAudioStream配对使用)
数据类型
- SDL_AudioDeviceID:音频设备标识类型(区分不同的逻辑/物理设备)
- SDL_AudioPostmixCallback:音频后混音回调函数类型(处理最终输出数据)
- SDL_AudioStream:音频流句柄类型(标识已创建的音频流)
- SDL_AudioStreamCallback:音频流数据回调函数类型(按需提供/处理数据)
- SDL_AudioStreamDataCompleteCallback:音频流数据完成回调类型(数据处理完毕触发)
结构体
- SDL_AudioSpec:音频格式描述结构体(包含采样率、声道数、格式、样本数等)
枚举
- SDL_AudioFormat:音频格式枚举(如SDL_AUDIO_U8、SDL_AUDIO_S16、SDL_AUDIO_F32等)
宏
- SDL_AUDIO_BITSIZE:提取音频格式的位深度(如16位、32位)
- SDL_AUDIO_BYTESIZE:计算音频格式的字节大小(位深度/8)
- SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK:默认音频播放设备标识
- SDL_AUDIO_DEVICE_DEFAULT_RECORDING:默认音频录制设备标识
- SDL_AUDIO_FRAMESIZE:计算单帧音频数据的字节大小(声道数×字节大小)
- SDL_AUDIO_ISBIGENDIAN:检查音频格式是否为大端序
- SDL_AUDIO_ISFLOAT:检查音频格式是否为浮点型
- SDL_AUDIO_ISINT:检查音频格式是否为整型
- SDL_AUDIO_ISLITTLEENDIAN:检查音频格式是否为小端序
- SDL_AUDIO_ISSIGNED:检查音频格式是否为有符号类型
- SDL_AUDIO_ISUNSIGNED:检查音频格式是否为无符号类型
- SDL_AUDIO_MASK_BIG_ENDIAN:大端序格式掩码
- SDL_AUDIO_MASK_BITSIZE:位深度格式掩码
- SDL_AUDIO_MASK_FLOAT:浮点型格式掩码
- SDL_AUDIO_MASK_SIGNED:有符号类型格式掩码
- SDL_DEFINE_AUDIO_FORMAT:自定义音频格式宏(组合位深度、字节序、类型等)
FreeBASIC 示例代码
' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"
' 播放WAV文件示例
Sub PlayWAVFile(ByVal filename As String)
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "尝试播放WAV文件:%s", filename)
' 1. 加载WAV文件
Dim As SDL_AudioSpec wavSpec
Dim As UByte Ptr wavBuffer = NULL
Dim As Uint64 wavLength = 0
If (SDL_LoadWAV(filename, @wavSpec, @wavBuffer, @wavLength) = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "加载WAV文件失败:%s", SDL_GetError())
Return
End If
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "WAV文件加载成功:")
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " 采样率:%d Hz", wavSpec.freq)
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " 声道数:%d", wavSpec.channels)
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " 格式:%s", SDL_GetAudioFormatName(wavSpec.format))
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " 数据长度:%llu 字节", wavLength)
' 2. 获取默认播放设备
Dim As SDL_AudioDeviceID defaultDevice = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK
' 3. 打开音频设备(使用WAV文件的格式)
Dim As SDL_AudioDeviceID devID = SDL_OpenAudioDevice(defaultDevice, @wavSpec, 0)
If (devID = 0) Then
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "打开音频设备失败:%s", SDL_GetError())
SDL_FreeWAV(wavBuffer)
Return
End If
' 4. 创建音频流(转换为设备兼容格式)
Dim As SDL_AudioStream Ptr stream = SDL_CreateAudioStream( _
wavSpec.format, wavSpec.channels, wavSpec.freq, _
wavSpec.format, wavSpec.channels, wavSpec.freq _
)
If (stream = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "创建音频流失败:%s", SDL_GetError())
SDL_CloseAudioDevice(devID)
SDL_FreeWAV(wavBuffer)
Return
End If
' 5. 向音频流写入WAV数据
SDL_PutAudioStreamData(stream, wavBuffer, wavLength)
SDL_FlushAudioStream(stream) ' 刷新流,确保所有数据可输出
' 6. 绑定音频流到设备
If (SDL_BindAudioStream(devID, stream) < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "绑定音频流失败:%s", SDL_GetError())
SDL_DestroyAudioStream(stream)
SDL_CloseAudioDevice(devID)
SDL_FreeWAV(wavBuffer)
Return
End If
' 7. 恢复设备播放(默认暂停)
SDL_ResumeAudioDevice(devID)
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "开始播放音频...")
' 8. 等待播放完成(检查流中是否还有数据)
While (SDL_GetAudioStreamAvailable(stream) > 0)
SDL_Delay(100)
Wend
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "音频播放完成")
' 9. 清理资源
SDL_UnbindAudioStream(devID, stream)
SDL_DestroyAudioStream(stream)
SDL_CloseAudioDevice(devID)
SDL_FreeWAV(wavBuffer)
End Sub
' 简化音频播放示例(使用SDL_OpenAudioDeviceStream)
Sub PlayAudioSimplified()
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, vbCrLf & "==== 简化音频播放示例 ====")
' 定义音频格式(44.1kHz,立体声,32位浮点)
Dim As SDL_AudioSpec spec
spec.freq = 44100
spec.format = SDL_AUDIO_F32
spec.channels = 2
spec.samples = 4096 ' 缓冲区大小
' 1. 打开简化音频流
Dim As SDL_AudioStream Ptr stream = SDL_OpenAudioDeviceStream( _
SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, _
@spec, _
NULL, NULL _
)
If (stream = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "打开简化音频流失败:%s", SDL_GetError())
Return
End If
' 2. 生成测试音频(正弦波,440Hz A调)
Const sampleCount As Integer = 44100 * 2 ' 2秒音频
Dim As Single Ptr audioData = Allocate(sampleCount * 2 * SizeOf(Single))
If (audioData = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "分配音频数据内存失败")
SDL_DestroyAudioStream(stream)
Return
End If
' 生成立体声正弦波
For i As Integer = 0 To sampleCount - 1
Dim As Single t = i / 44100.0
Dim As Single val = Sin(2 * 3.14159265 * 440 * t) * 0.5 ' 0.5音量
' 左右声道相同
audioData[i * 2] = val
audioData[i * 2 + 1] = val
Next
' 3. 写入音频数据到流
SDL_PutAudioStreamData(stream, audioData, sampleCount * 2 * SizeOf(Single))
SDL_FlushAudioStream(stream)
' 4. 恢复播放
SDL_ResumeAudioStreamDevice(stream)
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "播放2秒440Hz正弦波...")
' 5. 等待播放完成
While (SDL_GetAudioStreamAvailable(stream) > 0)
SDL_Delay(100)
Wend
' 6. 清理资源
Deallocate(audioData)
SDL_DestroyAudioStream(stream) ' 销毁流自动关闭设备
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "简化音频播放完成")
End Sub
' 枚举音频设备示例
Sub EnumerateAudioDevices()
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, vbCrLf & "==== 枚举音频设备 ====")
' 枚举播放设备
Dim As Integer playbackCount = SDL_GetAudioPlaybackDevices(NULL, 0)
If (playbackCount > 0) Then
Dim As SDL_AudioDeviceID Ptr playbackDevs = Callocate(playbackCount * SizeOf(SDL_AudioDeviceID))
playbackCount = SDL_GetAudioPlaybackDevices(playbackDevs, playbackCount)
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "播放设备(%d个):", playbackCount)
For i As Integer = 0 To playbackCount - 1
Dim As ZString Ptr devName = SDL_GetAudioDeviceName(playbackDevs[i])
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " %d: %s (物理设备:%s)", _
i, devName, IIf(SDL_IsAudioDevicePhysical(playbackDevs[i]), "是", "否"))
If (devName <> NULL) Then SDL_free(devName)
Next
Deallocate(playbackDevs)
Else
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "未检测到音频播放设备")
End If
' 枚举录制设备
Dim As Integer recordCount = SDL_GetAudioRecordingDevices(NULL, 0)
If (recordCount > 0) Then
Dim As SDL_AudioDeviceID Ptr recordDevs = Callocate(recordCount * SizeOf(SDL_AudioDeviceID))
recordCount = SDL_GetAudioRecordingDevices(recordDevs, recordCount)
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "录制设备(%d个):", recordCount)
For i As Integer = 0 To recordCount - 1
Dim As ZString Ptr devName = SDL_GetAudioDeviceName(recordDevs[i])
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " %d: %s", i, devName)
If (devName <> NULL) Then SDL_free(devName)
Next
Deallocate(recordDevs)
Else
SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "未检测到音频录制设备")
End If
End Sub
' 主程序
Sub Main()
' 初始化SDL音频子系统
If (SDL_Init(SDL_INIT_AUDIO) < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL初始化失败:%s", SDL_GetError())
Exit Sub
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL3 音频示例程序 ===")
' 1. 枚举音频设备
EnumerateAudioDevices()
' 2. 播放WAV文件(替换为实际的WAV文件路径)
Dim As String wavFile = "test.wav"
If (SDL_FileExists(wavFile)) Then
PlayWAVFile(wavFile)
Else
SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO, "WAV文件不存在:%s,跳过播放", wavFile)
End If
' 3. 简化音频播放(生成正弦波)
PlayAudioSimplified()
' 退出SDL
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序结束")
End Sub
' 辅助函数:检查文件是否存在
Function SDL_FileExists(ByVal filename As String) As Boolean
Dim As FILE Ptr f = fopen(filename, "rb")
If (f <> NULL) Then
fclose(f)
Return True
End If
Return False
End Function
' 运行主程序
Main()
核心知识点补充
-
音频格式说明: 格式常量 描述 位深度 类型 字节序 SDL_AUDIO_U8 无符号8位整型 8 整型 - SDL_AUDIO_S16 有符号16位整型 16 整型 小端序 SDL_AUDIO_S32 有符号32位整型 32 整型 小端序 SDL_AUDIO_F32 32位浮点型 32 浮点型 小端序 SDL_AUDIO_S16MSB 有符号16位整型 16 整型 大端序 -
开发注意事项:
- 数据对齐:交错式PCM数据中,每个采样点的各声道数据连续排列(如立体声:左、右、左、右...);
- 缓冲区大小:
spec.samples建议设置为2的幂(如1024、2048、4096),避免音频卡顿; - 线程安全:音频流操作需加锁(
SDL_LockAudioStream/SDL_UnlockAudioStream); - 资源释放:加载WAV后必须调用
SDL_FreeWAV,避免内存泄漏; - 跨平台兼容:不同平台的默认音频格式可能不同,建议使用浮点格式(SDL_AUDIO_F32)提高兼容性。
-
性能优化技巧:
- 批量写入/读取音频数据,减少频繁的
SDL_PutAudioStreamData/SDL_GetAudioStreamData调用; - 使用
SDL_PutAudioStreamDataNoCopy避免数据拷贝,提升性能; - 合理设置缓冲区大小,平衡延迟和卡顿风险;
- 多个音频源使用不同的音频流,通过SDL自动混音,避免手动混音的性能损耗。
- 批量写入/读取音频数据,减少频繁的
总结
- 核心优势:
- SDL3 以
SDL_AudioStream为核心,统一了音频处理流程,简化格式转换、混音等操作; - 逻辑设备机制实现了音频隔离和自动混音,简化多音频源管理;
- 提供简化接口和完整接口两套API,兼顾易用性和灵活性;
- 跨平台统一的声道布局,自动适配不同系统的音频硬件。
- SDL3 以
- 使用建议:
- 简单音频播放优先使用
SDL_OpenAudioDeviceStream简化接口; - 多音频源场景使用逻辑设备+音频流绑定,利用SDL自动混音;
- 音频格式转换必须通过音频流,避免直接操作原始数据;
- 播放WAV文件后务必释放缓冲区,设备使用完毕及时关闭。
- 简单音频播放优先使用
- 关键接口:
- 设备管理:
SDL_OpenAudioDevice/SDL_CloseAudioDevice/SDL_GetAudioPlaybackDevices; - 音频流:
SDL_CreateAudioStream/SDL_PutAudioStreamData/SDL_GetAudioStreamData; - 简化播放:
SDL_OpenAudioDeviceStream; - WAV加载:
SDL_LoadWAV/SDL_FreeWAV。
- 设备管理:
评论一下?