AAC 在音视频工程中的位置:从 PCM 到压缩音频的主线理解

前言:为什么很多人知道 AAC 很常见,但一问“它到底在系统里干什么”,脑子里还是会有点空

只要开始接触音视频,AAC 这个名字几乎绕不开。

你会在很多地方看到它:

  • MP4 文件里
  • FLV / RTMP 链路里
  • TS 流里
  • 手机录制视频里
  • 推流系统里
  • FFmpeg 命令里

所以大家通常都会知道一件事:

AAC 很常见。

但“很常见”和“真的理解它在系统里的位置”其实不是一回事。

因为只要你继续往下问几步,很多人就开始有点模糊:

  • AAC 和 PCM 到底是什么关系
  • 为什么录音设备拿到的通常不是 AAC,而是 PCM
  • 为什么播放器最终喂声卡的又不是 AAC,而还是 PCM
  • AAC 是不是就等于音频文件格式
  • ADTS、AudioSpecificConfig 又是怎么回事
  • FFmpeg 里编码 AAC 到底发生在哪一层

你会发现,这些问题表面上在问不同细节,但它们真正都指向同一个核心:

AAC 在音频链路里,到底站在哪一层,它前面接什么,后面又接什么。

所以这篇文章的目标,不是把 AAC 讲成一篇纯标准教材,而是把它放回音视频工程主线里讲清楚。

重点讲清:

  • PCM 和 AAC 的关系
  • 为什么 AAC 本质上是“压缩音频表示”,而不是最终播放形态
  • AAC 在采集、编码、封装、传输、播放链路中的位置
  • AAC 常见的外层组织方式是什么
  • FFmpeg 里从 PCM 到 AAC 的最小代码骨架长什么样

而且这篇依然按你要求,继续把这些都带上:

  • 核心关键代码示例
  • 结构图
  • 流程图
  • 时序图

我希望你读完之后,对 AAC 的感觉不再只是“一个常见音频编码格式”,而是能明确知道:

它到底在替系统解决什么问题,为什么前后都离不开 PCM。


一、先给一句最重要的话:AAC 不是原始音频,也不是播放器最终直接工作的基础形态,它是 PCM 经过有损压缩之后得到的音频码流表示

如果让我先给一句最重要的话,我会这样说:

AAC 的本质,是把 PCM 这类原始数字音频数据压缩之后得到的一种有损音频码流表示。

这句话非常重要。

因为它一下就把 AAC 放回了正确位置:

  • 它不是采集设备直接产生的原始数据
  • 它也不是音频设备最终最喜欢直接吃的底层形态
  • 它站在中间,负责把原始数字音频压缩成更省空间、更适合存储和传输的表示方式

也就是说,它更像音频世界里和 H.264/H.265 对视频所处位置相似的一层:

压缩编码层。


二、先看总图:PCM、AAC、容器、播放到底是什么关系

先上总图。

flowchart LR
    A[Mic/文件输入] --> B[PCM样本]
    B --> C[AAC编码器]
    C --> D[AAC码流]
    D --> E[MP4/FLV/TS等容器]
    E --> F[文件/网络传输]
    F --> G[Demux]
    G --> H[AAC码流]
    H --> I[AAC解码器]
    I --> J[PCM样本]
    J --> K[播放设备/算法处理]

这张图最关键的意义是:

  • PCM 是原始数字音频工作形态
  • AAC 是压缩音频码流层
  • 容器 是外层承载组织
  • 播放设备 最终往往仍然在 PCM 层工作

如果你把这张图立住,后面很多困惑一下就会顺很多。


三、为什么音频系统前后都绕不开 PCM

这是理解 AAC 的第一步。

3.1 采集侧通常先拿到 PCM

现实里的声音经过麦克风、ADC 之后,数字系统最自然得到的是:

  • 采样率明确
  • 位宽明确
  • 声道数明确
  • 一串按时间排列的数字样本

也就是 PCM。

所以录音设备、音频采集 API、算法处理模块,天然更容易直接面对 PCM。

3.2 编码器吃进去的很多时候也是 PCM

AAC 编码器不是直接对“空气里的声音”工作,而是对数字音频样本工作。

也就是说,它的输入往往就是:

PCM 样本块。

3.3 解码器吐出来的通常也还是 PCM

AAC 解码器做的事,本质上是:

  • 输入压缩 AAC 数据
  • 输出可播放、可处理的原始数字音频样本

而这一步出来的,通常依然是 PCM。

所以你可以很清楚地看到:

  • PCM 在前
  • AAC 在中间
  • PCM 在后

这就是它在系统里的真实位置。


四、AAC 到底在替系统解决什么问题

4.1 核心问题就是:PCM 太大了

如果你还记得上一篇 PCM 的数据量计算,就会知道原始音频其实并不小。

比如:

  • 48kHz
  • 16bit
  • 双声道

每秒大约就是:

1
48000 * 2 bytes * 2 channels = 192000 bytes/s

也就是大约 1.536 Mbps。

这对原始数据处理当然没问题,但如果你要:

  • 存储更长时长音频
  • 和视频一起打包
  • 做网络传输
  • 在带宽受限场景里推送

那 PCM 往往就显得太“直给”了。

4.2 AAC 做的就是压缩表达

AAC 的核心价值就在于:

尽量在可接受音质损失下,把原始 PCM 的数据量压下来。

所以它回答的是:

  • 怎样更省比特地表示这段音频
  • 怎样让存储和传输更划算
  • 怎样在有限带宽里还保持不错听感

这也是为什么 AAC 在音视频工程里地位非常稳。


五、AAC 为什么经常跟 MP4、FLV、TS 一起出现,但它们不是同一层

这点和视频那边“容器 vs 码流”的问题是完全平行的。

5.1 AAC 是压缩音频码流层

它关心的是:

  • 音频内容怎么被压缩表示
  • 解码器怎么从这些比特里恢复音频样本

5.2 MP4 / FLV / TS 是承载层

它们关心的是:

  • 音频轨和视频轨怎么组织
  • 时间戳怎么放
  • packet 怎么装
  • 文件或流怎么形成整体结构

所以你看到:

  • AAC in MP4
  • AAC in FLV
  • AAC in TS

一点都不矛盾。

因为这本来就是:

同一种音频码流,被装进不同容器。


六、AAC 常见外层组织方式为什么又会让人继续混

这里只抓工程理解,不往标准细节深潜太多。

6.1 ADTS

ADTS 常常出现在更接近流式、逐帧组织的 AAC 数据表达里。

你可以先粗略理解成:

  • 每帧 AAC 数据前面带一个头
  • 方便接收端逐帧识别和解析

6.2 AudioSpecificConfig

在 MP4 这类容器里,AAC 参数信息常常不会用 ADTS 那种每帧都带头的方式,而更常通过额外配置描述告诉解码器:

  • 采样率是什么
  • 声道是什么
  • AAC profile 是什么

这就让很多人开始糊涂:

  • 明明都是 AAC,为什么长得不一样

答案其实很简单:

里面的压缩音频内容类型还是 AAC,但它外层的组织方式可能不同。

这和你前面文章里讲过的 H.264 Annex B / AVCC 非常像。


七、一张图看懂 AAC 在系统里的分层位置

flowchart TD
    A[PCM原始样本] --> B[AAC编码]
    B --> C[AAC码流]
    C --> D[ADTS或容器内组织]
    D --> E[MP4/FLV/TS/网络传输]
    E --> F[取出AAC数据]
    F --> G[AAC解码]
    G --> H[PCM样本]

这张图非常值钱,因为它提醒你:

  • AAC 自己是一层
  • 它外面还可能再套不同组织方式
  • 它前后都和 PCM 紧紧相连

八、第一段核心代码:PCM 数据量和 AAC 目标码率的直觉比较

先给一个非常实用的代码例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct AudioFormat {
int sample_rate;
int bits_per_sample;
int channels;
};

double PcmBitrate(const AudioFormat& fmt) {
return static_cast<double>(fmt.sample_rate) *
fmt.bits_per_sample *
fmt.channels;
}

double CompressionRatio(double pcm_bps, double aac_bps) {
return pcm_bps / aac_bps;
}

比如:

1
2
3
4
5
6
AudioFormat fmt{48000, 16, 2};
double pcm_bps = PcmBitrate(fmt); // 1536000 bps
double aac_bps = 128000.0;

printf("pcm bitrate = %.0f bps\n", pcm_bps);
printf("compression ratio = %.2f\n", CompressionRatio(pcm_bps, aac_bps));

你会看到大概:

  • 原始 PCM 大约 1.536 Mbps
  • 如果压成 128 kbps AAC
  • 压缩比已经很可观

这段代码的意义

它会让你一下看见 AAC 的系统价值不是抽象的,而是非常现实的:

拿更小的码率,换可接受的主观音质。


九、第二段核心代码:FFmpeg 里从 PCM 到 AAC 的最小编码骨架

这一段很关键,因为它直接把“PCM 是输入,AAC 是输出”落成代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
AVCodecContext* ctx = avcodec_alloc_context3(codec);

ctx->sample_rate = 48000;
ctx->channel_layout = AV_CH_LAYOUT_STEREO;
ctx->channels = 2;
ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
ctx->bit_rate = 128000;

avcodec_open2(ctx, codec, nullptr);

AVFrame* frame = av_frame_alloc();
frame->nb_samples = ctx->frame_size;
frame->format = ctx->sample_fmt;
frame->channel_layout = ctx->channel_layout;
frame->sample_rate = ctx->sample_rate;

av_frame_get_buffer(frame, 0);

// 假设这里把 PCM 数据填进 frame->data
FillPcmSamples(frame);

if (avcodec_send_frame(ctx, frame) == 0) {
AVPacket pkt;
av_init_packet(&pkt);
while (avcodec_receive_packet(ctx, &pkt) == 0) {
printf("got aac packet, size=%d\n", pkt.size);
av_packet_unref(&pkt);
}
}

这段代码最该看什么

你要特别注意:

输入侧

编码器吃的是:

  • sample_rate
  • channel_layout
  • sample_fmt
  • frame->data 里的 PCM 样本

输出侧

编码器吐出来的是:

  • 压缩后的 AVPacket

这就非常清楚地说明了 AAC 编码层的职责:

PCM in,compressed audio packet out。


十、第三段核心代码:为什么 AAC 编码前常常还要做重采样 / 格式转换

真实工程里,采集到的 PCM 不一定正好就是编码器想吃的格式。

比如:

  • 采上来是 s16
  • 编码器想吃 fltp
  • 采样率不一致
  • 声道布局不一致

这时候中间往往要有一层音频格式桥接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SwrContext* swr = swr_alloc_set_opts(
nullptr,
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLTP, 48000,
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, 48000,
0, nullptr);

swr_init(swr);

// 假设 input_data 是 S16 PCM
const uint8_t** input_data = ...;
uint8_t** output_data = frame->data;

int out_samples = swr_convert(
swr,
output_data, frame->nb_samples,
input_data, input_nb_samples);

这里说明什么

它说明 AAC 编码在工程里通常不是孤立一步,而往往是:

PCM采集格式 -> 转成编码器想要的 PCM 格式 -> 再送编码器。

这和视频那边像素格式转换再送编码器,本质上是一样的工程逻辑。


十一、第四段核心代码:AAC 解码后为什么最终又回到 PCM

这一段也很关键,因为它能帮你真正把链路闭环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
AVCodecContext* ctx = avcodec_alloc_context3(codec);
avcodec_open2(ctx, codec, nullptr);

AVPacket pkt;
av_init_packet(&pkt);

AVFrame* frame = av_frame_alloc();

while (ReadAacPacket(&pkt)) {
if (avcodec_send_packet(ctx, &pkt) == 0) {
while (avcodec_receive_frame(ctx, frame) == 0) {
printf("decoded pcm: nb_samples=%d sample_rate=%d format=%d\n",
frame->nb_samples,
frame->sample_rate,
frame->format);
}
}
av_packet_unref(&pkt);
}

这段代码最值钱的地方

它再一次提醒你:

AAC 不是播放设备最后直接工作的原始层。

解码器最终给出来的,依然是 PCM 风格的样本数据。

也就是说,AAC 的作用不是取代 PCM,而是:

在存储和传输阶段临时把 PCM 压紧。


十二、一张流程图看懂“PCM -> AAC -> PCM”为什么是正常闭环

flowchart TD
    A[原始PCM样本] --> B[重采样/格式转换]
    B --> C[AAC编码器]
    C --> D[AAC压缩Packet]
    D --> E[封装/传输]
    E --> F[解封装]
    F --> G[AAC解码器]
    G --> H[PCM样本]
    H --> I[播放/算法处理]

这张图特别值钱,因为它能帮你纠正一个很常见的误解:

很多人会潜意识觉得“有了 AAC 之后就一路 AAC 到底”。

其实不是。

在很多系统里更真实的主线是:

PCM 负责原始工作层,AAC 负责压缩传输层,最后又回到 PCM。


十三、一张时序图看懂视频文件里音频轨是怎么被处理的

sequenceDiagram
    participant App as Player/App
    participant Demux as Demuxer
    participant ADec as AAC Decoder
    participant AudioDev as Audio Device

    App->>Demux: open MP4/FLV/TS
    loop read packets
        Demux->>ADec: AAC packet
        ADec->>AudioDev: PCM samples
    end

这张图很适合理解:

播放器里音频轨不是“直接拿 AAC 去播”,而通常是:

  • 先 demux 拿出 AAC packet
  • 再 decode 成 PCM
  • 再交给音频设备

十四、AAC 在工程里最常出现在哪些地方

这一节我给你直接拉回工程视角。

14.1 文件封装里

比如:

  • MP4 里的音频轨常见就是 AAC

14.2 推流系统里

比如:

  • RTMP 推流时常见音频侧就是 AAC

14.3 采集后压缩存储

比如:

  • 摄像录制视频时,视频可能是 H.264,音频可能就是 AAC

也就是说,AAC 在很多系统里扮演的是:

音频侧默认的主力压缩格式之一。


十五、工程里最容易混掉的 4 个问题

问题 1:AAC 是不是文件格式

不是。

更准确说,它是音频编码格式 / 压缩音频码流表示。

问题 2:AAC 是不是采集设备直接吐出来的原始音频

通常不是。

采集侧更常直接拿到的是 PCM。

问题 3:播放器是不是直接把 AAC 喂给音响

通常不是。

播放器通常先把 AAC 解码成 PCM,再交给音频设备。

问题 4:AAC 和 MP4 是不是一个层级

不是。

  • AAC 是码流层
  • MP4 是容器层

这和你前面那篇“容器 vs 码流”是一模一样的分层逻辑。


十六、最后给一句更像工程师的话:别把 AAC 当成“音频最后长什么样”,它更像“音频为了存储和传输而临时压缩成的样子”

写到这里,最想落下的一句话其实是:

AAC 在工程里的真正位置,不是音频数据的原始形态,也不是最终播放设备最自然的形态,而是为了存储和传输效率而出现的一层压缩表示。

这句话特别值钱。

因为很多人学音频时,最大的问题不是不知道 AAC,而是没把它放回链路里。

一旦你把它放回这条主线:

  • PCM 采集
  • PCM 处理
  • AAC 压缩
  • 封装 / 传输
  • AAC 解压
  • PCM 播放

整个理解就稳了。

这样后面再去看:

  • 音频码率
  • 音视频封装
  • 推流音频链路
  • 音频同步
  • WebRTC 里的 Opus vs AAC

就都会清楚很多。


十七、总结

最后把这篇收成几句话。

1. AAC 的本质是 PCM 经过有损压缩之后得到的音频码流表示

它属于压缩编码层,不是原始采样层。

2. PCM 在 AAC 前后都存在

  • 编码前:AAC 吃 PCM
  • 解码后:AAC 吐 PCM

3. AAC 常和 MP4、FLV、TS 一起出现,但它们不是同一层

  • AAC:音频码流层
  • MP4/FLV/TS:容器层

4. 真正该记住的主线是

PCM -> AAC编码 -> AAC码流 -> 封装/传输 -> AAC解码 -> PCM

5. 一旦这条主线稳了,音频系统在你的整个专栏里就开始真正立起来了

如果你愿意,我下一篇建议直接接:

《GStreamer 的 pipeline 思维是什么:它和 FFmpeg 的差别到底在哪》

因为你现在 FFmpeg 这条线已经越来越厚了,这时候把 GStreamer 接进来,会让整个媒体框架认知一下子完整很多。


AAC 在音视频工程中的位置:从 PCM 到压缩音频的主线理解
https://breaker505.github.io/2026/05/02/aac-from-pcm-to-compressed-audio-in-av-systems/
作者
爱发呆的鱼
发布于
2026年5月2日
许可协议