【干货】把 OBS 拆到骨头:手把手带你用 libobs 撸一个 40MB 的轻量录屏工具
收藏吃灰警告 ⚠️:本文不是「怎么用 OBS」,而是「怎么把 OBS 当库用」的保姆级拆解,含 5 个让你少走弯路的踩坑经验。建议先点赞收藏,慢慢看~
📌 目录
- 前言
- 一、为什么 OBS 那么重?
- 二、核心思路:OBS 其实是一个「库」
- 三、架构总览:一条最小的录制管线
- 四、踩坑①:libobs 的初始化顺序
- 五、踩坑②:编码器回退链与参数不统一
- 六、踩坑③:宿主内注册鼠标点击特效源
- 七、踩坑④:异步停止与 MKV 崩溃恢复
- 八、踩坑⑤:轻量靠打包,不靠少编译
- 九、那些「双击没反应」级别的工程细节
- 总结
- 参考资料
前言
大家好,我是博主的自研宿主小项目分享。😄
很多小伙伴私信问我:OBS 功能那么强,但我只想录个屏,每次打开它都要等半天,还带一堆用不到的东西,有没有更轻的办法?
答案是有的——而且办法比你想的更硬核:直接放弃 OBS 的前端,把它最核心的libobs当库来用,自己写一个宿主程序。最终成果是一个40MB 出头的绿色免安装目录,没有浏览器、没有推流、没有插件市场,只有一个目标:把屏幕录下来。
这篇文章就带你完整走一遍这个思路,以及过程中踩过的 5 个大坑。全程干货,建议配好水。💧
一、为什么 OBS 那么重?
先说说问题出在哪。
OBS Studio 是桌面录屏/直播的事实标准,强大、免费、插件生态丰富。但它有个公开的秘密:重。
- 完整安装包动辄200MB+
- 启动会拉起CEF 浏览器内核(为了浏览器源)
- 自带WebSocket 服务、虚拟摄像头、推流、插件市场……
- 一堆你可能一辈子用不到的模块
而真相是:大多数人打开 OBS,只想录个屏。
所以问题就变成了:我能不能只留「录屏」这一条能力,把其余的统统砍掉?
💡划重点:这里的「轻量」不是优化代码,而是做减法——搞清楚你真正需要什么,然后把其余全部拿掉。
二、核心思路:OBS 其实是一个「库」
这是本文最重要的一句话,请务必记住:
OBS 的精髓
libobs是一个独立的动态库,obs64.exe加 Qt 前端只是它上面的一层皮。
libobs对外暴露的是一套纯 C API:
| API | 作用 |
|---|---|
obs_startup/obs_shutdown | 启动与销毁 |
obs_reset_video/obs_reset_audio | 视频 / 音频子系统 |
obs_source_create | 创建采集源(显示器、窗口、WASAPI、摄像头…) |
obs_encoder_create/obs_output_create | 创建编码器与输出 |
obs_load_all_modules2 | 加载插件模块(win-capture、obs-ffmpeg、obs-nvenc…) |
也就是说,你完全可以绕开obs-frontend-api、不构建obs64.exe,自己写一个宿主程序,把这套 C API 串起来。
这就是「轻量」的全部秘密:把 OBS 庞大的前端及其配套(浏览器源、推流、插件管理、虚拟摄像头)一次性剔除,只保留一条录制管线。✅
三、架构总览:一条最小的录制管线
先来看一张整体架构图(收藏这张图就够了):
┌──────────────────── minirec.exe(自研宿主,单进程 UI)────────────────────┐ │ UI 层 (Qt6 Widgets):MainWindow / SettingsDialog / RecordingOverlay │ │ │ 信号槽 │ │ 编排层:RecorderController(状态机 + 门面,UI 只和它打交道) │ │ LibobsRuntime · SceneBuilder · EncoderFactory · OutputController · Remux │ │ │ 直接调用 libobs C API │ │ libobs 核心:obs.dll / libobs-d3d11.dll(场景→源→混音→编码→输出) │ │ │ 白名单加载的插件模块 │ │ win-capture · win-wasapi · win-dshow · obs-ffmpeg · obs-outputs │ │ obs-x264 · obs-nvenc · obs-qsv11 │ └──────────────────────────────────┬─────────────────────────────────────────┘ │ 编码后裸流经 IPC 管道 obs-ffmpeg-mux.exe(独立子进程,封装写 MKV) │ 停止后:宿主内 media-remux 流拷贝 → MP4(movflags=+faststart)核心就一件事:自研 Qt6 宿主,直接调用 libobs 的 C API,组装「采集 → 编码 → 落盘」的最小管线。
下面开始上硬菜,5 个坑,一个比一个隐蔽。🔥
四、踩坑①:libobs 的初始化顺序
libobs 不是「随便调一下就能用」的,初始化顺序错了,轻则功能异常,重则直接崩溃。
在LibobsRuntime里,顺序严格对齐 OBS 前端:
// 1. 工作目录必须是 exe 所在目录——libobs 在 Windows 上靠**相对路径**// 查找核心数据(../../data/libobs)与默认插件目录。SetCurrentDirectoryW(exeDir);// 2. 自检:obs.dll / libobs-d3d11.dll / default.effect / 插件目录 / mux.exe 都在,// 缺了直接给明确中文报错,而不是"双击没反应"。// 3. 启动obs_startup("zh-CN",configDir,nullptr);// 4. 先 reset 音频/视频(图形模块在此加载),再 load 插件模块obs_reset_audio(&aoi);// 48000Hz / 立体声obs_reset_video(&ovi);// 此时加载 libobs-d3d11obs_load_all_modules2(&failure);obs_post_load_modules();⚠️这里有个非显而易见的顺序陷阱:必须先
obs_reset_video再obs_load_all_modules2。原因是
win-capture(窗口/显示器捕获插件)在自己的obs_module_load阶段要探测 D3D11 与 WGC(Windows Graphics Capture)能力——如果你还没加载图形模块就去 load 插件,它探测不到,采集能力会静默降级。
视频参数里还埋着一个硬编码零拷贝的前提:
ovi.output_format=VIDEO_FORMAT_NV12;// NV12 + GPU 转换是硬编零拷贝的前提ovi.gpu_conversion=true;ovi.colorspace=VIDEO_CS_709;ovi.range=VIDEO_RANGE_PARTIAL;ovi.scale_type=OBS_SCALE_BICUBIC;ovi.graphics_module="libobs-d3d11";💡小贴士:这里如果不用 NV12,走 NVENC 硬编码时,帧数据就得多一次 GPU↔CPU 的搬运,白白烧掉性能和延迟。
五、踩坑②:编码器回退链与参数不统一
OBS 支持一堆硬编方案,但不是每台机器都有——老显卡、缺驱动、精简系统,随时可能翻车。
所以编码器不能写死,得做成一条优先级链,创建成功就用,失败立刻下一个:
return{"obs_nvenc_h264_tex",// NVIDIA NVENC(GPU 纹理直通)"h264_texture_amf",// AMD AMF"obs_qsv11_v2",// Intel Quick Sync"obs_x264",// 软件兜底};⚠️划重点:判断「真可用」不能只看插件注册了没有,得实际
obs_video_encoder_create()试探一次——显卡/驱动不支持时,注册了也会创建失败。
更烦人的坑是各家硬编对设置键的命名和取值不统一,尤其是码控rate_control:
// NVENC:preset 用 p4/p5,multipass 用 qresobs_data_set_string(settings,"rate_control","CQP");obs_data_set_string(settings,"preset",highQuality?"p5":"p4");obs_data_set_string(settings,"multipass",highQuality?"qres":"disabled");// QSV:target_usage 用 TU4/TU7(大小写敏感,写错直接不生效甚至创建失败)obs_data_set_string(settings,"target_usage",highQuality?"TU4":"TU7");// x264:preset 换一套名字obs_data_set_string(settings,"preset",highQuality?"faster":"veryfast");💡小贴士:这堆「家族专属」的参数填充,是硬编码最容易翻车的暗礁。MiniRec 的做法是统一走CQP(恒定质量)码控,再按家族分别填键——比「一刀切填一套参数」可靠得多。
六、踩坑③:宿主内注册鼠标点击特效源
点击涟漪是很多录屏工具的标志性功能。最「轻」的做法是不额外写插件 DLL,直接在宿主里注册一个obs_source_info,然后用内嵌 HLSL 着色器画圆环:
// 内嵌字符串着色器:按到圆心的距离画一圈环,半径/透明度随时间衰减。// 内嵌字符串可避免额外的 .effect data 文件依赖(gs_effect_create 直接编译)。constchar*kEffectSource=R"( uniform float4x4 ViewProj; uniform float2 quad_size; uniform float2 center; uniform float radius; uniform float thickness; uniform float4 color; ... float4 PSRing(VertInOut vert_in) : TARGET { float2 pixel = vert_in.uv * quad_size; float dist = distance(pixel, center); float band = 1.0 - saturate(abs(dist - radius) / max(thickness, 0.5)); float core = 1.0 - smoothstep(0.0, max(radius * 0.5, 1.0), dist); float alpha = saturate(band + core * 0.35); return float4(color.rgb, color.a * alpha); } )";这里有两个隐藏坑,都是「日志报错看起来很玄学」的那种:
gs_effect_create必须在图形上下文里调用。源是在主线程通过obs_source_create创建的,此刻 libobs 并不持有图形上下文——直接编译着色器会失败,日志里是「called while not in a graphics context」,而且error还是空的。解法是手动obs_enter_graphics()/obs_leave_graphics()包一层。点击事件不能碰图形接口。鼠标点击用
SetWindowsHookEx(WH_MOUSE_LL)全局低级钩子采集,回调里只入队、绝不碰图形。队列是个无锁环形队列(volatile LONG头尾指针 +InterlockedExchange,容量 128,满了丢最旧事件)——宁可丢几个特效帧,也不阻塞用户输入。
在video_render里,把屏幕物理坐标映射到源坐标,过滤掉采集范围外的点击(比如点了副屏),然后「半径由小变大、透明度由强变弱」逐涟漪绘制:
constfloatscaleX=fx->captureW>0?fx->width/fx->captureW:1.0f;ripple.x=(events[i].x-fx->captureX)*scaleX;ripple.y=(events[i].y-fx->captureY)*scaleY;这个源作为独立 scene item 叠在采集画面之上(后加入 → 绘制在上层),铺满整块画布对齐像素。✅
七、踩坑④:异步停止与 MKV 崩溃恢复
录屏工具最怕两件事:停止时卡死 UI,以及崩溃后文件打不开。
停止是异步的。obs_output_stop()只是发出停止请求,真正收尾发生在 libobs 输出线程的回调里。如果傻等,UI 线程会直接冻住。MiniRec 的做法是等stop信号回来,再用QMetaObject::invokeMethod(..., QueuedConnection)投递回主线程收尾。
录制中一律写 MKV。这是 OBS 的默认策略:MKV 边写边封装,崩溃/断电后文件仍可播放;而直接写 MP4 一旦中断,moov头没落盘,整个文件就废了。停止后再用 libobs 自带的media_remux_job_*做流拷贝转 MP4(movflags=+faststart,方便网页秒开),成功后可删 MKV。
配套的还有一层崩溃恢复保险:录制开始时写一个recording.lock标记(记录 MKV 路径),正常结束删除;下次启动若发现残留标记,提示用户上次异常结束、目录里可能有待验证的 MKV。
整条流程被收敛成一个状态机,UI 只订阅stateChanged:
Idle → Countdown → Starting → Recording ⇄ Paused → Stopping → Remuxing → Idle八、踩坑⑤:轻量靠打包,不靠少编译
一个反直觉的结论:OBS 的 install 规则会覆盖所有已配置 target,所以「少编译」几乎做不到——build-obs.ps1实际还是整包编译的。真正的「轻量」发生在打包阶段。
package.ps1做两件关键的事:
① 白名单裁剪
只保留这 8 个插件,其余(浏览器、推流、VLC、AJA、DeckLink、虚拟摄像头、VST…)全砍:
win-capture · win-wasapi · win-dshow · obs-ffmpeg · obs-outputs · obs-x264 · obs-nvenc · obs-qsv11② 依赖 DLL 补齐
OBS 的_bundle_dependencies只挂在自带前端目标上,而本工程不构建前端,所以得自己用dumpbin /dependents扫导入表(含传递依赖),从 obs-deps / Qt / VC 运行时分发里循环补齐,直到不再新增。
⚠️这里极易踩的坑:千万别删
obs-nvenc-test.exe/obs-qsv-test.exe/obs-amf-test.exe。NVENC/QSV 在
obs_module_load阶段会CreateProcess拉起这些小工具探测驱动——删了它们,硬件编码会静默退回 x264,你还以为硬编生效了。
另外,保持 OBS 原生目录结构(bin/64bit+data/+obs-plugins/64bit)是硬约束,因为 libobs 靠相对路径找数据,动了目录就找不到着色器。
九、那些「双击没反应」级别的工程细节
做 GUI 程序最怕的就是用户双击后没反应、还没控制台可看。MiniRec 为此做了几层防御:
- ✅自检报错:缺关键文件直接弹明确中文报错(“缺少 libobs 核心库:…”),而不是默默退出。
- ✅双写日志:正式日志写
%APPDATA%\MiniRec\logs\(带 UTF-8 BOM,记事本能正确显示中文,保留 7 天);兜底日志写 exe 同级的minirec-startup.log——即使%APPDATA%不可写,也能查清「双击没反应」的原因。 - ✅DPI 声明
PerMonitorV2:多显示器独立缩放,否则区域录制的坐标会错位。 - ✅画布偶数化:所有宽高向下取偶,否则 H.264 编码奇数宽高直接报错。
- ✅配置防御性校验:fps / 分辨率 / 倒计时超范围时回落,用 libobs 的
obs_data_save_json_safe做 tmp/bak 原子写。
总结
来,一张表帮你把核心要点收走:
| 主题 | 要点 |
|---|---|
| 核心思路 | OBS 是「库」不是「App」,绕开前端直调 libobs C API |
| 初始化 | 先 reset_video 再 load_modules,否则 win-capture 探测不到 D3D11/WGC |
| 编码 | 优先级链 + 实际创建试探,各家族 rate_control 键名不统一要分别填 |
| 鼠标特效 | 宿主内注册 source + 内嵌 HLSL,注意图形上下文与无锁队列 |
| 停止 | 异步收尾 + MKV 录制 + 流拷贝转 MP4(faststart) |
| 打包 | 白名单裁剪 + dumpbin 扫依赖,别删 *-test.exe |
最终产物是一个约40MB 的绿色免安装目录:录全屏 / 指定窗口 / 区域,系统声音 + 麦克风独立开关,NVENC / QSV / AMF / x264 自动回退,画质档一键预设,托盘 + 全局热键(F9 开始/停止、F10 暂停),倒计时,录制中悬浮控制条,摄像头画中画,鼠标点击涟漪,MKV 录制 + 自动转 MP4(faststart)。
代价是明确的:没有直播推流、没有浏览器源、没有虚拟摄像头、没有插件市场、没有剪辑、没有云上传、也没有游戏捕获。这些都被有意砍掉了——不是做不了,而是「录屏工具」这个定位不需要它们。
参考资料
- 源码与实现细节:
- 初始化:
minirec/src/core/LibobsRuntime.cpp - 编码器回退:
minirec/src/core/EncoderFactory.cpp - 鼠标特效:
minirec/src/core/MouseFxSource.cpp+MouseEventHook.cpp - 状态机:
minirec/src/core/RecorderController.h - 打包:
minirec/packaging/package.ps1
- 初始化:
- 项目说明:
README.md
📢声明:本文为博主原创,基于个人项目实践经验整理,转载请注明出处。
如果这篇文章对你有帮助,点赞 + 收藏 + 关注走一波~ 有问题欢迎在评论区留言,看到必回。💬
🚀 下一篇预告:如果你想「文件少到只剩一个 exe」,我会讲讲单文件打包的三条路线(7-Zip SFX / Enigma Virtual Box / 自研资源内嵌)的利弊对比,敬请期待~
执行文件 https://download.csdn.net/download/lg15273112290/93764456