尧图精选

多摄像头RTSP统一转发:Live555流媒体服务器实战解析

🕒 发布时间:2026/9/7 13:33:47 📁 来源:尧图网络
简介基于Live555的多摄像头RTSP转发服务器工程面向需要搭建多路视频流分发服务的开发者适合具备C与网络编程基础、希望快速落地RTSP服务的人群。工程共192个文件以hh头文件为主175个搭配cpp实现、静态库a、配置文件及cJSON解析模块压缩包仅686KB轻量且结构清晰。源码涵盖DynamicRTSPServer、H264LiveVideoServerMediaSubsession、H264FramedLiveSource等关键模块演示如何接入多个IP摄像头、处理DESCRIBE/SETUP/PLAY等RTSP请求并将H264视频流转发至多个客户端。服务器通过线程池或异步I/O实现多路复用保障不同摄像头的实时数据流互不阻塞摄像头参数既可在代码中配置也可读取配置文件灵活调整。已有1732人学习下载。通过这份资料读者可掌握Live555的会话管理、RTP/RTCP传输与并发模型的实际用法并能基于提供的cpp与h文件改造适配监控、视频会议、远程教育等场景。 上个月帮朋友调一套多摄像头采集系统遇到一个很典型的场景前端五路IPC有海康、大华还有一路树莓派接的OV5647摄像头。每台设备都有自己的RTSP地址但客户端想点播哪一路就点播哪一路不能一家一家去配置端口映射更不能每路视频都用厂商SDK单独拉一份。折腾一圈之后我最后选了Live555做转发层把多路摄像头数据统一拉进来再对外暴露一个干净的RTSP服务客户端只要访问固定端口就行。这个方案很适合监控大屏、工厂缺陷检测、实验室多视角录像这类需求。它会处理掉最麻烦的“取流”问题你不用关心摄像头是海康还是大华也不用管它用H.264还是H.265只要它有标准RTSP地址Live555就能以客户端的身份去拉流拉回来之后再以服务端身份统一打包成RTSP流分发给任意数量的播放器。这篇文章就把我从拉流到转发、从单路到多路的完整实现过程拆开讲一遍顺便把踩过的坑都列出来。1. 为什么要把多路摄像头统一转成一路RTSP服务1.1 这个服务器到底解决了什么问题单路摄像头直接给播放器看通常不需要额外开发VLC、PotPlayer、ffplay都能直接打开。但一旦摄像头数量多起来问题马上就暴露了。最明显的是客户端访问方式不统一海康默认端口554大华经常是37777树莓派接OV5647跑出来的RTSP服务可能又用了别的端口。客户端每次换一路流就要改一次端口和鉴权信息体验很差。另一个问题是并发数量。家用摄像头能同时承受的RTSP会话很有限如果三个屏幕同时看同一路摄像头主控芯片的负载已经吃不消了。把拉流集中到一个独立服务器上摄像头只跟这个服务器建立一路连接再由服务器把同一份数据复制给几十个客户端这样能大幅降低摄像头侧的压力。尤其在做多路视频汇聚的时候转播服务器不是锦上添花而是刚需。1.2 方案选型Live555、GStreamer、FFmpeg怎么选多摄像头转发的技术方案不止Live555一种。我用过FFmpeg做转推也试过GStreamer搭Pipeline最后回到Live555是因为它在“纯RTSP服务端”这个角色上最省心。方案适合场景优点坑点Live555专用RTSP服务端开发库轻、文档全、RTP打包逻辑成熟API比较老需要自己补码FFmpeg 自写服务需要转码或封装格式转换编解码能力强RTSP服务端要自己实现或依赖第三方GStreamer复杂媒体管线、嵌入式开发Pipeline灵活、插件丰富调试链路长信息噪音多Node.js / Go 轮子快速原型、小规模开发效率高高并发RTP分发性能难保证我自己选Live555还有一个实际原因它自带一套完整RTSP/RTP/RTCP实现不用自己拼RTP头、算序号、维护RTCP状态。对于摄像头这类标准H.264码流Live555的H264VideoRTPSink能直接打包省掉很多媒体细节。代价是它的框架是C写的事件循环有自己的调度规则必须先理解它的执行模型不然改起来会很别扭。2. 动手前需要搞懂的三个关键知识点2.1 RTSP/RTP/RTCP的分工RTSP本身不传视频数据它是一个控制协议负责协商地址、端口、编码格式类似“先打电话约好快递上门”。真正传视频数据的是RTP而RTP传输过程中的丢包统计、时钟同步由RTCP负责。很多新手在这里犯迷糊以为改了RTSP端口就能搞定一切实际上转播服务器要考虑的是RTP的收发方向。摄像头往外发RTPLive555客户端往回收RTP同时Live555服务端又要把RTP数据重新发给播放器。一次完整的“拉流再推流”过程至少涉及两组RTP会话。调试的时候必须分清是“拉流端卡了”还是“推流端卡了”。我习惯用Wireshark抓包先确认摄像头发往服务器的RTP是否平稳到达再去检查服务器到播放器这一段。如果拉流侧RTP丢包率高后面再调服务端都是白搭。2.2 SDP不是协议是“接线图”从摄像头拉RTSP前客户端会先发送DESCRIBE请求摄像头返回一段SDP文本里面写着视频编码格式、分辨率、RTP映射关系等。很多文章把SDP当成协议严格说它不是传输协议只是一份会话描述。它可以告诉客户端这段流是H.264payload type是96RTP时间戳基准是90000。知道了这些Live555才能正确把RTP包还原成视频帧。在多路转发场景里每路摄像头的SDP往往不一样。有的摄像头会强制指定SSRC有的还会在SDP里附加SPS/PPS信息。Live555在解析完SDP后会建立MediaSession和MediaSubsession你要做的就是把每一路MediaSubsession收到的帧数据转手喂给本地服务端的FramedSource。这里千万不能假设所有摄像头SDP格式完全一致代码里必须对每个subsession做合法判断。2.3 Live555事件循环和多路并发的执行模型Live555是单线程事件循环模型所有网络事件都在一个线程里通过select或者poll循环处理。很多做并发服务的人上来就开多线程这不对。用Live555的正确方式是“一套事件循环注册多个socket事件源”摄像头数量决定的是socket数量而不是线程数量。由于只有一个事件循环回调函数里绝对不能做阻塞操作。比如等待队列、休眠、耗时解码这些都会把整个服务器卡死表现为“一路摄像头卡住其他路也全卡住”。我的原则是回调只做数据拷贝和简单标记所有耗时操作挪到业务线程里做。队列之间用锁保护但锁的粒度要小只锁入队出队那段临界区。掌握这个执行模型后面写代码时才不会走弯路。3. 从摄像头拉流到本地转发的完整实现3.1 环境准备与依赖编译Live555源码可以从官方或镜像站拉取Linux下直接进live目录执行./genMakefiles linux再make生成头文件和静态库。树莓派上同样适用只是交叉编译时要注意架构。如果只是测试也可以直接用系统包管理器安装liblive555-dev但版本可能偏老我建议还是自己编译方便改配置。编译时有一个容易忽略的点默认配置会开启大量调试日志生产环境建议关掉。编译前修改config.*里相关宏或者在代码中设置env-setVerboseLevel(0)否则并发拉流时日志刷屏严重直接影响转发性能。树莓派这类性能有限的板子上日志磁盘写入频繁还会造成RTP时间戳跳动。依赖方面如果摄像头链接是rtsp://user:passip:port/stream1这种带鉴权的URL不需要额外装库Live555自带基础认证解析。如果遇到摘要认证部分版本支持得不是很好实测海康某些摄像头要用URL拼接方式绕过或者升级到较新的live555版本。OpenSSL只在RTSP over TLS场景下才需要普通局域网转发不必引入。3.2 拉流端用Live555客户端接收摄像头数据拉流端可以直接参考源码里的openRTSP示例。创建一个RTSPClient发DESCRIBE请求拿到SDP后用MediaSession::createNew建立会话然后对每个启用的MediaSubsession调用startStream。在流开始推过来后Live555会调用你自定义MediaSink的afterGettingFrame把一帧数据放到缓冲区里。// 伪代码示意 RTSPClient* client RTSPClient::createNew(env, rtsp://admin:123456192.168.1.64:554/h264, 1, cam0); client-sendDescribeCommand(continueAfterDescribe); void continueAfterDescribe(RTSPClient* client, ...) { MediaSession* session MediaSession::createNew(env, sdpDescription); MediaSubsession* sub session-createNewMediaSubsession(); sub-startStream(dummySink, afterGettingFrame); }需要注意多路摄像头时每个摄像头都要独立创建RTSPClient和MediaSession它们共享同一个UsageEnvironment这样所有流都注册到同一个事件循环里。不要给每路单独创建一个UsageEnvironment再各自跑事件循环那会让同步和资源管理变成灾难。afterGettingFrame里拿到的数据是剥离了RTP头的H.264帧。这里的“帧”可能是一个完整NALU也可能是分片取决于摄像头封装方式。实际上Live555已经帮我们处理了FU-A分片重组所以你拿到的基本上是完整访问单元可以直接交给服务端打包。我第一次做的时候在这个回调里直接调getNextFrame去取下一帧结果丢帧严重。正确做法是把数据缓冲区指针交给服务端的FramedSource让它提供给RTP Sink。3.3 转发端自定义MediaSource对接ServerMediaSessionLive555服务端的核心是RTSPServer加ServerMediaSession。每个ServerMediaSession代表一路媒体流可以包含音频和视频子会话。为了让摄像头数据能进入服务端必须写一个自定义FramedSource它从拉流端的帧队列里取数据并实现doGetNextFrame()方法。class CamFramedSource : public FramedSource { protected: virtual void doGetNextFrame() { if (queue.pop(frame)) { memcpy(fTo, frame.data, frame.size); fFrameSize frame.size; fDurationInMicroseconds frame.duration; afterGetting(this); } else { envir().taskScheduler().scheduleDelayedTask(1000, getNextFrame, this); } } };doGetNextFrame不能阻塞等待如果队列为空需要用定时任务延迟重试。数据放入fTo缓冲区后调用afterGetting(this)通知RTP Sink去打包发送。这里有一个细节fTo缓冲区的分配大小要根据摄像头最大帧尺寸来定。1080p的H.264单帧可能达到几百KB默认4KB缓冲区肯定不够我用的是maxFrameSize动态配置留够余量。服务端注册过程RTSPServer* rtspServer RTSPServer::createNew(env, 8554); ServerMediaSession* sms ServerMediaSession::createNew(env, cam0, cam0, Multi Cam Session); CamServerMediaSubsession* sub new CamServerMediaSubsession(source); sms-addSubsession(sub); rtspServer-addServerMediaSession(sms);这样客户端访问rtsp://127.0.0.1:8554/cam0时Live555会为每个播放器新建一个ServerMediaSubsession并调用它的createNewStreamSource来创建FramedSource。注意多路摄像头时一路摄像头可能对应多个播放器连接但拉流端只连摄像头一次播放器连接共享的是同一个帧队列。这个“单拉流、多分发”的结构正是这套系统能节省摄像头开销的关键。3.4 关键参数设置与测试方法首个要设置的参数是端口。默认RTSP端口是554但普通用户权限下绑不了1024以下端口开发时我习惯用8554。播放地址就是rtsp://192.168.1.100:8554/cam0、rtsp://192.168.1.100:8554/cam1。时间戳必须用90000Hz作为基准H.264 RTP规范要求这个值。服务器端收到摄像头帧后使用gettimeofday获取当前时间换算成相对起始时刻的时间差再乘90得到RTP时间戳。由于多路摄像头时钟并不一致统一在转发端重新打时间戳可以有效避免画面跳变。测试工具我通常这样用ffplay rtsp://127.0.0.1:8554/cam0快速验证单路流是否正常。ffprobe rtsp://127.0.0.1:8554/cam1查看流的编码参数是否正确。avprobe rtsp://127.0.0.1:8554/cam2检查音视频流信息适合确认服务是否响应。老牌一点的VLC也可以但VLC会把错误隐藏掉不利于排查。如果没有真实摄像头开发阶段可以用FFmpeg把一个本地MP4文件循环推成RTSP流来模拟比如ffmpeg -re -stream_loop -1 -i test.mp4 -c copy -f rtsp rtsp://127.0.0.1:8554/cam_test这里要注意本地FFmpeg推流和真实摄像头在RTP封装上可能有差异所以最终调试还是得拿至少一台真实IPC测否则会遇到很多意外问题。4. 多摄像头并发最容易踩的6个坑4.1 时间戳不同步导致画面跳变多路摄像头本身就是独立设备没有统一时钟。如果不处理转发出来的流播放时可能出现画面延迟忽大忽小甚至花屏。我第一次实测时两路摄像头都正常第三路加入后画面总是每隔几秒跳一下后来发现是时间戳计算方式不对。解决方法是在转发端统一使用系统启动时间作为时间基准用gettimeofday采集帧接收时刻换算成相对秒数再乘以90000。这样所有输出流的时间戳都来自同一个时钟源播放器就不会因为时间戳倒退而丢帧。如果后续要音视频同步还需要在同一时间基准上细调音视频pts偏移。4.2 阻塞回调导致所有流卡死前面提过Live555是单线程事件循环容易踩的坑就是在afterGettingFrame里做了太多事情比如H.265解码预览、写数据库、加锁等待。实测有一次我在回调里调了一段睡眠模拟处理逻辑结果六路摄像头全部在2秒内停止输出因为事件循环被一个人堵住了。优化思路是回调里只拷贝数据到环形缓冲区并唤醒业务线程具体的数据处理、写盘、转发都放到独立线程。队列用std::mutex保护但进入回调到出回调的时间必须控制在微秒级。另外处理完成后要立刻调用endOfData或afterGetting否则Live555会认为帧一直没发完链路就卡住。4.3 RTSP over UDP丢包与跨网络访问问题Live555默认传输协议是UDP局域网内问题不大一旦跨路由甚至跨网络RTP丢包概率会明显上升画面会出现马赛克或卡顿。很多RTSP客户端支持设置TCP模式在startStream时传入True强制使用RTSP over TCP这样RTP数据封装在TCP连接中稳定性和穿透性都好很多。代价是TCP模式会增加延迟和带宽占用不过在4G/5G回传、跨网段取流场景里丢包比延迟更致命。我在服务器端一般不做硬编码而是根据摄像头URL参数来判断内网走UDP跨网段用TCP。公网测试时也尽量不要依赖网上那些公开RTSP测试地址稳定性不受你控制自己用FFmpeg推一路模拟流更靠谱。4.4 关键帧间隔设置不好就会黑屏播放器请求RTSP流后必须收到关键帧才能开始解码。如果摄像头把关键帧间隔配置成5秒甚至更长那么客户端刚连接时可能要等好几个GOP周期才能出画面。更麻烦的是服务器端如果丢掉了关键帧后面所有帧都只能等下一个关键帧才能恢复。解决办法是尽量在摄像头端把IDR帧间隔调小比如1秒一个关键帧。转发端如果支持缓存关键帧可以保留最近一个IDR帧有新客户端接入时先发关键帧再发后续帧能大幅缩短首屏时间。但缓存关键帧会增加内存占用需要根据通道数量和分辨率权衡。4.5 设备厂商私有的RTSP差异同样是RTSP海康和大华的URL格式完全不同。海康通常是这样rtsp://admin:password192.168.1.64:554/Streaming/Channels/101大华常见的是rtsp://admin:password192.168.1.65:554/cam/realmonitor?channel1subtype0树莓派OV5647接GStreamer跑出来的RTSP地址可能又是另一套格式。代码里不能写死URL解析规则最好做成配置文件每路摄像头单独配置地址、用户、密码、传输协议和子码流/主码流。子码流分辨率低适合预览主码流适合录像和分析转发服务器最好把两路子会话都注册上。4.6 多路并发时的内存与缓冲区规划每一路摄像头都需要接收缓冲区和发送缓冲区。摄像头分辨率越高、码率越大缓冲需求越大。我用的是环形缓冲区大小设为单帧最大尺寸的4倍防止突发流量打满。实测1080p30fps的H.264流单帧最大可能到200KB环形缓冲区1MB左右才稳。还有一个容易忽略的问题fTo缓冲区是RTP Sink向FramedSource请求时传入的多路并发情况下这些缓冲区是相互独立的。如果缓冲区太小视频帧会被截断客户端收到破损的RTP包后表现出连续花屏。排查时先看服务端日志有没有Buffer overflow定位到具体链路再调大对应参数。5. 这套方案的落地场景与后续扩展5.1 适合用的实际场景这个多摄像头RTSP转发服务器最合适的场景是“取流复杂但播放端单一”。典型例子是工厂缺陷检测系统几个摄像头同时对准产线后端用YOLOv8做检测需要稳定获取多路视频流又不能每家设备都单独适配SDK。用Live555做汇聚后检测服务只需要从本机RTSP地址取流即可。安防平台接入也合适。很多自建的NVR或监控大屏软件只支持标准RTSP而前端可能混用海康、大华、萤石、小米摄像头统一转成标准RTSP协议后平台对接复杂度会明显下降。我自己还把一路树莓派摄像头接进去过虽然编码格式和码率跟专业IPC差很多但Live555作为协议层并没有任何问题。5.2 扩展从RTSP转发到其他协议这个框架的扩展性比我一开始预想的要好。如果你不想只对外提供RTSP可以把Live555服务端的FramedSource接到WebRTC网关把H.264帧转成WebRTC需要的格式这样浏览器就能直接播放。同理把帧再喂给FFmpeg做转码也可以转发成FLV或HLS格式给直播系统用。要做协议扩展关键还是保持“帧数据不落地内存中流转”的架构。只要内部帧格式统一底层是RTSP、RTMP还是WebRTC都只是封装层的变化。我目前正在尝试把这套转发服务器的帧队列抽象成通用接口把Live555耦合的部分隔离出来这样后续替换或增加协议不用推倒重写。这也算是我做完多路RTSP转发后觉得最值得投入的优化方向。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →