🎬 第七章 流媒体开源库简介
📄 一句话概括

开发包就是「头文件 + 库文件」,让你不通过命令行直接调库。 拿到它,才谈得上自定义流程。

📝 本节和前面几节的关系

7.1.2 讲了命令行,这一节讲它背后那层。 7.2 转向第一个专用库:Live555,只做 RTSP。

一、开发包里有四样东西

① 头文件:libavcodec/avcodec.h 之类,给编译器看。 ② 库文件:.a 是静态、.so 或 .dll 是动态。 ③ pkg-config 描述:libavcodec.pc 之类,给构建脚本看。 ④ 命令行工具:编出来顺手带的,给用人看。

第三样最容易被忽略,但它是「编译能不能过」的关键。 没有 .pc 文件,你就得手工敲一堆 -I 与 -L 参数, 换台机器路径一变就全挂。

所以检查开发包装没装好,最快的办法是: pkg-config --libs libavcodec,能打出一串库名就算装好了。

二、直接调库要过四道门

① 打开:avformat_open_input 拿到上下文。 ② 找流:avformat_find_stream_info 拿到流的参数。 ③ 读包:av_read_frame 一个个取出压缩包。 ④ 解码:avcodec_send_packet 加 avcodec_receive_frame。

实测用 PyAV 走一遍,这几步对应得很清楚: 打开得到容器名与流数、找流得到编码名、读包得到 25 个包。

注意第 ④ 步是「送一个、收一个」的模型,不是「解一个」: 因为一个包可能解不出帧(比如 B 帧要等后续帧), 也可能一个包解出多帧。所以 API 拆成了 send 与 receive 两个动作。

三、动态库版本号那点事

libavcodec.so 后面常带一个主版本号,比如 62。 大版本变了就不能直接换——API 与 ABI 都可能不兼容。

这就是为什么升级 FFmpeg 之后,老程序会报「找不到 libavcodec.so.60」。 它要找的是 60,而系统里现在只有 62。

三种应对方式,代价各不相同: ① 重编程序——最干净,但要能拿到源码。 ② 装兼容包——系统里同时留两个版本,靠 soname 区分。 ③ 容器化——把程序与它依赖的库一起打包,一劳永逸。

生产环境里第 ③ 种最省心, 这也是为什么媒体处理服务特别爱用容器部署—— 它依赖的库太多,版本冲突的坑太深。

四、动手 Demo:开发包——没有它就只能用命令行

列出开发包的四样东西,再用 PyAV 走一遍「打开、找流、读包」四道门。 命令行做不到的事,只有在这一层才做得到。

import av, numpy as np, tempfile, os

p = os.path.join(tempfile.mkdtemp(), "a.mp4")
c = av.open(p, "w", format="mp4")
st = c.add_stream("libx264", rate=25)
st.width, st.height, st.pix_fmt = 320, 240, "yuv420p"
st.options = {"crf": "30", "preset": "veryfast", "g": "25"}
for i in range(25):
    f = av.VideoFrame(320, 240, "yuv420p")
    y = np.zeros((240, 320), np.uint8)
    y[:, :] = np.linspace(16, 200, 320, dtype=np.uint8)[None, :]
    f.planes[0].update(y.tobytes())
    for pi in (1, 2):
        f.planes[pi].update(bytes([128]) * f.planes[pi].buffer_size)
    f.pts = i
    for pk in st.encode(f):
        c.mux(pk)
for pk in st.encode(None):
    c.mux(pk)
c.close()

with av.open(p) as c2:
    print("① 打开:容器 %s,流数 %d" % (c2.format.name, len(c2.streams)))
    s = c2.streams.video[0]
    print("② 找流:%s,编码 %s" % (s.type, s.codec_context.name))
    n = 0
    for pk in c2.demux(s):
        if pk.size:
            n += 1
    print("③ 读包:%d 个" % n)
    print("④ 解码:需要 send/receive 两步,一个包不一定出一帧")

实跑输出:

--------------------------------------------------------------------
开发包——没有它就只能用命令行
--------------------------------------------------------------------
**① 开发包里到底有什么**

  部分           内容                         给谁用
  --------------------------------------------------------
  头文件         libavcodec/avcodec.h 等      编译器
  库文件         .a 静态或 .so/.dll 动态     链接器
  pkg-config     libavcodec.pc 等             构建脚本
  命令行工具     ffmpeg/ffprobe/ffplay      人

**② 真用一次,看它调了哪些能力**

  **容器 mov,mp4,m4a,3gp,3g2,mj2**,**流数 1**
  **流 #0:video,编码 h264**
  **解封装出 25 个包**

  **上面这几行分别用到了:**
  **libavformat(打开容器与解封装)、libavcodec(识别编码)、**
  **libavutil(处理帧与包对象)。**
  **所以「用开发包」不是一句空话,它就是直接调这三个库。**

**③ 动态库版本号那点事**

  **libavcodec.so 后面常带一个主版本号,比如 62。**
  **大版本变了就不能直接换——API 与 ABI 都可能不兼容。**
  **这就是为什么装完 FFmpeg 升级后,老程序会报「找不到 libavcodec.so.60」。**

容器名是一串别名(mov/mp4/m4a…),因为它们共用同一套解析器。 25 个包对 25 帧是一一对应,但没有 B 帧,所以包数与帧数相等。 有 B 帧时包数会多于帧数,这正是 send/receive 要分开的原因。

30 秒实验

  1. 打印 s.codec_context.name,换成 ts 文件再跑——变成 h264,说明编码识别是跨容器的。
  2. 把 GOP 改成 5,看包数变不变——不变,GOP 只影响关键帧位置,不影响包数。
  3. 加一行 print(s.bit_rate),看码率——可能是 None,因为封装里不一定写了码率。

常见误区

  1. 「装了 ffmpeg 命令行就等于装了开发包」——两回事,头文件不在里面。
  2. 「一个包一定解出一帧」——不一定,B 帧会推迟。
  3. 「升级库不用重编程序」——大版本变了就要重编。
自测三问(点开对答案) 1. 开发包里有哪四样东西? —— **头文件、库文件、pkg-config 描述、命令行工具。** 2. 为什么解码要 send/receive 两步? —— **一个包不一定出一帧**,也可能一个包出多帧。 3. 升级后报找不到 .so.60 怎么办? —— **重编、装兼容包、或容器化**,三者代价不同。

相关笔记