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来自demuxdecode 后
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 |
