什么是容器,什么是码流:MP4、FLV、TS 与裸流不要再混了

前言:为什么很多人学了一堆 H.264、RTP、FFmpeg,最后还是会把“码流”和“容器”说混

只要开始接触音视频工程,几乎迟早会遇到这些名字:

  • H.264
  • H.265
  • AAC
  • MP4
  • FLV
  • TS
  • Annex B
  • AVCC

刚开始学的时候,很多人会下意识把它们都放进一个大筐里,统称成“视频格式”。

这也不奇怪,因为从使用者视角看,它们确实都和“音视频文件能不能播”“流能不能推”“数据怎么传”有关。

但只要你真的开始写代码、抓包、看 FFmpeg、查播放器问题,很快就会发现:

如果你分不清“码流”和“容器”,后面很多东西都会一直混。

比如这些问题:

  • H.264 到底是不是 MP4
  • 为什么同样是 H.264,有时是 .h264,有时又在 .mp4
  • FLV 和 TS 到底是“协议”还是“文件格式”
  • 为什么裸流里会看到 SPS / PPS / NALU,而 MP4 里看起来又不是那个样子
  • FFmpeg 里为什么有 demux / mux,但编码器又单独是一层
  • 为什么推 RTMP 时常常会看到 H.264 + FLV 这样的组合

你会发现,这些问题表面不同,但根底上都在问一件事:

媒体内容本身,和承载这些内容的外层组织方式,到底怎么分层理解?

所以这篇文章的目标,就是把这件事一次讲清楚。

重点讲清:

  • 什么是码流,什么是容器
  • 为什么 MP4、FLV、TS 都不是“视频编码格式”
  • 裸流到底裸在哪里
  • 编码、码流、封装、传输在整条链路里分别站哪一层
  • FFmpeg 里 demux / mux 到底在处理什么
  • 如果你自己写一个最小音视频程序,这些层会怎么出现在代码里

而且这篇按你要求,我会继续把四样都带上:

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

我希望你读完之后,不只是“知道容器和码流不一样”,而是真的能建立一套稳定的分层认知,以后看到 MP4、FLV、TS、裸 H.264、RTP、FFmpeg 这些东西时,脑子不会再打架。


一、先给一句最重要的话:码流是媒体内容的压缩表达,容器是对这些媒体数据及其时间信息的组织与承载

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

码流是音视频内容经过编码后的压缩表达形式,而容器是把这些媒体数据、时间信息、轨道信息按某种规则组织起来的承载层。

这句话特别重要。

因为它直接把两个最容易混掉的层给分开了:

  • 码流,更偏内容本体怎么被压缩表示
  • 容器,更偏这些内容怎么被装起来、索引起来、同步起来、对外呈现成文件或流

你可以先粗暴记成:

  • 码流:像“货物本身”
  • 容器:像“装货的箱子 + 货单 + 时间表”

这个比喻虽然不完美,但对建立直觉非常有帮助。


二、先看总图:编码、码流、容器、传输到底在整条链路里处于哪一层

先上总图。

flowchart LR
    A[原始音视频数据] --> B[编码器 H.264/H.265/AAC]
    B --> C[压缩码流 ES]
    C --> D[封装器 Mux]
    D --> E[容器 MP4/FLV/TS]
    E --> F[文件存储或网络传输]

如果从播放侧反过来看,就是:

flowchart LR
    A[文件或网络输入] --> B[Demux 解封装]
    B --> C[取出压缩码流 Packet]
    C --> D[解码器]
    D --> E[原始Frame / PCM]
    E --> F[渲染 / 播放]

这两张图一起看,基本能把很多概念一下拉直:

  • 编码器负责把原始数据压成码流
  • 容器不是编码器,它只是把压缩数据组织起来
  • demux / mux 工作在容器层
  • decoder / encoder 工作在码流层和原始媒体层之间

这也是为什么 FFmpeg 里:

  • codec 是一层
  • format 又是一层

它们本来就不是一个问题。


三、什么是码流,它到底“流”在哪里

3.1 码流的本质

码流你可以先理解成:

编码器输出出来的一串压缩媒体数据。

比如:

  • H.264 码流
  • H.265 码流
  • AAC 码流

它回答的核心问题是:

  • 原始图像 / 音频样本,如何被压缩表达
  • 压缩后的比特序列长什么样
  • 解码器如何从这些比特里恢复出原始媒体

所以码流更偏:

内容压缩表示层。

3.2 视频码流里常见会看到什么

比如 H.264 / H.265 这类视频码流里,你常会看到:

  • NALU
  • SPS / PPS / VPS
  • slice
  • IDR / 非 IDR 图像数据
  • Annex B 或 AVCC 这类组织方式

这些东西都在围绕“压缩视频内容怎么表达”展开。

也就是说,它们关心的不是:

  • 文件名叫什么
  • 能不能拖动进度条
  • 里面有几条音轨

而是:

  • 这个压缩视频内容本身怎么编码与组织

3.3 音频码流也一样

比如 AAC,码流层更关心:

  • 压缩后的音频帧
  • ADTS 头或 AudioSpecificConfig
  • 解码器如何拿到正确参数

所以“码流”不是视频专属概念,音频也完全有。


四、什么是容器,它到底“装”了什么

4.1 容器的本质

容器你可以理解成:

把一个或多个媒体轨道、时间戳、索引、元信息按规则组织起来的封装格式。

这句话里有几个关键词特别重要:

  • 一个或多个轨道
  • 时间戳
  • 索引
  • 元信息
  • 组织规则

这说明容器关心的事情,比单纯“装字节”多很多。

4.2 容器通常装哪些东西

一个典型容器里,可能会包含:

  • 视频轨
  • 音频轨
  • 字幕轨
  • 每个 packet 的时间戳
  • 索引信息
  • 编码参数描述
  • 文件头 / 元信息

所以 MP4、FLV、TS 这些格式真正解决的是:

如何把媒体数据组织成一个可存储、可传输、可解析、可播放的整体。

4.3 容器并不决定“视频内容怎么压缩”

这点非常关键。

MP4 本身不负责定义:

  • H.264 的预测结构
  • H.265 的变换量化
  • AAC 的编码算法

这些是编码标准的工作。

MP4 做的更像是:

  • 你的视频已经压好了
  • 你把压好的数据按 MP4 的规则装进来
  • 再给它配上时间信息和索引信息

所以你不能把:

  • MP4
    n- H.264

看成同一层东西。


五、一张图看懂“码流”和“容器”为什么经常一起出现,却又根本不是一回事

flowchart TD
    A[H.264 / H.265 / AAC] --> B[压缩码流层]
    B --> C[MP4 / FLV / TS]
    C --> D[文件或网络承载]

这张图最关键的意思就是:

  • H.264/H.265/AAC 更像“里面装的内容类型”
  • MP4/FLV/TS 更像“外层包装方式”

你以后看见:

  • H.264 in MP4
  • H.264 in FLV
  • H.264 in TS

就不会再觉得矛盾了。

因为这本来就是:

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


六、什么叫裸流,裸到底是什么意思

6.1 裸流的本质

“裸流”这个词听起来很玄,其实意思很朴素。

它通常指:

没有再套完整容器层、而是更接近编码器直接输出结果的码流数据。

比如:

  • .h264
  • .h265
  • 某些原始 AAC 数据

这些通常就比 MP4、FLV、TS 更“裸”。

6.2 它为什么叫裸

因为它通常少了很多容器层会帮你做的事,比如:

  • 完整索引
  • 多轨统一组织
  • 拖动播放所需的结构信息
  • 丰富元信息
  • 文件级别头结构

也就是说,裸流更接近:

我这里只有被压好的媒体内容本体。

6.3 裸流为什么在工程里仍然很重要

因为它更接近编码器本来的输出形态。

比如你在研究:

  • SPS / PPS
  • NALU 切分
  • Annex B / AVCC
  • RTP 打包前的数据组织

这时你经常就会直接面对裸码流。

所以裸流虽然不总适合直接拿来做用户文件交付,但它在工程分析里非常重要。


七、MP4、FLV、TS 各自到底是什么定位

这一节很关键,因为很多人只知道这几个名词,却不知道它们各自擅长什么。

7.1 MP4

MP4 更偏:

  • 文件存储
  • 点播
  • 索引和随机访问友好
  • 结构化媒体文件

它通常适合:

  • 本地文件
  • 点播分发
  • 需要清晰轨道组织的场景

7.2 FLV

FLV 更偏:

  • 流式传输场景中的轻量封装
  • 传统 RTMP 生态里的常见承载格式

它在很多直播推流链路里长期常见,尤其是:

  • RTMP 推流时,媒体数据常按 FLV tag 方式组织

所以很多人会看到:

  • H.264 + AAC + FLV + RTMP

这不是四个平级概念,而是不同层的组合。

7.3 TS

TS 更偏:

  • 连续传输
  • 广播 / 流媒体传输友好
  • 对流式切片和容错更友好

它常出现在:

  • MPEG-TS 文件
  • 某些直播 / 切片 / 广播相关场景

所以你不能简单问“哪个更高级”,而要问:

当前这个场景,到底需要哪种封装能力。


八、第一段核心代码:最小 demux 流程到底在处理什么

现在上代码。

下面这个例子是一个非常典型的 FFmpeg 解封装骨架,重点是让你看见:

demux 处理的是容器,不是解码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
AVFormatContext* fmt = nullptr;
if (avformat_open_input(&fmt, input_url, nullptr, nullptr) < 0) {
throw std::runtime_error("open input failed");
}

if (avformat_find_stream_info(fmt, nullptr) < 0) {
throw std::runtime_error("find stream info failed");
}

AVPacket pkt;
av_init_packet(&pkt);

while (av_read_frame(fmt, &pkt) >= 0) {
// 这里拿到的是容器里拆出来的压缩数据包
// 还不是原始图像帧
printf("stream=%d pts=%lld dts=%lld size=%d\n",
pkt.stream_index,
static_cast<long long>(pkt.pts),
static_cast<long long>(pkt.dts),
pkt.size);

av_packet_unref(&pkt);
}

这段代码最该看懂什么

最该看懂的是:

av_read_frame() 干的事不是“给你一帧画面”,而是:

从容器里拆出一个压缩 packet。

这就是 demux 的职责边界。

也就是说:

  • MP4 / FLV / TS 这层先被 format 模块处理
  • 取出来的是压缩数据包
  • 后面要不要变成 AVFrame,是 decoder 的事

这条边界必须清楚。


九、第二段核心代码:最小 decode 流程为什么是“码流层”的工作

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

AVFrame* frame = av_frame_alloc();
AVPacket pkt;
av_init_packet(&pkt);

while (av_read_frame(fmt, &pkt) >= 0) {
if (pkt.stream_index != video_stream_index) {
av_packet_unref(&pkt);
continue;
}

if (avcodec_send_packet(codec_ctx, &pkt) == 0) {
while (avcodec_receive_frame(codec_ctx, frame) == 0) {
printf("decoded frame: w=%d h=%d format=%d\n",
frame->width, frame->height, frame->format);
}
}

av_packet_unref(&pkt);
}

这段代码说明什么

它说明解码器关心的是:

  • 输入压缩 packet
  • 输出原始 frame

这里处理的对象已经不再是“MP4 文件结构”,而是:

压缩码流内容。

所以你可以很清楚地看到两层分工:

  • format 层处理容器
  • codec 层处理码流

十、第三段核心代码:最小 mux 流程为什么是在“把码流重新装进容器”

这一层也很关键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
AVFormatContext* out_fmt = nullptr;
avformat_alloc_output_context2(&out_fmt, nullptr, "mp4", output_path);

AVStream* out_stream = avformat_new_stream(out_fmt, nullptr);
avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
out_stream->time_base = in_stream->time_base;

avio_open(&out_fmt->pb, output_path, AVIO_FLAG_WRITE);
avformat_write_header(out_fmt, nullptr);

AVPacket pkt;
while (ReadEncodedPacketSomehow(&pkt)) {
pkt.stream_index = out_stream->index;
av_interleaved_write_frame(out_fmt, &pkt);
av_packet_unref(&pkt);
}

av_write_trailer(out_fmt);

这段代码最该看什么

最该看的是:

  • mux 不负责重新编码内容本身
  • mux 负责把已有压缩 packet 按某种容器规则写出去

也就是说,这一层干的是:

组织与封装。

它再次证明:

容器层和编码层不是一回事。


十一、第四段核心代码:裸 H.264 码流和 MP4 输入在程序入口上到底有什么差别

这段特别适合建立“裸流 vs 容器”的代码直觉。

11.1 处理 MP4 输入时

你通常可以直接:

  • open input
  • find stream info
  • read packet

因为 MP4 已经帮你把轨道、时间、组织结构都整理好了。

11.2 处理裸 H.264 时,系统 often 需要更多帮助

裸流更接近原始压缩数据,所以在很多实现里,往往要更依赖:

  • parser
  • 手动送入 decoder
  • 手动处理帧边界 / access unit

下面是一个简化版 parser 骨架:

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
AVCodecParserContext* parser = av_parser_init(AV_CODEC_ID_H264);
AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_H264);
AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
avcodec_open2(codec_ctx, codec, nullptr);

uint8_t inbuf[4096];
while (ReadRawBytes(inbuf, sizeof(inbuf)) > 0) {
uint8_t* data = inbuf;
int data_size = sizeof(inbuf);

while (data_size > 0) {
AVPacket pkt;
av_init_packet(&pkt);

int used = av_parser_parse2(parser, codec_ctx,
&pkt.data, &pkt.size,
data, data_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
data += used;
data_size -= used;

if (pkt.size > 0) {
avcodec_send_packet(codec_ctx, &pkt);
// 后续再 receive_frame
}
}
}

这段代码为什么有价值

因为它直接让你看到:

裸流没有容器层替你把边界和结构整理得那么舒服。

所以在工程里,裸流往往更接近码流本体,需要你更直接面对 parser / NALU / access unit 这些问题。


十二、一张流程图看懂“码流进容器”和“从容器取码流”是两件对称但不同的事

flowchart TD
    A[原始Frame/PCM] --> B[编码器]
    B --> C[压缩码流Packet]
    C --> D[Mux封装]
    D --> E[MP4/FLV/TS]

    E --> F[Demux解封装]
    F --> G[压缩码流Packet]
    G --> H[解码器]
    H --> I[原始Frame/PCM]

这张图其实就是整条媒体主线最核心的骨架之一。

如果这张图立住了,后面你再看:

  • FFmpeg format
  • FFmpeg codec
  • MP4 / FLV / TS
  • H.264 / AAC

就基本不会再乱。


十三、一张时序图看懂播放器打开 MP4 时系统到底在干嘛

sequenceDiagram
    participant App as Player/App
    participant Demux as Demuxer
    participant Decoder as Decoder
    participant Render as Renderer

    App->>Demux: open MP4 file
    Demux->>Demux: parse container metadata / tracks
    loop read packets
        Demux->>Decoder: compressed video/audio packet
        Decoder->>Render: decoded frame / pcm
    end

这张图特别适合纠正常见误解:

  • 打开 MP4,不等于直接“读到画面”
  • 中间一定有容器解析和 packet 提取
  • decoder 处理的是 packet,不是整个 MP4 文件

十四、FLV、TS 为什么常常跟“传输”联系更紧,而 MP4 更像文件世界的常客

这个问题也值得单独说一下。

14.1 MP4 更偏结构化文件组织

MP4 对:

  • 多轨组织
  • 时间信息
  • 索引
  • 随机访问

这些事情做得很适合点播与文件。

14.2 FLV、TS 更常出现在流式场景里

比如:

  • RTMP 常见搭配 FLV tag 组织媒体
  • TS 常见于连续传输和切片相关场景

这不是说它们只能传输,或者 MP4 绝不能流式,而是说:

不同容器的设计取向和工程主战场不同。

所以你以后看到:

  • RTMP + FLV
  • HLS + TS
  • 文件点播 + MP4

就会觉得这些组合很自然。


十五、工程里最容易混掉的 4 个错误认知

这一节我给你直接列出来。

错误 1:MP4 是视频编码格式

不是。

MP4 是容器。

错误 2:H.264 是文件格式

不是。

H.264 更准确说是视频编码标准 / 码流格式。

错误 3:FLV 和 TS 跟 H.264 是平级可替换关系

不是。

它们通常处于不同层:

  • H.264 是里面的压缩视频内容
  • FLV / TS 是外层承载组织

错误 4:demux 就是 decode

不是。

  • demux:从容器里拆 packet
  • decode:把压缩 packet 解成原始 frame

这两层不分清,FFmpeg 永远看着像一团雾。


十六、最后给一句更像工程师的话:别把“媒体内容怎么压缩”和“媒体数据怎么装起来”混成一件事

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

音视频工程里,最基础但也最容易混的分层,就是“内容压缩层”和“数据承载层”。

如果你把这两层混掉,就会出现一连串认知问题:

  • 看不懂 FFmpeg 模块分工
  • 分不清 parser、demuxer、decoder 谁在干嘛
  • 一看到 MP4、FLV、TS、RTP、Annex B 就容易全串
  • 很难真正理解“转封装”和“转码”的区别

但如果你把这层分清,很多问题就会一下变简单:

  • H.264 / H.265 / AAC 是压缩表达
  • MP4 / FLV / TS 是承载组织
  • demux / mux 处理容器
  • decoder / encoder 处理码流与原始媒体的转换

到这一步,你的整个音视频知识体系会明显稳很多。


十七、总结

最后把这篇收成几句话。

1. 码流和容器不是一个层次的东西

  • 码流:压缩后的媒体内容表达
  • 容器:对媒体数据和时间信息的组织与承载

2. H.264/H.265/AAC 更偏码流层,MP4/FLV/TS 更偏容器层

它们经常组合出现,但不是平级概念。

3. 裸流更接近编码器原始输出,容器则提供更完整的组织能力

所以裸流更适合看码流本体,容器更适合文件与整体播放交付。

4. FFmpeg 里 demux/mux 和 decode/encode 分属不同层

这层边界清楚后,整个框架会好理解很多。

5. 真正该记住的不是几个名词,而是这条主线

原始媒体 -> 编码得到码流 -> mux 装进容器 -> demux 拆出 packet -> decode 还原原始媒体

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

《音频系统基础入门:采样率、位宽、声道数与 PCM 到底怎么理解》

这篇补上之后,你这套专栏就不再是“视频特别强,音频还偏薄”,整个体系会更完整。


什么是容器,什么是码流:MP4、FLV、TS 与裸流不要再混了
https://breaker505.github.io/2026/03/22/container-vs-bitstream-mp4-flv-ts-and-elementary-stream/
作者
爱发呆的鱼
发布于
2026年3月22日
许可协议