原生解封装与解码:FFmpeg 最小闭环
AVFormatContext、AVPacket、AVCodecContext 与 AVFrame
用 FFmpeg 对象关系理解打开输入、查找流、读取 Packet、send/receive 解码和 flush 的完整协议。
先明确本课目标
区分 AVFormatContext 与 AVCodecContext
正确使用 send_packet / receive_frame
处理 EOF、EAGAIN 与 flush
libavformat 把容器拆成 AVPacket;libavcodec 把 Packet 消费成 AVFrame。两个库之间以 Codec 参数、时间戳和流索引交接。
FFmpeg 解码循环为什么有两层 while?
解码器有内部输入和输出队列。外层读取 Packet 并发送,内层持续接收所有已可用 Frame;遇到 EAGAIN 后再回到另一侧推进。
cppwhile (av_read_frame(format, packet) >= 0) {
if (packet->stream_index == videoStream) {
avcodec_send_packet(codec, packet);
while (avcodec_receive_frame(codec, frame) == 0) {
render(frame);
av_frame_unref(frame);
}
}
av_packet_unref(packet);
}- Demux 读取 Packet av_read_frame 按文件交错顺序返回不同轨道 Packet。
- 发送匹配轨道数据 只有对应 stream_index 的 Packet 进入该解码器。
- 取尽当前可用 Frame receive 直到 EAGAIN,避免输出积压。
容器 Packet 被稳定转换为带像素格式和 PTS 的 AVFrame,且引用得到及时释放。
OPTIONAL · 按需查阅完整术语表与概念边界遇到陌生术语时再展开,不打断主线实验。
展开⌄
边改边运行
浏览器加载后会激活代码编辑器、独立运行环境与实时输出。
写出同时解码音频和视频、保留 PTS 并正确 flush 的最小 C++ 循环。
回到原理,解释刚才发生了什么
先用结果建立反馈,再按运行流程、核心原理和知识关系逐层深入。
核心讲解
Packet 和 Frame 不是一一对应
send 一个 Packet 可能暂时没有输出,也可能 receive 多个 Frame。EAGAIN 表示需要在发送和接收之间继续推进状态机。
EOF 后仍要冲刷解码器
有 B 帧或内部延迟时,最后一些 Frame 仍留在解码器。向 send_packet 传 null 再持续 receive 才能取完。
OPTIONAL · 迁移时查阅Web 与原生实现对照主线实验完成后,再用完整映射处理跨平台或跨 API 迁移。
展开⌄
FFmpeg 把浏览器内部 Demux/Decode 的核心对象显式交给原生应用。
WebGL
libavformat 直接解析容器,libavcodec 通过 Packet/Frame send-receive API 解码。
WebGPU
WebCodecs 接受应用已经 Demux 的 EncodedVideoChunk,并输出 VideoFrame。
WebCodecs 从 FFmpeg 流程中拿走了 Demux 层;原生 FFmpeg 则把容器和 Codec 都补齐。