在 VS2022 中编译 Live555 并集成到现有项目

最近在一个 Windows 桌面端项目中需要实现 RTSP 视频流拉取功能。项目本身已经有一个 VideoModule 动态库模块负责从共享内存中读取视频帧并显示,我们希望在此基础上增加从网络摄像头拉取 RTSP 流的能力,而 Live555 是一个成熟且常用的选择。然而将 Live555 编译为 Windows 静态库并融入现有 CMake/VS 工程并非一帆风顺,其间遇到了不少配置与兼容性问题。本文从零开始,逐步记录整个集成过程,包括:

  • 如何将 Live555 源码组织到解决方案中

  • 如何创建一个静态库项目并配置编译环境

  • 如何解决编译错误(std::atomic_flagifaddrs.h、OpenSSL 依赖等)

  • 如何配置生成后事件实现自动化

  • 如何在调用模块中链接该静态库


1. 项目背景与设计思路

当前我们的解决方案中已有一个 VideoModule 动态库模块,它负责获取视频帧并将视频帧发给主程序显示。现在需要增加 RTSP 拉流能力,但先不做解码,只完成流媒体协议交互并拿到编码后的视频帧。所以我们决定:

  • 将 Live555 四个核心库(UsageEnvironmentgroupsockliveMediaBasicUsageEnvironment)编译成 单个静态库 MyLive555lib.lib

  • VideoModule 项目中链接该静态库,直接在动态库内调用 Live555 的 RTSP 客户端 API。

  • 所有输出 .lib 文件集中管理,通过生成后事件自动复制到公共库目录。

整体依赖关系如下:

复制代码
VideoModule.dll  ─── 依赖 ───> MyLive555lib.lib                                 ws2_32.lib

未来可以在 VideoModule 内部创建 RTSP 客户端,在帧回调中将数据写入共享内存,供主程序或其他模块消费。


2. 源码准备与目录结构

从 Live555 官网(Index of /)或 GitHub 下载源码,解压后找到这四个目录(其他如 WindowsAudioInputDevicetestProgs 等不需要):

复制代码
live/
├── UsageEnvironment/
│   ├── include/    (头文件 .hh)
│   └── *.cpp
├── groupsock/
│   ├── include/
│   ├── *.cpp
│   └── *.c        (可能存在)
├── liveMedia/
│   ├── include/
│   └── *.cpp
└── BasicUsageEnvironment/
    ├── include/
    └── *.cpp

我选择将这些源文件直接复制到解决方案下新创建的的 MyLive555lib 项目目录中,并按原有模块分文件夹放置,便于后续在 VS 中按筛选器整理。即先进行3,将静态库项目创建后,再将文件复制进来。

实际最终磁盘结构如下:

复制代码
SolutionDir/
├── VideoModule/          (已有的动态库项目)
├── MyLive555lib/          (即将创建的静态库项目)
│   ├── UsageEnvironment/
│   │   ├── include/       (头文件)
│   │   └── *.cpp
│   ├── groupsock/
│   │   ├── include/
│   │   ├── *.cpp, *.c
│   ├── liveMedia/
│   │   ├── include/
│   │   └── *.cpp
│   └── BasicUsageEnvironment/
│       ├── include/
│       └── *.cpp
├── lib/                   (公共库输出目录)
└── YourSolution.sln

说明:也可以在添加文件时使用"添加为链接"方式保持源码位置不变,我这里为了归档方便直接复制了进来。


3. 创建 MyLive555lib 静态库项目

  1. 在 VS2022 中打开解决方案,右键解决方案 → 添加新建项目

  2. 选择 静态库 模板,项目名称设为 MyLive555lib,位置选在 SolutionDir\MyLive555lib\

  3. 创建完成后,删除项目自动生成的 .cpppch.hpch.cppframework.h 等文件(因为我们不需要预编译头)。

  4. 打开项目属性(注意选择所有配置和所有平台,或者至少对应你需要的配置,如 Release x64):

    • C/C++ → 预编译头 → 预编译头 改为 不使用预编译头

    • 高级 → 字符集 → 选择 使用多字节字符集(Live555 假定多字节字符集)。

    • C/C++ → 代码生成 → 运行库 → 选择 多线程 DLL (/MD) (与调用它的VideoModule动态库保持一致)。


4. 组织筛选器(强迫症友好)

在解决方案资源管理器中,右键 MyLive555lib 项目,添加下列筛选器(虚拟文件夹),让结构清晰:

复制代码
MyLive555lib
├── liveMedia
│   ├── 头文件
│   └── 源文件
├── groupsock
│   ├── 头文件
│   └── 源文件
├── UsageEnvironment
│   ├── 头文件
│   └── 源文件
└── BasicUsageEnvironment
    ├── 头文件
    └── 源文件

然后通过右键每个 源文件/头文件 筛选器 → 添加现有项 ,将对应模块的 .cpp.hh 文件分别添加进去。注意 groupsock 下如果有 .c 文件也同样加入源文件,VS 会以 C 方式编译。


5. 项目属性关键配置

打开 MyLive555lib 项目属性,我们需要设置以下选项。

5.1 附加包含目录

让编译器能直接通过文件名找到头文件。因为源文件就位于各模块子目录中,头文件在 include 子目录下,所以包含目录应为:

复制代码
$(ProjectDir)UsageEnvironment\include
$(ProjectDir)groupsock\include
$(ProjectDir)liveMedia\include
$(ProjectDir)BasicUsageEnvironment\include

在项目属性的 C/C++ → 常规 → 附加包含目录 中填入,用分号分隔。

5.2 预处理器定义

这是最核心的一步,用于解决 Windows 平台兼容性和减少外部依赖。在 C/C++ → 预处理器 → 预处理器定义 中填入以下宏(分号分隔,注意不要漏掉分号):

复制代码
NOMINMAX
_CRT_SECURE_NO_WARNINGS
_WINSOCK_DEPRECATED_NO_WARNINGS
WIN32_LEAN_AND_MEAN
NO_OPENSSL
NO_STD_LIB
NO_GETIFADDRS

各宏含义:

用途
NOMINMAX 避免 Windows.h 定义的 min/max 宏冲突
_CRT_SECURE_NO_WARNINGS 禁止不安全 CRT 函数警告
_WINSOCK_DEPRECATED_NO_WARNINGS 禁止 Winsock 废弃警告
WIN32_LEAN_AND_MEAN 精简 Windows.h 包含内容,减少编译时间
NO_OPENSSL 不编译 TLS/SSL 相关代码,避免需要 OpenSSL 头文件
NO_STD_LIB 使用 Live555 自带的备选原子操作实现 ,避免依赖 VS2022 已废弃的 std::atomic_flag::test
NO_GETIFADDRS 禁用 getifaddrs() 调用 ,Windows 下没有 ifaddrs.h,此项让 Live555 使用备用方法获取本机 IP

5.3 其他设置

  • C/C++ → 高级 → 禁用特定警告 → 填入 4996(可选,忽略不安全函数警告)。

  • 链接器 → 常规 → 输出文件 保持默认,最终会生成 MyLive555lib.lib


6. 编译与错误修复

第一次直接生成,可能出现若干错误。以下是我实际遇到的及其修复方法。

6.1 ifaddrs.h 找不到

错误信息

复制代码
GroupsockHelper.cpp(46,10): error C1083: 无法打开包括文件: “ifaddrs.h”: No such file or directory

原因 :Live555 在未定义 NO_GETIFADDRS 时默认包含该头文件。

解决 :已在预处理器定义中添加 NO_GETIFADDRS,直接跳过该包含。

6.2 std::atomic_flag::test 不是成员

错误信息

复制代码
BasicTaskScheduler.cpp(191,40): error C2039: "test": 不是 "std::atomic_flag" 的成员

原因 :VS2022 的 <atomic> 中移除了 std::atomic_flag::test 方法。Live555 在 BasicUsageEnvironment0.hh 中有条件编译分支:若定义了 NO_STD_LIB,则使用 Boolean volatile fTriggersAwaitingHandling[...] 代替 std::atomic_flag

解决 :添加预处理器定义 NO_STD_LIB 即可,无需修改任何源码。

6.3 OpenSSL 头文件找不到

大量错误

复制代码
TLSState.hh(34,10): error C1083: 无法打开包括文件: “openssl/ssl.h”: No such file or directory

原因 :Live555 默认启用 TLS 支持,需要 OpenSSL。

解决 :宏 NO_OPENSSL 可禁用所有 SSL 相关代码,已在预处理器定义中添加。

6.4 strtolabort 未定义

错误信息

复制代码
RTSPCommon.cpp(40,22): error C3861: “strtol”: 找不到标识符
UsageEnvironment.cpp(49,3): error C3861: “abort”: 找不到标识符

原因 :某些源文件没有显式包含 <stdlib.h>,而 VS2022 的某些头文件依赖链发生了变化。

解决 :在报错的 RTSPCommon.cppUsageEnvironment.cpp 文件最顶部添加:

复制代码
#include <stdlib.h>

这是本次集成中唯二直接修改源码的地方


7. 设置生成后事件(复制 lib)

为了方便其他项目链接,我们让 MyLive555lib 编译成功后自动将输出的 .lib 文件复制到公共库目录。

在项目属性 → 生成事件生成后事件 的命令行中输入:

复制代码
if not exist "$(SolutionDir)lib" mkdir "$(SolutionDir)lib"
xcopy /y "$(OutDir)$(TargetName)$(TargetExt)" "$(SolutionDir)lib\"

这里假设公共目录是 $(SolutionDir)lib,可根据需要调整。也可以按 Debug/Release 区分,将路径改为 $(SolutionDir)lib\$(Configuration)\


8. 重新生成 MyLive555lib

做完以上所有设置后,右键 MyLive555lib重新生成 。此时应该能够顺利编译,只有少量类型转换警告,可忽略。生成成功后,检查 lib 目录,应能看到 MyLive555lib.lib


9. 在 VideoModule项目中集成

现在我们拥有了静态库,需要让 VideoModule项目能够使用 Live555 的功能。

9.1 配置 VideoModule项目属性

打开 VideoModule项目属性,进行以下设置(注意选择与 MyLive555lib 相同的配置和平台):

  1. 附加包含目录 :与静态库项目相同,添加四个 include 路径(从 VideoModule项目目录出发的相对路径可能稍有不同,示例为 ..\MyLive555lib\...\include,按实际调整)。

  2. 预处理器定义 :添加与 MyLive555lib 完全一致的宏:

    复制代码
    NO_OPENSSL;NO_STD_LIB;NO_GETIFADDRS

    同时保留原有的 NOMINMAX 等。

  3. 字符集 :同样设为 多字节字符集

  4. 附加库目录 :在 链接器 → 常规 → 附加库目录 中添加 $(SolutionDir)lib\(或带配置子目录)。

  5. 附加依赖项 :在 链接器 → 输入 → 附加依赖项 中添加:

    复制代码
    MyLive555lib.lib
    ws2_32.lib
  6. 运行库 :保持 /MD,与静态库一致。

9.2 注意预处理器宏的一致性

NO_STD_LIB 会改变 BasicTaskScheduler0 类中 fTriggersAwaitingHandling 数组的类型和大小。如果静态库和调用它的模块之间该宏定义不一致,会导致结构体大小不同,运行时可能发生难以排查的崩溃。所以务必保持两个项目的宏完全相同。


10. 在 VideoModule中编写 RTSP 拉流代码框架

至此编译环境已就绪,可以在 VideoModule的某个源文件中包含 Live555 头文件:

复制代码
#include <liveMedia.hh>
#include <BasicUsageEnvironment.hh>
#include <GroupsockHelper.hh>

拉流的基本步骤遵循 Live555 的经典用法:创建 TaskSchedulerUsageEnvironment,然后创建 RTSPClient,发送 DESCRIBE 命令;在回调中拿到 MediaSession,对每个 MediaSubsession 执行 SETUP 和 PLAY;同时创建我们自定义的 MediaSink(通常称为 DummySink),在其 afterGettingFrame 回调中获取每一帧的原始编码数据 (fTo,长度为 frameSize),然后将其写入共享内存中,供主程序读取显示。

由于本文主要聚焦于编译集成,具体 RTSP 交互逻辑可参考 Live555 自带的 testRTSPClient 示例,或继续在本模块中实现。


11. 整体编译运行

  1. 确保 MyLive555lib 已编译成功且 .lib 已复制到公共目录。

  2. 编译 VideoModule,应无链接错误。

  3. 主程序加载 VideoModule.dll,在适当位置调用其提供的 RTSP 启动函数,传入摄像头 RTSP 地址,内部开始拉流并将帧不断写入共享内存。

  4. 主程序从共享内存取出帧并显示。


12. 常见问题与注意事项

  • 链接错误 LNK2019(无法解析的外部符号) :检查是否忘记了 ws2_32.lib 依赖,或库路径是否正确。

  • 运行时崩溃 :大概率是 NO_STD_LIB 等宏在静态库和使用者之间不一致,重新确认两边预处理定义完全一致。

  • ssize_t 未定义 :Live555 的 NetCommon.h 会处理,但在极端情况下可在包含 Live555 头文件前自行 typedef SSIZE_T ssize_t;

  • WinSock 重定义 :包含顺序应先 winsock2.hwindows.h,Live555 自身已处理。


结语

通过上述步骤,我们成功将 Live555 编译为静态库并集成到现有的 Windows 动态库模块中,为项目增加了 RTSP 拉流能力。整个过程虽然遇到了不少编译问题,但借助 Live555 自身预留的编译选项(NO_OPENSSLNO_STD_LIBNO_GETIFADDRS 等),我们几乎无需修改源码就完成了适配。希望这篇记录能帮助到在 Windows 下集成 Live555 的开发者。