
简介这是一个用C编写的桌面共享项目重点解决屏幕和声音的实时抓取与传输问题。它可以抓取屏幕图像和声卡音频经过H.264视频编码与AAC音频编码后实现RTSP本地转发并支持RTSP推流和RTMP推流到流媒体服务器。屏幕采集模块支持DXGI和GDI两种方式音频采集模块基于WASAPI编码器集成FFmpeg同时支持NVIDIA显卡的NVENC硬编码和Intel核显的QSV硬编码能够根据设备自动选择显著降低CPU占用。项目还使用SDL2和ImGui构建了简单UI便于操作与观察推流状态。整个工程按模块划分屏幕采集、音频采集、编码器、推流器各司其职可在Windows 10下使用VS2017或VS2019编译。资源包为ZIP格式大小约21MB内含完整源码工程覆盖采集、编码、推流及界面显示等完整链路二次开发也较方便。目前已有1689人学习适合对屏幕采集、音频采集、音视频编码、RTSP/RTMP推流以及硬件加速编码感兴趣的开发者参考。项目中的DXGI/WASAPI采集与FFmpeg编码集成方式对Windows桌面音视频开发很有参考价值。 DesktopSharing这个项目我最近拿它当主力工具折腾了一个多月。它的核心能力就三句RTSP转发、RTSP推流、RTMP推流。一句话解释就是把桌面这块屏幕包装成一个标准的流媒体源让VLC、ffplay、各种播放器甚至公网上的终端都能直接拉流观看。为什么对这件事这么执着因为桌面共享的实际需求远不止看看对方屏幕这么简单。远程协助要看副屏教学场景要录屏推流给几百人运维监控需要会议室大屏实时看一台设备上的操作实况还有人想把摄像头画面和桌面画面统一进一套流媒体体系。这些场景用远程桌面软件也能做但一旦把画面变成标准 RTSP/RTMP 流可玩性就完全不同任何支持这些协议的播放器都能直接看不用装特定客户端还能顺手把流接到直播服务器、录制服务、甚至 Unity 里的视频插件。这篇文章围绕这三条链路把我在实际搭建 DesktopSharing 时的架构设计、参数取舍、踩坑排查过程完整记录下来。适合正在做屏幕推流、或者想把自己已有的 RTSP/RTMP 流接入统一分发体系的同学参考。1. 为什么桌面共享要同时搞定 RTSP 和 RTMP 两套推流先说清楚一个问题桌面共享本身不难难的是共享出来的画面别人用什么看、通过什么网络看。不同场景对协议的要求完全不同这也是我一开始只做了一个 RTSP 推流之后又补上 RTMP 和转发的原因。1.1 三个典型场景和一个核心痛点我最早遇到的需求是远程协助型团队里有人在外地需要看本地同事桌面上发生的问题。这种场景画质要求不高但延迟必须低点一下鼠标对方几乎要同步看到。RTSP 的 RTP-over-UDP 模式天然适合这类低延迟局域网传输VLC 一行命令就能拉流Windows 上还能用 avpro 这类商业播放器直接播放。第二个场景是教学和会议录制屏幕内容要推到一台流媒体服务器上服务器负责转存、转码、再分发给网页端观众。这种场景 RTMP 是绕不开的因为主流的 nginx-rtmp、SRS、云直播服务都基于 RTMP 接入。网页端播放时再转成 HLS 或 HTTP-FLV画质和兼容性都能兼顾。第三个场景是设备画面接入现在工作室里好几台海康摄像机、小米摄像头都有自己的 RTSP 地址很多同事希望把桌面画面和这些摄像头画面放进同一个监控墙里统一查看。这时候桌面共享如果只支持主动推流就打不开了得支持被别人拉流也就是说桌面端必须具备 RTSP 转发能力把自己作为一个 RTSP 源暴露给监控终端。核心痛点在于这三个场景不是三选一而是经常同时出现。一台机器要一边让人通过 RTSP 直接看一边往 rtmp 服务器推直播流还要能作为转发节点把一路源转给多路观看端。这也是 DesktopSharing 最终做成一进多出架构的根本原因。1.2 RTSP 和 RTMP 的差异决定选型方向很多初学者会把 RTSP 和 RTMP 混着用其实它们从设计目标上就不一样。我做了个简单的对比表维度RTSPRTMP底层传输控制走 TCP媒体走 RTP默认整体基于 TCP交互模型客户端可以拉流也可以推流给服务器基本采用推流到服务器模型浏览器兼容原生不支持需播放器插件或转封装不支持直连通常转 HLS/FLV 给网页实时性局域网可做到几百毫秒以内常态 1-3 秒优化后可更低常用领域安防监控、桌面共享、音视频设备直播平台接入、服务器转码分发桌面共享这个场景有个特殊性画面内容变化不像自然场景那么平稳鼠标拖动、滚动窗口都会带来大面积的硬边和细节对编码器压力非常大。RTSP 适合近距离低延迟观察RTMP 适合远距离广播分发两者形成互补单押一个协议都会在实际使用中遇到墙。1.3 RTSP 转发和推流的本质区别这是我在这个项目里最想强调的一点。RTSP 推流是你主动把桌面画面送给一台流媒体服务器别人通过那台服务器获取流。RTSP 转发则完全不同你的机器本身起了一个 RTSP 服务别人直接来你这台机器拉流。转发模式下你的角色既是客户端又是服务端上游拉一路流下游分发多路流。用生活化的类比推流像是你把一份文件上传到网盘别人去网盘下载转发像是你自己架了一个文件的 FTP 服务别人直接从你电脑上下载。前者适合公网分发后者适合内网或者小规模直接共享而且转发不需要经过中间服务器延迟往往更低。DesktopSharing 同时实现了这两条路径就是因为实际使用中真的会同时用到摄像头画面进系统后既可以通过转发暴露给局域网内的王翔终端也可以通过推流组件把画面送到远程的 RTSP 服务器做异地备份。2. DesktopSharing 整体架构一进多出的数据流设计搞清楚为什么之后技术实现就顺理成章了。DesktopSharing 的整体架构用一句话概括就是屏幕采集一次编码一次多路分发。这个设计避免了每个观看端各采一次屏、各编一次码的性能灾难。2.1 模块划分采集、编码、分发三段式核心模块我分成三段采集模块Windows 上优先用 DXGI 桌面复制接口比传统的 GDI BitBlt 效率高很多特别是在高分屏和高刷新率下。Linux 上可以用 X11 抓屏加 PipeWiremacOS 则走 ScreenCaptureKit。编码模块把采集到的 RGB 帧转成 YUV再用 H.264 编码器压缩。这里有一个关键取舍用硬件编码Intel Quick Sync / NVIDIA NVENC / AMD AMF还是软件编码libx264。桌面共享对 CPU 占用敏感我实测在同一台电脑上用 libx264 推 1080p60CPU 能吃到 40% 以上换 NVENC 后 CPU 占用掉到 5% 以内。当然软件编码在低码率下的画质略优但桌面共享场景对实时性要求高硬件编码的轻微画质损失可以接受。分发模块同一份编码后的 H.264 裸流按需分别喂给 RTSP 转发服务端、RTSP 推流客户端、RTMP 推流客户端。2.2 编码参数决定延迟和画质的上限分发模块再高效编码参数不对也白搭。桌面共享我最常用的参数组合是这样分辨率跟随主屏或者指定副屏1080p 是大多数场景的甜点位。帧率普通办公场景 15fps 够用演示动画或游戏录制需要 30fps 以上。码率1080p30 桌面内容建议 4-6 Mbps。桌面画面文字边缘多码率给太低会出现明显的马赛克特别是滚动代码窗口的时候。GOP关键帧间隔这个参数直接决定客户端首次出画面的速度。我一般固定为帧率的绝对值比如 30fps 下设置 GOP 为 30也就是 1 秒一个关键帧。如果追求更低延迟可以设为 1-2也就是每秒两个关键帧代价是码率增加 20% 左右。profile 和 level注意别设置过高的 profile否则老播放器可能解不了码。1080p 一般用 High profile 加 level 4.0 就够用了。2.3 只编码一次多路输出分发层的核心是同一份编码好的样本被多个输出目标共享。这意味着即便同时有 5 个客户端通过 RTSP 拉流上游也只需要采集一次、编码一次。具体实现上我维护了一个带引用计数的环形缓冲池编码器每吐出一帧就把这一帧的引用分发给所有注册的输出通道每个通道内部各自维护发送队列。有一个细节值得提醒RTMP 推流和 RTSP 推流的音频处理能力不一样。RTMP 通常要求 AAC 音频而 RTSP 服务器可能接受 AAC、Opus 或 PCM。DesktopSharing 里我统一把系统音频麦克风加扬声器混合采集后编码成 AAC验证下来两条链路都能兼容音画同步也靠时间戳对齐搞定。3. RTSP 转发链路瓶颈从来不在协议而在队列RTSP 转发是三个功能里最容易被低估的。表面上看转发无非是拉流再发流实际操作中真正的难点在于并发观看数一多转发端的缓冲队列就会变成延迟和花屏的放大器。3.1 转发的适用场景和核心思路我在 DesktopSharing 里做转发的初衷是解决多端同时直连摄像头的问题。比如一个画面源被 4 台显示器同时要看如果每台显示器都直接拉源源设备播放器并发上限很容易被打满尤其是硬解 IP Camera并发超过 3 路后画面会频繁闪断。转发模式的好处是源端只维护一路连接其余观看端全部连接转发服务由转发服务把这一路流复制分发。对桌面共享来说桌面其实就是一个虚拟源Windows 桌面本身没有 RTSP 能力所以转发端同时充当了源的提供者采集编码后直接在本地起 RTSP Server观看端连上来即视为转发。3.2 TCP 还是 UDP没有绝对正确只有场景匹配RTSP 协议里媒体传输可以走 RTP over UDP也可以走 RTP over TCPinterleaved 模式。我在项目里遇到的坑是很多播放器客户端默认走 UDP但跨路由器、跨网段观看时 UDP 经常被 NAT 丢包画面表现为花屏和卡顿偶尔还会完全断流。经过一段时间调试我做成策略可选 默认自适应同一局域网默认 UDP公网或跨网段默认 TCP。同时用 ffplay 验证时显式加-rtsp_transport tcp参数来强制 TCP。TCP 模式唯一的缺点是慢速网络上可能出现队头阻塞但因为桌面共享的画面本身是实时流我用了只保留最新帧策略老帧直接丢弃实际体验比 UDP 乱序丢包好得多。3.3 缓存队列和丢包策略宁丢帧不堆积这一节是整个转发链路里最值得分享的经验。刚开始我的各个下游通道各自维护一个 FIFO 队列结果拉流端网络稍有波动队列就积压出几百毫秒甚至几秒的延迟。几个人同时观看时每个人的队列深度不一样画面就会不同步互相之间还会抢转发端带宽。后来我改成了滑窗覆盖策略每个下游通道只缓存最近 3 个关键帧区间如果发送速度跟不上就把最早的关键帧及之前的普通帧全部丢弃。这样做的代价是弱网观看端会偶尔跳到新的关键帧视觉上像快进了一下但整体延迟始终被压在 500ms 以内。对比下来这个取舍对桌面共享比保帧率重要得多——远程协助的时候延迟 2 秒意味着对方已经搬走了 3 行代码你还没看到。3.4 多路转发时的带宽和并发考量还有一个容易被忽视的点转发端同时给多个客户端发送相同的画面如果每个客户端各发一份CPU 和网卡开销都是线性增长。我在 DesktopSharing 里加了一个组播优化逻辑同一个局域网里如果多个客户端请求同一路流尽量用发送队列合并的方式把数据包一次性写入 socket减少用户态到内核态的切换。实测下来同一台机器上同时转发给 6 路观看端CPU 占用只比单路多了不到 8%带宽消耗则是正常的按路数累加。想控制整机带宽就得从编码源头入手降低码率这是我后面单独测试出来的教训。4. RTSP 推流和 RTMP 推流的具体落地操作转发聊完了回到实际操作部分的重点怎么把桌面画面稳定地推到远端服务器。这里我分 RTSP 推流和 RTMP 推流两条线来记录包括服务器选型、验证命令和参数细节。4.1 RTSP 推流目标服务器和 SDP 参数RTSP 推流相比拉流很多开发者会陌生一些。实际上RTSP 协议本身支持客户端主动发送 ANNOUNCE 和 SETUP 请求来建立发布会话只要目标 RTSP 服务器允许发布就能把桌面流推上去。我用的转发组件是 Mediamtx原 rtsp-simple-server配置非常简单rtspAddress: :8554 rtmpAddress: :1935 hlsAddress: :8888它在同一个进程里同时提供 RTSP 推流接入和 RTMP 推流接入。启动以后用 ffmpeg 推桌面流非常简单Windows 桌面采集推 RTSPffmpeg -f gdigrab -framerate 30 -i desktop -c:v libx264 -preset ultrafast -tune zerolatency -b:v 4M -f rtsp -rtsp_transport tcp rtsp://your-server:8554/desktop推流后VLC 直接打开rtsp://your-server:8554/desktop就能看到桌面。我实际测试中遇到过一个问题某些 RTSP 服务器要求 SDP 里带 H.264 的 sprop-parameter-sets编码器参数集合才能正常解码否则播放器只会黑屏。解决办法是用-sdp_file选项提前把 SDP 内容打印出来确认或者干脆用 Mediamtx它对 H.264 参数集的兼容性处理得比较稳。4.2 RTMP 推流nginx-rtmp 与 SRS 的搭建与验证RTMP 推流是桌面共享接入直播体系的必经之路。常见服务器有两种老牌的 nginx-rtmp和国产的 SRS。nginx-rtmp 配置简单小规模使用足够SRS 优化更充分支持大规模分发和 HLS 转封装。nginx-rtmp 最小配置rtmp { server { listen 1935; chunk_size 4096; application live { live on; record off; } } }保存配置后nginx -t nginx -s reload即可生效。ffmpeg 推流命令ffmpeg -f gdigrab -framerate 30 -i desktop -c:v libx264 -preset ultrafast -tune zerolatency -b:v 4M -c:a aac -b:a 128k -f flv rtmp://your-server:1935/live/desk注意两件事一是推 RTMP 时必须加-f flv指定封装格式二是如果在同一台机器上开了 Mediamtx 的 RTMP 接入端口不要和 nginx-rtmp 冲突。我把 Mediamtx 的 RTMP 端口改成 2935nginx 保持 1935两条链路互不干扰。RTMP 推流验证建议用 VLC 或 ffplay 直接打开rtmp://your-server:1935/live/desk。如果 VLC 拉流黑屏先检查ffmpeg推流端是否报错再用 ffprobe 看目标地址会不会响应。这类问题九成出在编码参数而不是服务器配置。4.3 从命令行验证到集成进编译器里的注意事项命令行验证通过后就可以把同样的手段集成到代码里。有一个我踩过的坑是桌面采集的帧并不会按固定间隔到达尤其在用户没操作时帧率会自然下降。但推流到 RTMP 时RTMP 协议要求一定的音视频时间戳节奏如果长期没有新视频帧播放端会卡在最后一帧音频却一直在走。解决方式是启用解码器侧重复帧策略当系统没有采集到新帧时把最后一帧重新打上已更新的时间戳送进编码器。ffmpeg 里可以用-vsync 1代码集成时则要在采集循环里维护一个最新帧引用超时后重新提交。这样既不会烧编码器资源又能维持推流时间轴的连续性。4.4 音画同步问题桌面共享独有的麻烦桌面共享的音频来源很杂可能是系统扬声器输出可能是麦克风还可能是某个窗口的声音。我在 DesktopSharing 里用的是 Windows 的 WASAPI 混音采集这样观众听到的就是桌面实际播放的声音而不是只有麦克风。RTMP 推流时音画同步主要靠编码器输出的 PTS/DTS 对齐。常见坑是音频先推了一堆包视频后到播放器就会长时间只出声不出画。我的经验是推流前先给编码器预热等视频编码器产出第一个关键帧后再开始发送音频数据两种协议链路下都实测有效。5. 实测中的三个典型故障与完整排查过程这部分单独拿出来写因为排查过程的价值有时候比功能实现还高。以下三个故障是我实际跑 DesktopSharing 时遇到的每个都花了不少时间定位。5.1 故障一VLC/ffplay 拉 RTSP 长时间不出画面现象转发服务正常启动端口通VLC 能连上但画面黑屏拖时间轴没反应。排查链路先用 ffplay 打开同一地址报了一段 SDP 解析错误指向 H.264 的 sprop 参数集缺失。再用 ffprobe 检查推流端的裸 H.264 流发现确实没有带sps和pps。最后定位到编码器初始化的顺序问题我先启动了 RTSP Server然后才创建编码器导致 SDP 写死时空了。修复方案是编码器初始化完成后再启动 RTSP Server并把参数集写入 SDP 响应。这个坑提醒我RTSP 的 SDP 是播放端是否秒开的关键一定要在编码器稳了之后再去开服务。5.2 故障二高帧率桌面推流后画面频繁花屏现象推 1080p60 到 RTMP局域网内播放画面每几秒就绿屏或者马赛克一下。排查链路第一反应是网络问题但局域网千兆码率才 4Mbps基本排除带宽瓶颈。接着看 CPU 占用发现 libx264 在 ultrafast 下依然飙高丢帧严重。再用 NVENC 硬件编码替换 libx264花屏频率明显降低但仍有偶发。最终定位到是 GPU 编码队列和桌面采集队列不同步采集到新的帧时编码器还在忙导致部分帧被直接丢弃播放端找不到可解码的参考帧。修复方式是给采集和编码之间加一个最新帧缓冲从不排队只保留最新帧同时把 GOP 从 30 改到 15屏幕恢复稳定。桌面共享里丢帧和花屏的本质都是参考帧不连续增加关键帧频率是最快的兜底办法。5.3 故障三RTMP 推流后延迟越来越大现象刚推流启动播放端延迟 1 秒左右运行十分钟后延迟涨到 5 秒以上而且还在持续增加。排查链路先怀疑播放器缓冲但同一个播放器播 RTSP 就没有这个问题。再用ffprobe看 RTMP 流的时间戳发现推流端发送的数据包里时间戳增长速度和真实时间不一致音频时间戳正常视频时间戳偏慢。最终确认是非实时推流问题桌面采集帧率在用户不动鼠标时降到 10fps 以下但 ffmpeg 用-re限速模式推流导致发送节奏低于实时需求播放端为了等待视频帧不断累积音频缓冲延迟越来越大。修复方式是去掉-re参数改为按时间戳立即发送模式让服务器以真实时间缓存流播放端缓冲归位延迟始终保持在 1-2 秒以内。这个问题在直播场景最容易翻车核心思路只有一个推流节奏必须由实时时钟驱动而不是由采集帧率驱动。5.4 附一张排查速查表现象优先排查项常用验证命令拉流黑屏SDP 参数集、编码器启动顺序ffprobe rtsp://addr画面花屏GOP 间隔、参考帧丢失、编码器过载ffmpeg -rtsp_flags...加日志RTMP 延迟增长推流节奏、视频时间戳、-reffprobe -show_packetsOpenCV 读不了 RTMPOpenCV 编译缺 FFmpeg/RTMP 支持cv2.getBuildInformation()Unity 播放黑屏AVPro 插件版本和 RTSP 参数集用 ffplay 对比验证OpenCV 打开 rtmp 失败这个问题我单独说一下很多人问为什么cv2.VideoCapture(rtmp://...)返回 False。绝大多数情况是当前 OpenCV 是精简版编解码器里没有带 FFmpeg 的 RTMP 协议支持。先跑cv2.getBuildInformation()看输出里有没有 FFmpeg有 FFmpeg 再确认是不是带了 librtmp。实在不行就用 ffmpeg 先把 RTMP 流转成本地 UDP再交给 OpenCV 读取虽然多一跳但稳定。6. 一些实际操作中的个人心得项目落地之后我对桌面共享流媒体这条路线的看法比开始时实际了很多。很多文章会把 RTSP、RTMP、转发写得像选择题实际做下来它们更像是组合拳内网低延迟就用 RTSP公网分发配 RTMP 加 HLS摄像头接入靠转发。把这三条链路做进同一个 DesktopSharing 进程里才能在各种需求砸过来时不手忙脚乱。编码参数这块我给后来者一个最直接的参考值桌面远程协助场景用硬件编码GOP 设帧率等值码率 3-5 Mbps关闭 B 帧preset 用 fast 或 balanced延迟能做到 300-500ms画质足够看清代码。如果想要更低延迟把 GOP 缩短到 10码率提高 30%换来的是播放端更快的首帧和更少的参考帧断裂。最后一个私房技巧部署桌面共享离线环境时不要只测试推流命令能跑通一定要测试断网重连和服务器重启两个场景。很多 RTSP 转发服务在源端异常断开后不会自动清理会话会导致新的客户端连不上。我最后的做法是给转发模块加了一个 60 秒空闲会话回收机制实测下来很长一段时间都没有再收到连不上的反馈。做流媒体相关项目永远要把异常恢复当作功能的一部分来设计而不是事后打补丁。本文还有配套的精品资源点击获取