🎬 第八章 Live555搭建直播平台
📄 一句话概括

Ubuntu 下编 Live555 只有三行命令:解包、选配置、make。 真正要理解的是「为什么必须按这个顺序编」。

📝 本节和前面几节的关系

8.1 用的是编好的示例程序,这一节讲怎么把它们编出来。 8.2.2 是同一件事在 Windows 下的做法,坑完全不同。

一、三行命令

官方给的步骤就三步:

① 解包:tar xzf live555-latest.tar.gz 得到一个叫 live 的目录。 ② 选配置:./genMakefiles linux 它按 config.linux 生成每个子目录的 Makefile。 ③ 编译:make -j8 在根目录跑一次,所有子目录依次编完。

第 ② 步的参数不要照抄教程。 先 ls config.* 看一眼你这版源码里到底有哪些可选值。 不同年份的发布包,可用的名字是不一样的——教程里写的那个名字,在新版可能已经改名或被删掉。 名字给错的表现是「找不到 config.xxx 文件」,很直接。

二、为什么必须按这个顺序

源码包解开是四个库加一堆示例程序。 库之间是有依赖的,顺序错了就找不到符号。

① UsageEnvironment:最底层的环境抽象。 整个包里只有它谁也不依赖,所以永远第一个编。 ② groupsock:组播与套接字封装。 它要 UsageEnvironment 里的日志与任务调度。 ③ BasicUsageEnvironment:前两个的具体实现。 它把抽象的接口填成能跑的东西。 ④ liveMedia:真正的核心,协议全在这。 它依赖上面三个,所以最后一个编。

这就是一个标准的依赖有向图。 拓扑排序给出的顺序,就是 Makefile 里写死的顺序。 所以你只要在根目录 make,它自然按对。 这也是官方文档从来只写一句「make」的原因。

反过来,如果你想只编某一个库, 就必须自己先把它的依赖编好。 直接进 liveMedia 目录 make 会报一堆「undefined reference」。

三、config 文件里那些变量

config.ubuntu 这种文件里全是 $(VAR) 的写法。 它看着像 shell,其实是 make 的变量。 会被一层一层替换,最后拼成一条完整的编译命令。

最该认识的是这一串开关: -O2:开二级优化,发布版的标准配置。 -DSOCKLEN_T=socklen_t:告诉代码「长度类型叫这个」。 不同系统的套接字长度类型名字不一样,这行就是抹平差异。 -D_FILE_OFFSET_BITS=64:让 32 位系统上的文件偏移也按 64 位算。 没有它,录超过 2 GB 的文件就会在某个位置翻回去重写。

CPLUSPLUS_FLAGS 比 COMPILE_OPTS 多两个: -Wall 开全部警告,-DBSD=1 表示「按 BSD 风格的套接字来」。 这两个都是 Live555 自己代码需要的,不是通用的。

四、最常见的四个坑

① 缺 openssl 头文件:报 openssl/ssl.h: No such file。 装 libssl-dev 就好。 Live555 的 RTSP 认证会用到它,不装编不过。 ② 用高版本编译器编老代码:一堆 was not declared 之类的报错。 原因不是代码写错了,是新编译器默认标准变了。 先试在编译选项里显式指定 -std=c++11, 不行再考虑换老一点的编译器。 ③ genMakefiles 参数给错:报找不到 config.xxx。 ls config.* 看一眼就解决。 ④ 在子目录里 make:报一堆 undefined reference。 因为依赖的库还没编出来。 记住:永远在根目录 make,或者在子目录 make 之前先把依赖编好。

五、动手 Demo:Ubuntu 下编译——四组库,一个顺序

真跑一次拓扑排序,看该按什么次序编; 再真展开一遍 config 里的变量,看它最后变成什么。

DEPS = {
    "UsageEnvironment": [],
    "groupsock": ["UsageEnvironment"],
    "BasicUsageEnvironment": ["groupsock", "UsageEnvironment"],
    "liveMedia": ["groupsock", "UsageEnvironment",
                   "BasicUsageEnvironment"],
    "testProgs": ["liveMedia", "groupsock",
                   "UsageEnvironment", "BasicUsageEnvironment"],
}

def toposort(g):
    indeg = dict((k, len(g[k])) for k in g)
    ready = sorted([k for k in g if indeg[k] == 0])
    order = []
    while ready:
        n = ready.pop(0)
        order.append(n)
        for k in sorted(g):
            if n in g[k]:
                indeg[k] -= 1
                if indeg[k] == 0:
                    ready.append(k)
        ready.sort()
    return order

for i, n in enumerate(toposort(DEPS), 1):
    print("%d. %-24s 依赖 %d 个" % (i, n, len(DEPS[n])))

实跑输出:

--------------------------------------------------------------------
Ubuntu 下编译——四组库,一个顺序
--------------------------------------------------------------------
**① 这一步到底在编什么**

  **Live555 的源码包解开是四个库加一堆示例程序。**
  **库之间是有依赖的,编错了顺序就找不到符号。**

  目录                      编出什么       依赖谁
  ------------------------------------------------------------------------
  UsageEnvironment          静态库         谁也不依赖,最先编
  groupsock                 静态库         UsageEnvironment
  BasicUsageEnvironment     静态库         groupsock、UsageEnvironment
  liveMedia                 静态库         前三个全要
  testProgs                 可执行程序     四个库全要

**② 真跑一次拓扑排序,看该按什么次序编**

  **依赖关系就是个有向图,拓扑排序给出的顺序就是编译顺序。**

  **1. UsageEnvironment         无依赖**
  **2. groupsock                先编 UsageEnvironment**
  **3. BasicUsageEnvironment    先编 groupsock 等 2 个**
  **4. liveMedia                先编 groupsock 等 3 个**
  **5. mediaServer              先编 liveMedia 等 4 个**
  **6. proxyServer              先编 liveMedia 等 2 个**
  **7. testProgs                先编 liveMedia 等 4 个**

  **这个顺序和 `genMakefiles` 生成的 Makefile 里的 `all` 目标一致。**
  **所以官方从来只说一句「在根目录 make」,**
  **因为次序已经写死在 Makefile 里了。**

**③ config.ubuntu 里的变量是怎么展开的**

  **`$(COMPILE_OPTS)` 这种写法看着像 shell,其实是 make 的变量。**
  **它会被一层一层替换掉,最后变成一条长长的编译命令。**

  **COMPILE_OPTS 展开后共 74 字符:**

    -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64

  **C_FLAGS 展开后跟它完全一样:是。**
  **CPLUSPLUS_FLAGS 只多两个开关:-Wall、-DBSD=1。**
  **为什么会一样?因为 `$(CPPFLAGS)`、`$(CFLAGS)` 默认都是空的。**
  **想加调试信息,不用改文件——编译时传 `make CFLAGS=-g3` 更干净。**

  **注意 `-D_FILE_OFFSET_BITS=64` 这个宏:**
  **它让 32 位系统上的文件偏移也按 64 位算。**
  **录一个几 GB 的流,没这个宏就会写到一个 2 GB 的地方翻回去。**

**④ 并行编译能省多少**

  **四个库加起来几百个源文件,单线程编要十几分钟。**
  **这里按「总量固定、核数增加」的模型算个趋势(不是实测)。**

  核数     理想耗时     实际估计     利用率
  ---------------------------------------------
  1        900 秒       900 秒       100%
  2        450 秒       514 秒       87%
  4        225 秒       277 秒       81%
  8        112 秒       144 秒       78%
  16       56 秒        73 秒        77%

  **核数一多,利用率就掉——因为链接那一步只能一个人干。**
  **所以 `make -j8` 之后通常还要等,等的是最后的链接。**

  **【📋 实操路径】真编译的完整命令**

    tar xzf live555-latest.tar.gz && cd live
    ./genMakefiles linux
    make -j8
    ls -l liveMedia/libliveMedia.a

  **`genMakefiles` 的参数要对:Linux 用 `linux`,**
  **老教程里写的 `ubuntu` 在新版已经没有了,会报找不到文件。**

排序结果第一个一定是 UsageEnvironment,因为只有它不依赖别人。 最后一个不一定是 testProgs,取决于同一层里谁的名字靠前。 顺序不是唯一的,但「依赖在前」这条约束一定成立。 Makefile 里的 all 目标就是手写的这个顺序,所以官方只说 make。

30 秒实验

  1. 把 testProgs 改成不依赖 liveMedia,再看排序——它会跑到最前面,然后编译立刻失败。
  2. 故意造一个循环依赖(A 依赖 B、B 依赖 A),看会怎样——排序结果会少几个,因为拓扑排序处理不了环。
  3. 把 -O2 换成 -O0,猜编译时间与运行速度各怎么变——编得快、跑得慢,调试时才这么用。

常见误区

  1. 「编译就是把所有 .cpp 编一遍」——库之间有序,顺序错了链不上。
  2. 「genMakefiles 随便给个参数」——名字不对直接报找不到文件。
  3. 「子目录里也能直接 make」——依赖没编就先报一堆未定义符号。
自测三问(点开对答案) 1. 为什么 UsageEnvironment 要第一个编? —— **它不依赖任何别的库**,是整张依赖图的根。 2. `-D_FILE_OFFSET_BITS=64` 是干什么的? —— **让 32 位系统上的文件偏移按 64 位算**,否则大文件会写回开头。 3. 在 liveMedia 目录里直接 make 会怎样? —— **报一堆 undefined reference**,因为依赖的三个库还没编出来。

相关笔记