🎬 第二章 把控FFmpeg骨架:【八大金刚】核心开发库
📄 一句话概括

libavformat 是门房:认容器、拆包、把包塞回容器,搬的是 AVPacket。 它不认识编码,只知道「这个容器里装的是 h264 还是 aac」这个标签。

📝 本节和前面几节的关系

2.2 讲底座,这一篇开始讲业务层的第一个库。 它给出的 AVPacket 要交给 2.4 的 libavcodec 才解得开。

一、它能认 414 种格式

本机 414 个格式名,分三类:容器、协议、设备。 类 例子 干什么


容器 mp4、flv、mkv、ts 文件的壳 协议 file、http、rtsp、rtp 怎么取数据 设备 dshow、gdigrab 摄像头、屏幕、声卡

这三类混在一张表里,是 FFmpeg 的特色设计:打开文件和打开摄像头用的是同一个函数, 区别只在给的名字。 本机没有 rtmp,所以往 RTMP 推流会「打开成功但发不出数据」。 这类问题查起来最费劲,因为返回码是 0。

二、它的四步动作

步 函数 拿到什么


1 avformat_open_input AVFormatContext 2 avformat_find_stream_info 每路流的参数 3 av_read_frame AVPacket 4 av_interleaved_write_frame 写进容器

第二步常被跳过,跳过的代价是时间基不准。 不调它,stream.time_base 可能是猜的, 做时间换算时会漂。 第四步的 interleaved 是关键词:交错写把音视频包按时间穿插进文件, 否则播放器读起来会先播完所有视频再播音频。

三、一个文件里有多路流

AVFormatContext 里挂着一个 AVStream 数组,每一路一个。 字段 含义 为什么重要


index 流的编号 包里的 stream_index 指向它 time_base 时间刻度 算秒数必须用它 codecpar 编码参数 建解码器要用

最容易犯的错是把视频的包喂给音频解码器。 av_read_frame 每次都告诉你 stream_index, 按它分流就行。 第二个容易错的是拿错 time_base。 视频与音频的 time_base 通常不同,混用会让同步整个歪掉。

四、它不知道编码,只知道标签

容器里存的是压缩包,libavformat 只负责把它们搬出来。 它知道这包属于哪路流、什么时刻, 但不知道怎么变成画面。 这就是转封装能不走编码的原因:解开容器、换个容器装回去,中间一个像素都没动,所以又快又无损。 代价是「只能原样搬」:想改分辨率、改码率、加水印,就必须解码重编,那是 2.4 与 2.7 的活。

五、动手 Demo:libavformat——认容器的门房

这一段查八个格式名在不在本机构建里,再看它干的四件事。 rtmp 那一行是本机最值得记住的「不在」。

import av

fa = set(av.formats_available)
for n in ("mp4", "flv", "hls", "rtsp", "rtmp"):
    print(n, n in fa)

实跑输出:

--------------------------------------------------------------------
libavformat——认容器的门房
--------------------------------------------------------------------

**① 它能认 414 种格式,但能读与能写不是一回事**

  格式         本机构建里有
  -----------------------------
  mp4          在
  flv          在
  hls          在
  rtsp         在
  rtmp         不在
  matroska     在
  mpegts       在
  avi          在

**这张表里最值得看的是 `rtmp` 那一行。** 本机这版没有它,
意味着用 PyAV 往 RTMP 推流会「打开成功但一个字节也发不出去」——这个坑在 30 课里踩过,
原因就是 libavformat 没编 rtmp。

**② 它干的活只有四件**

  动作     函数                           拿走什么
  -----------------------------------------------------------
  打开     avformat_open_input            AVFormatContext
  找流     avformat_find_stream_info      每路流的参数
  读包     av_read_frame                  AVPacket
  写包     av_interleaved_write_frame     写进容器

**注意它搬的是 AVPacket,不是 AVFrame。** 容器里存的本来就是压缩后的包,
解封装不需要解码——这也是「转封装为什么快」的全部原因。

**③ 一个文件里有多路流**

一路视频、一路音频是常态,`avformat_find_stream_info` 之后每路各有一个 AVStream,
各自带 `time_base`、`codecpar`。
**把视频的包喂给音频解码器,是新手最容易犯的错**——两边 time_base 都不一样。

**④ 它不认识编码**

libavformat 只认「这个容器里装的是 h264 还是 aac」这个标签,不知道 h264 怎么解。
**解码是 libavcodec 的事。**所以读文件的流程永远是「format 打开 → codec 解码」两段。

mp4、flv、hls、rtsp 都在,rtmp 不在。 「能打开」不等于「能工作」——30 课在 RTMP 上踩过这个坑。

30 秒实验

  1. python -c "import av; print(len(av.formats_available))" 数格式。
  2. 拿一个 mp4 文件,只做解封装不解码,看输出的包有多大、间隔多久。
  3. rtmp 与 rtmps 都查一遍,看本机有没有替代方案。

常见误区

  1. 跳过 avformat_find_stream_info。 time_base 可能是猜的,时间换算会漂。
  2. 不按 stream_index 分流。 把视频包喂给音频解码器,报错还很难查。
  3. 以为能打开就能用。 rtmp 打开成功的返回码是 0,但一个字节都发不出去。
自测三问(点开对答案) 1. 容器、协议、设备为什么混在一张格式表里? —— 因为 FFmpeg 用同一个打开函数处理三者,区别只在给的名字不同。这是它的设计特色。 2. 转封装为什么不掉画质? —— 它只搬 AVPacket,不解码也不编码,像素数据一个都没动。 3. `av_interleaved_write_frame` 里的 interleaved 是什么意思? —— 按时间把音视频包交错写进文件,否则播放器会先播完视频再播音频。

相关笔记