三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

FFmpeg 入门绕不开的两个词:封装是快递盒,解封装是拆快递

FFmpeg 入门绕不开的两个词:封装是快递盒,解封装是拆快递

目录

一、先扔一句人话定义

二、想象一个快递包裹(经典比喻)

三、解封装(Demux)到底干了什么?

1️.读文件头,认出“这是什么盒子”

2️.把 Stream 列出来(但还没解码)

3️.开始吐 Packet(av_read_frame)

四、封装(Mux)是反过来的事

流程:

五、一个最容易混淆的点

MP4 里存的不是“视频”吗?

六、用一张 FFmpeg 调用链串起来

七、为什么“解封装”会出问题?(实战经验)

1️.你以为读到的是一帧画面

2️.时间戳是“容器给的”

3️.裸流没有封装(.h264 / .aac)

八、一句话记忆法

九、给 Qt / C++ 程序员的映射表


觉得有用,就请您帮忙点赞转发收藏吧,您的鼓励是我创作的动力,多谢看官。

由于能力水平有限,文中的错误或不严谨的地方在所难免,还请批评指正。

FFmpeg 是一个非常强大的开源项目,它包含了大量用于处理视频和音频的库和工具。其中,封装(Muxing)和解封装(Demuxing)是处理多媒体文件时常见的操作。封装是将多个媒体流(例如视频、音频和字幕流)合并成一个单一的文件格式,例如 MP4 或 MKV。解封装则是将封装好的文件拆分成原始的媒体流,以便单独处理或编辑。

很多人第一次看 FFmpeg 示例,都会被一句话劝退:

“先解封装,再解码”

心里 OS:

封装?解码?不是一个意思吗?

今天把话讲清楚:

FFmpeg 里,「封装 / 解封装」和「编码 / 解码」完全是两层世界的事。


一、先扔一句人话定义

名词

FFmpeg 叫法

人话

封装

Mux / Muxing

把音视频塞进一个文件

解封装

Demux / Demuxing

从一个文件里把音视频拆出来

编码

Encode

原始数据 → 压缩数据

解码

Decode

压缩数据 → 原始数据

一句话:

封装 ≠ 压缩

MP4 / MKV / FLV 是“盒子”

H264 / HEVC / AAC 是“盒子里的货”


二、想象一个快递包裹(经典比喻)

你下载的movie.mp4

┌─────────────── movie.mp4 (Container) ───────────────┐ │ │ │ ┌─────────────── Video Stream ─────────────┐ │ │ │ H264 / HEVC bitstream (.h264 / .m4s) │ │ │ └──────────────────────────────────────────┘ │ │ │ │ ┌─────────────── Audio Stream ─────────────┐ │ │ │ AAC / Opus packets │ │ │ └──────────────────────────────────────────┘ │ │ │ │ Metadata / Subtitle / Chapter / Cover │ └─────────────────────────────────────────────────────┘

FFmpeg 的职责是:

  • 解封装(Demux):拆盒子 → 拿出 N 个“数据流”

  • 解码(Decode):把每个流里的压缩数据 → PCM / YUV


三、解封装(Demux)到底干了什么?

对应 FFmpeg 代码世界:

AVFormatContext *fmt_ctx = avformat_alloc_context(); avformat_open_input(&fmt_ctx, "test.mp4", NULL, NULL); avformat_find_stream_info(fmt_ctx, NULL);

这一步只干三件事

1️.读文件头,认出“这是什么盒子”

FFmpeg 会说:

“哦,这是 MP4(ISOBMFF),有 2 个流”

fmt_ctx->iformat->name == "mov,mp4,m4a,3gp,3g2,mj2"


2️.把 Stream 列出来(但还没解码)

fmt_ctx->nb_streams; // 通常是 2

每个流:

AVStream *st = fmt_ctx->streams[i]; st->codecpar->codec_type; // AVMEDIA_TYPE_VIDEO / AUDIO

你得到的是:

Stream

codecpar 告诉你

video

H264 / HEVC / VP9

audio

AAC / OPUS

subtitle

ass / srt / webvtt

注意

此时你连一帧画面都没解码


3️.开始吐 Packet(av_read_frame)

while (av_read_frame(fmt_ctx, pkt) >= 0) { // pkt 属于某个 stream_index }

AVPacket是什么?

一个“时间连续的压缩数据块”

  • 视频:一个 / 多个 NALU(不一定是完整帧)

  • 音频:若干采样点的 AAC ADTS frame

重点:

av_read_frame()只拆快递,不拆箱子里的东西


四、封装(Mux)是反过来的事

你做播放器时很少用

你做转码 / 录屏 / 存文件​ 时天天用。

流程:

YUV → encode → AVPacket (video) PCM → encode → AVPacket (audio) ↓ av_write_frame() ↓ mp4 / mkv / flv

代码骨架:

avformat_alloc_output_context2(&oc, NULL, NULL, outfile); avformat_new_stream(oc, codec_ctx); avformat_write_header(oc, NULL); while (has_pkt) { av_interleaved_write_frame(oc, pkt); } av_write_trailer(oc);

FFmpeg 负责:

  • 按时间戳交错写(audio/video interleaving)

  • 写 moov box

  • 写 index / duration / metadata

你决定“怎么塞”,FFmpeg 决定“盒子怎么封口”


五、一个最容易混淆的点

MP4 里存的不是“视频”吗?

严格说:

  • ❌ MP4 ≠ 视频格式

  • ✅ MP4 =容器格式

  • ✅ H264 = 编码格式

所以这句话是对的:

同一个 MP4 里

可以装 H264 / HEVC / AV1

也可以装 AAC / FLAC / Opus

FFmpeg 里:

模块

管什么

libavformat

封装 / 解封装

libavcodec

编码 / 解码

libswscale

YUV 换尺寸

libswresample

PCM 换采样率


六、用一张 FFmpeg 调用链串起来

.mp4 ↓ avformat_open_input ← 解封装开始 ↓ av_read_frame ↓ AVPacket (compressed) ↓ avcodec_send_packet ← 解码开始 ↓ avcodec_receive_frame ↓ AVFrame (YUV420P / PCM)

反过来:

AVFrame ↓ encode ↓ AVPacket ↓ av_write_frame ← 封装开始 ↓ .mkv / .mp4

七、为什么“解封装”会出问题?(实战经验)

1️.你以为读到的是一帧画面

av_read_frame()
  • H264 Annex-B:可能是 SPS/PPS

  • B 帧:后面才显示

  • HEVC:VPS/SPS/PPS + slice

packet ≠ frame

一个 frame 可能拆成 N 个 packet


2️.时间戳是“容器给的”

  • pkt->pts/pkt->dts来自demux

  • decode 后frame->pts是解码器重新算的

所以:

音画不同步,90% 在封装层时间基没处理好

av_q2d(stream->time_base);

3️.裸流没有封装(.h264 / .aac)

你直接读.h264

  • 没 stream

  • 没 timebase

  • 没 duration

  • 没 audio/video 绑定关系

FFmpeg 示例里要手写AVFormatContext

新手直接劝退现场


八、一句话记忆法

封装 = 把多股绳子捆成一捆

解封装 = 把绳子一根一根解开

编码 = 把棉花压成砖

解码 = 把砖拆回棉花


九、给 Qt / C++ 程序员的映射表

FFmpeg

Qt 类比

AVFormatContext

QIODevice + 文件头解析器

AVStream

QMediaPlayer::mediaStatus() 前的元数据

AVPacket

readyRead() 吐的一块 buffer

AVFrame

QImage / QAudioBuffer

← 返回列表