🎬 第三章 RTP与RTCP流媒体协议
📄 一句话概括

JRTPLIB 不是单个库,它依赖 jthread,所以必须编两次、且顺序不能反。 四步走完,把两个 .lib 和头文件拷进自己的工程就算通。

📝 本节和前面几节的关系

3.1 到 3.3 讲的都是「协议长什么样」,从这一节起开始动真代码。 这一节在 Windows 上编库,3.4.2 在 Ubuntu 上编同一个库,3.4.3 用编好的库收发第一个包。

一、为什么是两个包

JRTPLIB 的源码包里,线程、互斥量、定时器这些全部交给 jthread 做。 所以下 jrtplib 之前必须先下 jthread。

两者的分工很清楚: ① jthread:跨平台的线程封装(Windows 上包 CreateThread,Linux 上包 pthread),外加互斥量与条件变量。 ② jrtplib:RTP/RTCP 的会话管理,内部开一个后台线程收包,这个线程就是 jthread 建的。

所以「先编 jthread,再编 jrtplib」不是建议,是硬依赖。 顺序反了会在链接期报一大片未解析外部符号,而报错信息里写的是 jthread::JMutex::Lock 之类的名字,第一次见很容易以为是 C++ 标准库的问题。

二、四步:下载、生成、编译、拷贝

① 下载:jrtplib 取 3.11.2,jthread 取 1.3.3,两个版本要配对,混版本偶发编译不过。 ② 生成工程:用 CMake,别手工建工程。 命令是 cmake -G "Visual Studio 14 2015 Win64" ..——14 就是 v140 工具集,对应 VS 2015。 ③ 编译:先 jthread 的 INSTALL 目标,再 jrtplib 的 INSTALL 目标。 ④ 拷贝:把 .lib 放进链接目录、头文件放进 include 目录。

两个容易踩的细节: ① 位数要一致:Win64 生成 64 位库,自己的工程也必须是 x64。32 位工程链 64 位库,报的是 LNK1112: 模块计算机类型冲突。 ② 运行时库要一致:Debug 用 MDd、Release 用 MD。 混了会报 LNK2038: 检测到 RuntimeLibrary 的不匹配项。

三、库到底帮你做了什么

很多人编完库、跑通示例,却说不清它省了自己多少事。 实测把它的行为拆开,其实只有三件:

① 记住配置:SSRC、PT、时钟频率、MTU 这些每包都要用的值,库替你存着,不用每次传。 ② 自动递增序号:每发一包序号加一,绕回 65535 自动归零。 这一行自己写也对,但加上多线程就不会了。 ③ 收包时按序号排队:PollData 内部会处理乱序与重复。 这是最值钱的一条——自己写要维护一个窗口缓冲区,边界条件极多。

换句话说:RTP 头的拼装是最简单的部分,难的是「多线程 + 乱序窗口」。 JRTPLIB 的价值全在后半段。

四、沙箱里怎么验证语义

这一节的核心动作是「在 Windows 上编库」,而沙箱里没有 VS 2015。 所以做法是: 真实步骤按【📋 实操路径】写清,代码语义用 Python 等价实现验证。

等价实现的五个动作,和 JRTPLIB 的五个接口一一对应: 建 socket → bind → 设 PT → 记对端 → 发/收。 跑通之后你会发现:五步里有四步都是「配置」,只有最后一步是「做事」。 这就是封装型库的典型形态。

要在真机上复现,建议按这个顺序试: 先只编 jthread 并跑它自带示例(验证编译器没问题)→ 再编 jrtplib 跑 example1(验证链接对)→ 最后才改自己的代码。 一次只前进一步,出错时才知道是哪一层的问题。

五、动手 Demo:VS 2015 的四步与沙箱里的等价验证

上表是【📋 实操路径】——需要一台装了 VS 2015 的 Windows 10。 下面这段是沙箱能跑的等价实现:用五个动作复刻 JRTPLIB 的五步接口。

import socket, struct

SSRC, PT, CLK = 0x1A2B3C4D, 96, 90000

# ① RTPSession sess;   ② Create(params):建 socket 并 bind
snd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
snd.bind(("127.0.0.1", 0))
rcv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
rcv.bind(("127.0.0.1", 0))
rcv.settimeout(2.0)

# ③ SetDefaultPayloadType / SetDefaultMark / ④ AddDestination
dest = rcv.getsockname()

# ⑤ SendPacket:序号自动递增,时间戳按帧走
for i in range(5):
    hdr = struct.pack("!BBHII", 0x80, PT, 100 + i, CLK // 25 * i, SSRC)
    snd.sendto(hdr + ("帧%d" % i).encode("utf-8"), dest)

# ⑤ PollData / GetNextPacket
got = []
for _ in range(5):
    try:
        got.append(rcv.recvfrom(2048)[0])
    except socket.timeout:
        break

print("收到 %d 个包" % len(got))
print("序号 %s" % [struct.unpack("!BBHII", d[:12])[2] for d in got])

实跑输出:

--------------------------------------------------------------------
编译 JRTPLIB——VS 2015 的四步与沙箱里的替代验证
--------------------------------------------------------------------
  📋 **这一节的主体是「在 Windows 上把 JRTPLIB 编出来」,而沙箱里没有 VS 2015。**
  **所以真实步骤按实操路径写,代码部分用一个纯 Python 的等价实现验证语义。**

**① 实操路径(在有 VS 2015 的 Windows 10 上)**

  步     做什么                                    关键点
  ------------------------------------------------------------------------------------------------
  1      下载 jrtplib-3.11.2 与 jthread-1.3.3      两个包都要,jrtplib 依赖 jthread
  2      用 CMake 生成 VS 2015 工程                `cmake -G "Visual Studio 14 2015 Win64" ..`
  3      先编 jthread,再编 jrtplib                顺序错了会报一堆未解析外部符号
  4      把 jrtplib.lib 与头文件拷进自己的工程     Debug 配 Debug、Release 配 Release,别混

  **第 3 步是最容易卡住的:**
  **jrtplib 内部大量用到 jthread 的线程与互斥量封装,先编 jthread 才能拿到 .lib。**
  **而 VS 2015 的默认工具集是 v140。**
  **CMake 生成时要保证 `-G` 参数对得上,否则打开工程会提示「需要升级」。**

**② 沙箱里的替代:用 Python 复刻 JRTPLIB 的五步接口**

  **JRTPLIB 的用法永远是这五步,语言换成什么都一样:**
  **建会话 → 设载荷类型 → 加目的地址 → 发 → 收。**

  JRTPLIB 的调用               这里等价做的事            结果
  --------------------------------------------------------------------------
  RTPSession sess;             建两个 UDP socket         成功
  Create(sessparams)           bind 到 127.0.0.1:0       端口 57043
  SetDefaultPayloadType        PT = 96                   写进头
  SetDefaultMark               M = 0                     写进头
  AddDestination               记下对端地址              127.0.0.1:57043
  SendPacket                   打包 12 字节头 + 载荷     发 5 个
  PollData / GetNextPacket     recvfrom 并解析           收 5 个

  **实测收到 5 个包,序号 100、101、102、103、104。**
  **所以库帮你做的其实就三件事:**
  **① 记住 SSRC/PT/时钟这几项配置;② 每次发自动递增序号;③ 收的时候把乱序的包按序号排好。**
  **这三件事自己写也就几十行,用库是为了少踩边界条件的坑。**

实测收到 5 个包,序号 100 到 104——这正是 SendPacket 内部「序号自动加一」的结果。 PT 与 SSRC 一次设好、每包复用,对应库里的 SetDefault* 系列。 这段代码里没有一行是 RTP 协议特有的「难题」,真正的难题(乱序窗口、多线程、超时重传)都在库里。

30 秒实验

  1. 把 PT 从 96 改成 97,看接收端解出来的 PT 变不变——96 以上都是动态类型,具体含义要靠 SDP 说明。
  2. 把 5 个包的 SSRC 改成两个不同的值,模拟「两路流复用一对端口」,看接收端能不能分开。
  3. 在真机上先只编 jthread 跑示例,确认能过再编 jrtplib——分步验证比一次编完省时间。

常见误区

  1. 「jrtplib 可以单独编」——它硬依赖 jthread,且必须后编。
  2. 「x64 工程链 32 位库也行」——会报 LNK1112 模块计算机类型冲突。
  3. 「Debug 库给 Release 用没事」——会报 LNK2038 运行时库不匹配,症状是偶发崩溃。
自测三问(点开对答案) 1. 为什么必须先编 jthread? —— **jrtplib 的收包线程、互斥量都来自 jthread。**先编才能拿到 `.lib`。 2. CMake 里 `Visual Studio 14 2015` 的 14 是什么意思? —— **v140 工具集版本号。**对应 VS 2015,写错会提示「需要升级工程」。 3. JRTPLIB 帮你做的三件事里,哪一件最值钱? —— **收包时按序号排队。**多线程加乱序窗口的边界条件最难写。

相关笔记