目录
[三、编码器为什么要搞"奇怪格式"?(FLTP 是重灾区)](#三、编码器为什么要搞“奇怪格式”?(FLTP 是重灾区))
[FLTP 拆解一下:](#FLTP 拆解一下:)
[四、为什么编码器死磕 float planar?](#四、为什么编码器死磕 float planar?)
[2️.Planar 是为了 SIMD,不是为了你爽](#2️.Planar 是为了 SIMD,不是为了你爽)
[五、那为什么声卡不用 float planar?](#五、那为什么声卡不用 float planar?)
[六、FFmpeg 的真相:三层世界模型**](#六、FFmpeg 的真相:三层世界模型**)
[七、声卡格式 vs 编码格式:特性对照表](#七、声卡格式 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 负责"小"