在 Windows 上编译 baresip 4.12.0:一个 1.3MB 单文件 SIP 软电话的诞生记
在 Windows 上编译 baresip 4.12.0:一个 1.3MB 单文件 SIP 软电话的诞生记
环境:Windows 11 + Visual Studio 2026 Community(MSVC 14.51)
产物:baresip.exe(x64 / 静态 CRT / 无任何第三方 DLL 依赖 / 约 1.3 MB)
用途:对接 FreeSWITCH 做语音调度联调,与 RK3588 板端 baresip 同一套版本
0. 为什么要在 Windows 上自己编译
baresip 官方并不提供 Windows 二进制包,社区流传的旧版要么缺 Opus、要么拖着
一堆 DLL。我们的诉求很简单:
- 单文件:一个 exe 拷到哪都能跑,不装运行库(静态链接 MSVC 运行时)
- 带 Opus:和板端、FreeSWITCH 协商编码一致
- 带 wasapi:用 Windows 原生音频接口,不要 portaudio/sdl 那套
最终交付物验证数据(后文有复现命令):
baresip.exe 1,338,880 字节 (约 1.3 MB)
SHA256: 6b984658f89d1df8933ff1a51062d731618beec664fd76e7484e2bc66baa589f
machine: 8664 (x64) subsystem: 3 (Windows CUI 控制台程序)
依赖 DLL 仅 8 个系统库:
qwave.dll / IPHLPAPI.DLL / WSOCK32.dll / WS2_32.dll
ole32.dll / KERNEL32.dll / SHELL32.dll / ADVAPI32.dll
也就是说:没有 MSVCP140.dll、没有 VCRUNTIME140.dll、没有 libopus-0.dll,
双击即用。
1. 环境准备
| 组件 | 版本 | 说明 |
|---|---|---|
| Visual Studio | 2026 Community(目录名 18) |
安装「使用 C++ 的桌面开发」工作负载 |
| CMake | VS 自带 4.3.1-msvc1 | 不需要单独装,路径见下 |
| MSVC 工具链 | 14.51.36231 | |
| 源码 | baresip 4.12.0 / re 4.12.0 / opus 1.5.2 | 见下 |
VS 自带的 cmake 在这个路径(版本号 18 对应 VS2026,老版本是 17):
C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe
提示:可以直接把上面这个
...\CMake\bin加进 PATH,后面命令就写cmake。
用 VS 生成器不需要跑 vcvars 脚本,cmake 会自己找工具链。
源码获取
三个仓库的正式发布 tag:
| 组件 | 仓库 | tag |
|---|---|---|
| opus | https://github.com/xiph/opus | v1.5.2 |
| re(baresip 的网络协议栈) | https://github.com/creytiv/re | v4.12.0 |
| baresip | https://github.com/baresip/baresip | v4.12.0 |
注意:re 4.12.0 起自带 rem 子目录,不需要再单独编译 rem,三步(opus → re → baresip)即可。
国内网络如果拉 GitHub 慢,可以给地址加 gh-proxy 镜像前缀,
或直接用 tarball(baresip-v4.12.0.tar.gz / re-v4.12.0.tar.gz / opus-1.5.2.tar.gz)。
本文假设的目录布局(保持这个结构,后面命令可直接复制):
C:\Code\baresip-win\
├── src\ # 源码:opus-1.5.2 / re-4.12.0 / baresip-4.12.0
├── build\ # 构建目录:opus / re / baresip
├── deps\ # 安装树:include / lib / bin / share(铃声 wav)
└── deliver\ # 最终交付:exe + 配置文件
2. 三步构建
三步的共同思想:全部装进同一个 deps 前缀,后面的步骤从里面找头文件和库。
命令按顺序复制粘贴即可(在任意终端,bash / cmd 都行——注意第 3 坑的引号差异)。
2.1 第一步:Opus 1.5.2
cmake -S C:/Code/baresip-win/src/opus-1.5.2 ^
-B C:/Code/baresip-win/build/opus ^
-DCMAKE_INSTALL_PREFIX=C:/Code/baresip-win/deps ^
-DOPUS_STATIC_RUNTIME=ON
cmake --build C:/Code/baresip-win/build/opus --config Release
cmake --install C:/Code/baresip-win/build/opus --config Release
坑①:-DOPUS_STATIC_RUNTIME=ON 不能省。
opus 的 CMakeLists.txt(274-280 行附近)会无条件覆写
CMAKE_MSVC_RUNTIME_LIBRARY——你在外层传 CMAKE_MSVC_RUNTIME_LIBRARY 根本
传不进去。它专门提供了 OPUS_STATIC_RUNTIME 这个开关,打开后 opus 才会用
/MT(静态 CRT);不打开就是 /MD,最后链接时 CRT 不匹配报一堆
LNK2038: RuntimeLibrary 不匹配。
2.2 第二步:re(libre)4.12.0
cmake -S C:/Code/baresip-win/src/re-4.12.0 ^
-B C:/Code/baresip-win/build/re ^
-DCMAKE_INSTALL_PREFIX=C:/Code/baresip-win/deps ^
-DLIBRE_BUILD_SHARED=OFF ^
-DCMAKE_DISABLE_FIND_PACKAGE_OpenSSL=ON ^
-DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$<CONFIG:Debug>:Debug>"
cmake --build C:/Code/baresip-win/build/re --config Release
cmake --install C:/Code/baresip-win/build/re --config Release
坑②:OpenSSL 必须用 CMAKE_DISABLE_FIND_PACKAGE_OpenSSL=ON 关掉,
-DUSE_OPENSSL=OFF 是无效的。
re 的 cmake/re-config.cmake 里 find_package(OpenSSL) 是无条件执行的,
机器上没装 OpenSSL 会直接配置失败。USE_OPENSSL=OFF 只是后续的功能开关,
管不住这个 find_package。CMAKE_DISABLE_FIND_PACKAGE_* 是 CMake 的原生机制,
设上之后 find_package(OpenSSL) 会当作"找不到"直接跳过(后面 baresip 那步同理)。
LIBRE_BUILD_SHARED=OFF 则保证 re 编成静态库(re-static.lib),配合 -DSTATIC=ON 才能单文件。
2.3 第三步:baresip 4.12.0
cmake -S C:/Code/baresip-win/src/baresip-4.12.0 ^
-B C:/Code/baresip-win/build/baresip ^
-DCMAKE_INSTALL_PREFIX=C:/Code/baresip-win/deps ^
-DCMAKE_PREFIX_PATH=C:/Code/baresip-win/deps ^
-DSTATIC=ON ^
-DMODULES="wasapi;wincons;g711;opus;auconv;auresamp;uuid;stun;turn;ice;account;contact;debug_cmd;menu;netroam;ctrl_tcp" ^
-DCMAKE_DISABLE_FIND_PACKAGE_OpenSSL=ON ^
-DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$<CONFIG:Debug>:Debug>" ^
-DRE_INCLUDE_DIR=C:/Code/baresip-win/deps/include/re ^
-DRE_LIBRARY=C:/Code/baresip-win/deps/lib/re-static.lib ^
-DOPUS_INCLUDE_DIR=C:/Code/baresip-win/deps/include ^
-DOPUS_LIBRARY=C:/Code/baresip-win/deps/lib/opus.lib
cmake --build C:/Code/baresip-win/build/baresip --config Release --target baresip_exe
cmake --install C:/Code/baresip-win/build/baresip --config Release
坑③:可执行目标叫 baresip_exe,不是 baresip。
baresip 这个工程里,baresip 是核心静态库的名字,exe 目标叫 baresip_exe。
直接 --target baresip 会构建出一堆库却看不到 exe。
关于 -DMODULES(16 个模块,按需裁):
| 模块 | 作用 |
|---|---|
wasapi |
Windows 原生音频(采集/播放/提示音) |
wincons |
Windows 控制台交互(就是让你敲 /dial 的那个界面) |
g711 / opus |
编码:PCMU/PCMA、Opus |
auconv / auresamp |
采样格式转换、重采样 |
uuid |
UUID 生成(SIP 标识用) |
stun / turn / ice |
NAT 穿透(跨网段互通必备) |
account / contact |
账户与联系人命令 |
debug_cmd |
调试命令 |
menu |
来电接听/回铃音/按键菜单 |
netroam |
网络变化(换 WiFi 重新注册) |
ctrl_tcp |
开放 TCP 控制端口(用脚本控 baresip 时有用) |
cmake --install 会把 exe 装到 deps\bin\,并把铃声 wav 装到 deps\share\baresip\——
铃声路径是安装时写进配置模板的,所以整目录一起拷才不丢铃声。
3. 大坑复盘:$<$...> 的引号写法
CMAKE_MSVC_RUNTIME_LIBRARY 的值里有个生成器表达式 $<$<CONFIG:Debug>:Debug>,
不同终端的引号写法不一样,写错就被 shell 吞掉:
| 终端 | 写法 | 原因 |
|---|---|---|
| bash / Git Bash | 'MultiThreaded$<$<CONFIG:Debug>:Debug>' |
单引号防一切展开;< 在 bash 里是重定向符 |
| cmd.exe | "MultiThreaded$<$<CONFIG:Debug>:Debug>" |
双引号防 < 被当重定向 |
| PowerShell | 'MultiThreaded$<$<CONFIG:Debug>:Debug>' |
单引号防 $ 被当变量展开 |
4. Windows 版的一个先天限制:没有命令行参数
MSVC 环境没有 getopt.h,源码里 #ifdef HAVE_GETOPT 会把全部参数解析逻辑剥掉。
后果:
-f 配置目录、-m 模块、-e之类的命令行选项全部不可用;- 配置文件路径是写死的(见下一节),只有一个地方生效;
- 启动就等于:双击 exe,它自己找配置、自己注册。
这不是开关没打开,是 Windows 构建下的既定行为,不用挣扎。
5. 运行时三件套(Windows 上最容易翻车的部分)
5.1 配置真正生效的位置:%APPDATA%\.baresip
这是最坑的一条:exe 所在目录旁边放 config、accounts 是没用的,
baresip 只认:
%APPDATA%\.baresip\config
%APPDATA%\.baresip\accounts
%APPDATA%\.baresip\contacts
也就是 C:\Users\<用户名>\AppData\Roaming\.baresip\。
实战教学:我们把这个 exe 拷给同事用,他双击后控制台显示
Populated 0 accounts(一个账号都没加载)——因为配置还在我这边,
他那边 %APPDATA%\.baresip 是空的。给别人的正确姿势:
exe + config + accounts 两个文件,让对方放到自己 %APPDATA%\.baresip\ 下,
重启即生效。改完配置也要重启 baresip 才生效(没有热重载)。
一份能用的 accounts 长这样(分机 1002 注册到 FreeSWITCH 5070 端口):
"测试话机" <sip:1002@192.168.86.56:5070>;auth_pass=<你的SIP密码>;regint=60;audio_codecs=opus/48000/2
对应的 config 关键行:
sip_listen 0.0.0.0:5064 # 同机避开 linphone(5060) 和 FreeSWITCH(5070)
audio_player wasapi,default
audio_source wasapi,default
audio_alert wasapi,default
ctrl_tcp_listen 127.0.0.1:4444 # 可选,脚本控制口
同机端口分配经验:一个 Windows 上最多跑一个 SIP 软电话占 5060;
我们的坑是 linphone(通配0.0.0.0:5060)和 FreeSWITCH(具体192.168.86.56:5060)
"共存绑定"但收包只命中一方,导致注册包被投回 linphone 自己。
最终方案:FreeSWITCH 5070 / baresip 5064 / linphone 5060,全部错开。
5.2 麦克风权限:不开就"哑巴"
Windows 隐私设置默认可能禁止桌面应用访问麦克风。
不打开时的现象:日志里出现
wasapi/src: IAudioClient_Initialize failed: 0x80070005
0x80070005 = E_ACCESSDENIED(拒绝访问)。baresip 的 wasapi 模块遇到这个
错误时线程直接退出、不重试——表现就是"能接通、对方听不到你"。
需要打开的三处开关(设置 → 隐私和安全性 → 麦克风):
- 麦克风访问(总开关)
- 允许应用访问你的麦克风
- 允许桌面应用访问你的麦克风 ← 最关键,baresip 属于"桌面应用"
实测:三个开关打开后,同样操作,0x80070005 消失,语音双向可通。
5.3 扬声器:默认设备 + "默认通信设备"两个都要设
baresip 的 wasapi 模块选音频设备时,用的不是"默认设备",而是
「默认通信设备」角色(源码 GetDefaultAudioEndpoint(..., eRender, eCommunications),
采集端同理用 eCapture + eCommunications)。
Windows 把音频设备分三个角色:默认、默认通信、默认多媒体——
插拔 HDMI / USB 耳机时三者可能被系统分别改掉。
手动设置方法(两个都设一遍):
Win + R → mmsys.cpl → 「播放」选项卡
→ 右键「扬声器 (Realtek(R) Audio)」→ 设为默认设备
→ 再右键一次 → 设为默认通信设备
(「录制」选项卡里对麦克风做同样操作)
真实翻车故事:有一次我抱着笔记本去同事工位(拔了 HDMI 外接显示器),
回来后重新插上显示器,Windows 悄悄把默认播放设备切成了显示器
(R27q-30 自带的音频输出)。于是我这边"接通了但一点声音都没有"——
声音全送到显示器去了,笔记本扬声器一声不吭。查了半小时才发现
是拔插 HDMI 惹的祸。教训:笔记本上排查"没声音",先看一眼当前默认播放设备
是不是显示器的名字。
6. 产物验证与自测
6.1 静态链接验证
:: 看依赖 DLL(应该只有 8 个系统库)
dumpbin /dependents C:\Code\baresip-win\deliver\baresip.exe
:: 看架构与子系统
dumpbin /headers C:\Code\baresip-win\deliver\baresip.exe | findstr /i "machine subsystem"
:: 校验哈希
certutil -hashfile C:\Code\baresip-win\deliver\baresip.exe SHA256
6.2 回声测试(推荐的自测法)
- 确认 FreeSWITCH 已启动(我们是手动跑
FreeSwitchConsole.exe) - 双击 baresip.exe,等到出现
All 1 useragent registered successfully! - 控制台输入(注意长命令必须以
/开头,不带/首字符会被当快捷键吞掉):
/dial 9196 0
9196 是 FreeSWITCH 自带回声测试号,0 表示用第 1 个账号发起。
对着麦克风说话应该能听到自己的回声。
- 通话中控制台会持续刷新状态行:
[0:00:56] audio=19702/19667 (bit/s)
audio=发送/接收,两边数值对称说明回环在工作;完全是 0 则媒体流没起来。
挂断按 b,退出按 q。
7. 速查表:症状 → 原因 → 处置
| 症状 | 原因 | 处置 |
|---|---|---|
Populated 0 accounts |
配置没放对地方 | 放 %APPDATA%\.baresip\accounts |
IAudioClient_Initialize failed: 0x80070005 |
麦克风隐私权限 | 开"允许桌面应用访问麦克风"三开关 |
| 接通但听不到任何声音 | 默认播放设备被抢(常见于插拔 HDMI) | mmsys.cpl 把扬声器设为默认和默认通信设备 |
| 没有来电铃声 | 铃声 wav 找不到 | 铃路径写的是安装时的绝对路径,deps 树整体搬家,或改 config 的 audio_path |
LNK2038 RuntimeLibrary 不匹配 |
opus 没开静态 CRT | 重编 opus 加 -DOPUS_STATIC_RUNTIME=ON |
配置报 find_package(OpenSSL) 失败 |
re/baresip 无条件找 OpenSSL | 两步都加 -DCMAKE_DISABLE_FIND_PACKAGE_OpenSSL=ON |
--target baresip 找不到目标 |
目标名不对 | 用 --target baresip_exe |
命令行 -f 等选项无效 |
MSVC 无 getopt,参数解析被编译掉了 | 死心,配置只认 %APPDATA%\.baresip |
| 注册没反应 / 405 | 同机 SIP 端口冲突 | `netstat -ano |
8. 结语
从源码到可发布的单文件 exe,核心其实就是三段 cmake + 三个坑
(OpenSSL 门控、opus 的 CRT 覆盖、baresip_exe 目标名)。
真正花时间的反而是 Windows 的"环境玄学":配置文件为什么改了没用(位置不对)、
为什么别人机器上没有账号(配置没给)、为什么没声音(麦克风权限 + 默认播放设备
被 HDMI 插拔切走)。
源码里的行为都有据可查:
wasapi 模块选设备用的是 eCommunications(默认通信设备)角色、
CHECK_HR 宏决定了采集初始化失败就直接退线程不重试——
理解了这两点,上表里一半的"玄学"就都变成"必然而已"。
浙公网安备 33010602011771号