目录
[一、sample_fmt 到底是什么?](#一、sample_fmt 到底是什么?)
[二、先分清:Packed vs Planar(音频里最容易被忽略)](#二、先分清:Packed vs Planar(音频里最容易被忽略))
[三、8bit 系列](#三、8bit 系列)
[S8 / U8](#S8 / U8)
[五、S32(专业音频 / 高动态范围)](#五、S32(专业音频 / 高动态范围))
[AV_SAMPLE_FMT_S32 / S32P](#AV_SAMPLE_FMT_S32 / S32P)
[六、FLT / FLTP(AAC 解码默认)](#六、FLT / FLTP(AAC 解码默认))
[AV_SAMPLE_FMT_FLT(Packed float)](#AV_SAMPLE_FMT_FLT(Packed float))
[AV_SAMPLE_FMT_FLTP(Planar float)](#AV_SAMPLE_FMT_FLTP(Planar float))
[七、DBL / DBLP(双精度,基本只在滤波链里)](#七、DBL / DBLP(双精度,基本只在滤波链里))
[九、Buffer 大小怎么算(新手必错)](#九、Buffer 大小怎么算(新手必错))
[十、Planar → Packed 实战(S16P → S16)](#十、Planar → Packed 实战(S16P → S16))
[十一、AAC 解码标准链路](#十一、AAC 解码标准链路)
[坑 1:SDL 播音炸裂](#坑 1:SDL 播音炸裂)
[坑 2:声道数变了但 format 没变](#坑 2:声道数变了但 format 没变)
[坑 3:linesize 不等于 sampleschbytes](#坑 3:linesize 不等于 sampleschbytes)
觉得有用,就请您帮忙点赞转发收藏吧,您的鼓励是我创作的动力,多谢看官。
由于能力水平有限,文中的错误或不严谨的地方在所难免,还请批评指正。
做音视频的人,几乎都在音频上栽过:AAC 解码出来是 fltp,SDL 播放劈里啪啦,自己 malloc 的 buffer,copy 完全是杂音;av_samples_get_buffer_size算出来的大小跟想的不一样; 混音时发现"数值怎么还有负数还带小数点",问题的根,90% 都在 sample_fmt(音频采样格式),本文就把 FFmpeg 里最常见、最容易混淆的音频采样格式聊一下。
一、sample_fmt 到底是什么?
视频有 pix_fmt,音频对应就是:
enum AVSampleFormat;
命令行里叫:
-sample_fmt s16
它决定两件事:
-
每个采样点用什么类型存(int8 / int16 / int32 / float)
-
多声道怎么排(planar or packed)
注意:它不决定采样率,也不决定声道数,只管"一个样本长什么样"。
二、先分清:Packed vs Planar(音频里最容易被忽略)
Packed(交错)
所有声道挤在一起:
L R L R L R L R
代表:
-
AV_SAMPLE_FMT_S16 -
AV_SAMPLE_FMT_FLT
Planar(平面)
每个声道独占一块内存:
LLLLLLLL
RRRRRRRR
代表:
-
AV_SAMPLE_FMT_S16P -
AV_SAMPLE_FMT_FLTP
AAC / OPUS / MP3 解码器几乎都输出 Planar
SDL / OpenSL / 声卡 DMA 几乎都吃 Packed
所以你一定会做:
Planar → Packed
三、8bit 系列
S8 / U8
AV_SAMPLE_FMT_U8:0--255
AV_SAMPLE_FMT_S8:-128--127早期 PCM、游戏音效、嵌入式喇叭。
动态范围小、量化噪声大,现代编码不用。
四、S16(最经典的"能直接播"的格式)
AV_SAMPLE_FMT_S16(Packed)
16bit 有符号整型
范围:-32768 ~ 32767
布局:
int16_t L R L R...特点:
SDL2 默认
Linux ALSA 默认
WAV 文件常见
不挑平台,性能最好
int16_t *pcm = (int16_*)frame->data[0];
AV_SAMPLE_FMT_S16P(Planar)
每个声道一个
int16_t*
data[0] = L, data[1] = RFFmpeg 内部常见,但不能直接丢给声卡。
五、S32(专业音频 / 高动态范围)
AV_SAMPLE_FMT_S32 / S32P
32bit 整数
范围:-2147483648 ~ 2147483647
动态范围比 S16 大 48dB
用在:
专业音频工作站
多代混音(防止截断误差累积)
FFmpeg 某些滤波器内部
播放前要转 S16,否则声卡不认。
六、FLT / FLTP(AAC 解码默认)
AV_SAMPLE_FMT_FLT(Packed float)
32bit IEEE float
范围:-1.0 ~ 1.0
float L R L R...
AV_SAMPLE_FMT_FLTP(Planar float)
data[0] = float* L
data[1] = float* RAAC / OPUS / Vorbis 默认解码输出
为什么用 float?
滤波器数学运算方便
不会溢出
多代处理误差小
但是:
SDL 不能直接播
写 WAV 要转 int
存文件前必须 scale
七、DBL / DBLP(双精度,基本只在滤波链里)
AV_SAMPLE_FM_DBL/DBLP64bit double
极少见(科学计算、降噪研究)
编解码器不使用
八、各格式对比速查表
| sample_fmt | 类型 | 范围 | Planar? | 典型用途 |
|---|---|---|---|---|
| u8 / s8 | int8 | 0~255 / ±127 | 否 | 老式 PCM |
| s16 | int16 | ±32767 | 否 | SDL/WAV/ALSA |
| s16p | int16 | ±32767 | ✅ | FFmpeg 内部 |
| s32 / s32p | int32 | ±2^31 | ✅ | 专业音频 |
| flt | float | -1~1 | 否 | 某些渲染器 |
| fltp | float | -1~1 | ✅ | AAC/OPUS 解码 |
| dbl / dblp | double | -1~1 | ✅ | 滤波/科研 |
九、Buffer 大小怎么算(新手必错)
不要写:
size = nb_samples * channels * 2;
要用 FFmpeg API:
int buf_size = av_samples_get_buffer_size(
NULL,
frame->channels,
frame->nb_samples,
frame->format,
1); // align
Planar 时:
-
data[0..N]每个声道nb_samples * bytes_per_sample -
总 buffer = 单声道大小 × 声道数(连续分配时)
十、Planar → Packed 实战(S16P → S16)
// frame->format == AV_SAMPLE_FMT_S16P
int16_t *dst = out_buf;
for (int i = 0; i < frame->nb_samples; i++) {
for (int ch = 0; ch < frame->channels; ch++) {
dst[i * frame->channels + ch] =
((int16_t*)frame->data[ch])[i];
}
}
FLTP → S16(带 clip):
float f = ((float*)frame->data[ch])[i];
f = FFMAX(-1.0f, FFMIN(1.0f, f));
dst[i * ch + ch] = (int16_t)(f * 32767);
十一、AAC 解码标准链路
AAC packet
↓
avcodec_send_packet
↓
avcodec_receive_frame
→ sample_fmt = fltp
↓
swr_convert (fltp → s16)
↓
SDL_QueueAudio (s16 packed)
SwrContext 设置:
swr_alloc_set_opts2(&swr,
&out_layout, AV_SAMPLE_FMT_S16, 48000,
&in_layout, AV_SAMPLE_FMT_FLTP, 44100,
0, NULL);
十二、三个高频坑
坑 1:SDL 播音炸裂
解码是 fltp,直接当 s16播 → 爆音
必须 swr_convert
坑 2:声道数变了但 format 没变
swr 不自动帮你改 layout,要显式设 channel_layout
坑 3:linesize 不等于 sampleschbytes
音频 frame 也有 linesize[0],通常等于单平面字节数,别拿它当总大小。
十三、总结
S16 是"声卡语言",FLTP 是"解码器语言",Planar 是"FFmpeg 内部语言"。
工程里 80% 的音频 bug,都是这三者没接好。