目录
一、先澄清一个巨大误会
核心原因解析
实际工作流程
二、声卡到底要什么?
三、编码器为什么要搞“奇怪格式”?(FLTP 是重灾区)
FLTP 拆解一下:
四、为什么编码器死磕 float planar?
1️.数学精度:量化噪声是编码器第一敌人
2️.Planar 是为了 SIMD,不是为了你爽
3️.编码标准根本不关心“整数”
五、那为什么声卡不用 float planar?
六、FFmpeg 的真相:三层世界模型**
七、声卡格式 vs 编码格式:特性对照表
八、一个你可能踩过的坑
九、总结
觉得有用,就请您帮忙点赞转发收藏吧,您的鼓励是我创作的动力,多谢看官。
由于能力水平有限,文中的错误或不严谨的地方在所难免,还请批评指正。
音频不直接编码为"S16"等声卡格式,是因为S16 仅是 PCM 裸数据的位深与字节序描述(非完整文件格式),缺乏采样率、声道数等关键元数据且无法压缩存储/传输;文件需封装元数据头并采用压缩编码以适配多样场景,而声卡格式仅作为底层播放时的最终转换目标 。
一句话暴论:声卡格式是物理,采样格式是代数。两者本来就不是一个物种。
一、先澄清一个巨大误会
你看到的:
SDL_OpenAudio(S16) PulseAudio(PCM_S16LE) WASAPI(KSDATAFORMAT_SUBTYPE_PCM)和 FFmpeg 里的:
AV_SAMPLE_FMT_S16 AV_SAMPLE_FMT_FLTP AV_SAMPLE_FMT_S32P不是“同一个维度的东西”
声卡:DAC 要吃的电压序列
编码器:比特流要榨的油水
一个管“怎么出声”
一个管“怎么压缩”
核心原因解析
- S16 不是完整文件格式:S16(Signed 16-bit)仅定义采样点的数值范围和字节序(如 S16_LE),不包含采样率、声道数、时长等必要信息。一段纯 S16 数据若无外部约定参数,播放器无法知其如何还原声音(例如 44.1kHz 还是 48kHz?单声道还是立体声?)。
- 存储与传输效率极低:S16 代表未压缩的 PCM 数据。若所有音频文件均以此存储,1 分钟立体声 44.1kHz 音频需约 10MB 空间,且无法通过网络流畅传输;实际需 MP3/AAC/FLAC 等编码进行压缩,仅在播放瞬间由解码器转为声卡支持的 S16/S24 等格式 。
- 声卡硬件多样性:不同声卡支持的格式各异(部分仅支持 S16_LE,高端卡支持 S24/S32 Float),不存在统一的“声卡格式”。操作系统音频服务(如 ALSA、Core Audio、WASAPI)负责将多种来源音频统一重采样、转换为目标声卡所需的特定格式 。
- 处理灵活性需求:音频制作、流媒体、语音识别等场景需保留高位深(如 24bit/32bit Float)以避免中间运算失真,若强制存为 S16 会永久丢失动态范围和精度 。
实际工作流程
- 存储/传输层:文件采用含头信息的容器格式(如 WAV/FLAC)或压缩编码(如 AAC/Opus),完整记录采样率、位深、声道等参数。
- 解码与混音层:播放时解码器还原为 PCM 数据,系统音频引擎将其重采样并转换为统一内部格式(常为 32bit Float)进行混音处理。
- 输出层:音频服务将最终数据实时转换为当前声卡硬件支持的特定格式(如 S16_LE @ 48kHz)写入缓冲区,驱动 D/A 转换器发声 。
简言之,S16 是声卡“吃”的饲料规格,而非仓库里存的“粮食包装”;文件需自带说明书(元数据)和压缩包装(编码),上桌前再由厨房(系统)按需加工成声卡能直接处理的形态
二、声卡到底要什么?
声卡要的是:
时间离散 + 幅度线性 + 连续缓冲区典型组合:
项 | 值 |
|---|---|
位宽 | 16bit / 24bit / 32bit |
符号 | signed |
排列 | 交错(LRLRLR) |
字节序 | 小端 |
采样率 | 44100 / 48000 |
本质:
DAC 不懂傅里叶,DAC 只会按电压爬梯子
所以声卡必须:
整数
连续内存
固定步进
三、编码器为什么要搞“奇怪格式”?(FLTP 是重灾区)
你第一次见AV_SAMPLE_FMT_FLTP的反应:
float?还 planar?有病吧?
FFmpeg 说得很直白:
我不是写给 DAC 看的,我是写给 MDCT 看的
FLTP 拆解一下:
缩写 | 含义 |
|---|---|
FLT | float(32bit IEEE) |
P | planar(分通道存),left[], right[] |
内存长这样:
planar[0]: L L L L L L planar[1]: R R R R R R而不是:
L R L R L R四、为什么编码器死磕 float planar?
1️.数学精度:量化噪声是编码器第一敌人
AAC / Opus / MP3 核心流程:
MDCT → 量化 → 熵编码MDCT 是啥?
浮点矩阵变换
系数乘来乘去
舍入误差累积
如果用 S16:
short *pcm = buffer; coeff = pcm[i] * win[j] >> shift; // ❌ 灾难结果:
截断失真
噪声整形失效
心理声学模型崩
float 的好处:
项 | S16 | float |
|---|---|---|
动态范围 | 96 dB | ~1500 dB |
DC 偏移 | 难处理 | 减一下就行 |
增益 | 整数倍 | 乘法无痕 |
MDCT | 数值烂 | 数值稳 |
2️.Planar 是为了 SIMD,不是为了你爽
现代 CPU:
vmulps ymm0, [l_chan] vaddps ymm1, [r_chan]Planar:
连续内存
无 stride
AVX2 / NEON 直接飞
交错 S16:
gather load
unpack
shuffle
性能直接腰斩
FLTP = 写给 CPU 的格式
3️.编码标准根本不关心“整数”
AAC 标准里写的是:
time-domain input is real-valued sequence
没说:
必须是 short
必须是 LSB aligned
Opus / AAC / LC3 内部:
全浮点 / 定点 Q31
最后才 dither 成 16bit
五、那为什么声卡不用 float planar?
因为 DAC 是模拟世界的奴隶:
限制 | DAC |
|---|---|
只能线性阶梯 | ✅ |
不能向量化 | ✅ |
FIFO 硬连线 | ✅ |
DMA 只认 LRLR | ✅ |
你给声卡灌 float planar:
DMA scatter-gather 复杂
时钟抖动
驱动直接 BSOD
所以:
内核 / WASAPI / ALSA 帮你做了一件事:重采样 + 转换
六、FFmpeg 的真相:三层世界模型**
┌────────────── 编码世界 ───────────────┐ │ FLTP / DBL / S32P │ │ libopus / libfdk_aac / libx264audio │ └────────────── swr_convert ───────────┘ ↓ ┌────────────── 中间层 ────────────────┐ │ AV_SAMPLE_FMT_S16 (interleaved) │ │ SDL / PortAudio / OpenSL ES │ └────────────── 声卡驱动 ──────────────┘ ↓ ┌────────────── 物理世界 ──────────────┐ │ I2S / USB Audio / HDMI LPCM │ │ DAC → 电容 → 空气 → 耳朵 │ └─────────────────────────────────────┘libswresample 就是那个“翻译官”
七、声卡格式 vs 编码格式:特性对照表
对比项 | 声卡格式(S16 interleaved) | 编码采样格式(FLTP / S32P) |
|---|---|---|
服务对象 | DAC | 变换域压缩 |
数值类型 | 整数 | float / Q-fixed |
通道排布 | packed(LRLR) | planar |
是否关心精度 | 不深 | 极深 |
SIMD 友好 | 一般 | 极佳 |
可压缩性 | 极差 | 极佳 |
心理声学友好 | ❌ | ✅ |
硬件直接支持 | ✅ | ❌ |
八、一个你可能踩过的坑
avcodec_decode_audio4() → AV_SAMPLE_FMT_FLTP → 直接 memcpy 给 SDL → 声音炸裂 + 啸叫正确路径:
swr_alloc_set_opts( AV_SAMPLE_FMT_FLTP, ch_layout, AV_SAMPLE_FMT_S16, ch_layout, rate, rate, 0, NULL ); swr_convert();FFmpeg 不替你做 DAC 适配,这是设计不是偷懒
九、总结
采样格式不是“谁更先进”,而是“站在哪一边”
靠近人耳:整数交错
靠近算法:浮点分通道
Qt / SDL / WASAPI 负责“响”
FFmpeg / Opus / AAC 负责“小”