ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于live555的RTSP三件套:Linux C播放器、服务器与下载器实现

基于live555的RTSP三件套:Linux C播放器、服务器与下载器实现 简介这是一份基于Linux平台、使用C语言实现的RTSP客户端下载器源码资源面向希望深入理解RTSP/RTP协议交互、并具备一定C语言基础的网络开发者。程序能够从指定的RTSP服务器如live555拉取TS格式多媒体流依次完成RTSP会话建立、请求发送、RTP数据包接收与解析最终将媒体数据还原为本地文件且生成的文件可直接使用VLC播放器正常播放。资源包共4个文件包括3个C源文件与1个头文件压缩包整体仅5KB代码量小但覆盖了RTSP客户端涉及的核心流程。当前已有203人学习浏览说明其在同类协议学习资源中有一定参考价值。通过阅读源码可以具体掌握RTSP的OPTIONS、DESCRIBE、SETUP、PLAY等命令交互过程熟悉RTP报文头与负载的解析方法并学会在Linux环境下用Socket编程实现流媒体下载是一份适合协议入门与二次开发的简洁示例。1. RTSP 三件套落在 Linux Clive555 的完整技术地图“RTSP.rar_Linux C下载器_RTSP播放器_RTSP服务器_live555_rtsp”这个交付包把三件事放在一起能用 C 落地的 RTSP 下载器、一个能拉流播放的最小播放器、一个能对外分发媒体流的 RTSP 服务器。三者的共同底座是 live555。很多从安防、车载、边缘网关转 Linux 国产化平台的团队拿到这类工程包的第一反应是找现成命令跑通但真正要改的是会话管理、数据回调和落盘策略。live555 用 C 实现对外却保留了一层很像 C 回调的接口工程上可以把它当协议栈用本地代码仍然用 C 写业务。下文按协议、播放器、服务器、下载器、验证的顺序把整条能复现的链路走一遍。2. RTSP 协议详解与 live555 的线程模型2.1 信令状态机与 RTP/RTCP 通道的关系RTSP 本质上是文本控制协议在 TCP 上跑控制的是另一对 UDP 通道上的 RTP/RTCP 数据流。一个完整的拉流过程客户端依次发 OPTIONS、DESCRIBE、SETUP、PLAY、TEARDOWN服务器端通过 SDP 描述媒体类型、编码参数、采样率和端口映射。RTSP 不是 HTTP 那种一问一答就完事的协议它会经历状态迁移SETUP 成功后进入 ReadyPLAY 之后进入 PlayingPAUSE 回到 ReadyTEARDOWN 直接释放资源。实现里最容易忽略的是 RTCP 通道。RTCP 承担两件事周期报告丢包率和往返时延以及同步音视频的 RTP 时间戳。live555 的 RTSPClient 会在内部维护这两者的解析但前提是 SETUP 阶段协商出来的端口对能正常收发 UDP。如果服务器在 NAT 后面端口协商只在几秒内有效后续 UDP 阻塞一次整个会话就可能被判定超时。所以检视 RTSP 问题先看信令状态能不能推进到 Playing再看 RTP 包有没有真正进入接收循环。阶段协议状态典型触发现象Init尚未建立会话DESCRIBE 发送前ReadySETUP 成功可发送 PLAY / PAUSEPlayingPLAY 成功RTP 数据持续到达TerminatedTEARDOWN 或超时连接断开、回调停止2.2 live555 的模块边界RTSPClient、MediaSession、Sink 与 Groupsocklive555 的项目结构可以看成四层。UsageEnvironment 负责日志、错误和定时器Groupsock 封装 UDP socket负责 RTP 包收发和 multicast 地址管理liveMedia 是核心库里面是 RTSPClient、MediaSession、MediaSubsession、MediaSink 这一系列类BasicUsageEnvironment 则提供了一个默认的单线程事件循环实现。你写的业务代码基本都挂在三个回调面上RTSPClient 的信令回调、MediaSink 的数据回调、以及自定义 Scheduler 里的定时器。live555 组件承担角色业务侧需要处理的内容UsageEnvironment错误输出、日志、定时器重定向日志到 syslog 或文件GroupsockUDP 收发、multicast一般不用碰RTSPClientOPTIONS/DESCRIBE/SETUP/PLAY 信令处理回调里的 failCodeMediaSession解析 SDP、创建 Subsession按轨创建 SinkMediaSink接收解帧后的媒体数据C 业务回调的入口一个常见误解是 live555 是多线程的。其实默认实现是单线程事件循环全部网络读取和回调都在doEventLoop()里串行执行。这对 C 侧开发反而是好事回调里能安全访问共享状态不用加锁坏处是任何阻塞操作比如直接在一个回调里写磁盘或调用同步网络 API都会把整个事件循环卡死导致 RTCP 超时和掉线。后面讲下载器落盘时还会再提这个点。2.3 为什么不用 GStreamerlive555 的取舍点团队做 RTSP 选型时经常在 live555 和 GStreamer rtsp 服务器之间摇摆。GStreamer 的优势是管线完整从网络输入到解码显示一路打通调试工具 gst-launch 很方便但它的依赖树很庞大交叉编译到 ARM 板或 Linux 国产化平台时需要带一堆插件包光 rtsp 服务器相关的就有 gstreamer-rtsp-server、gst-plugins-base、gst-plugins-good体积和启动耗时都上来了。live555 编译产物就是几个静态库应用层只依赖三四个 .a 文件控制点全部暴露在代码里。取舍的边界在哪里如果业务重点是播放器本身后面要接视频渲染和音频输出选 GStreamer 更省事如果业务重点是“拉流、推流、落盘”这三个动作而且希望信令流程完全自主可控live555 明显更合适。还有一种场景是设备带宽有限只需要提取 H.264 裸流交给硬件编码器或自己的推流模块。live555 不会夹带额外的编解码格式拿到的就是最原始的 Annex-B 流正好对应标题里说的“Linux C 下载器”这类轻量工具。2.4 Linux C 环境下编译 minimal live555先把 live555 编一遍目录建议单独建一个工作目录放源码cd /opt/rtsp tar xzf live555-latest.tar.gz cd live ./genMakefiles linux make -j4genMakefiles linux会根据config.linux里的参数生成当前平台对应的 Makefile它决定了编译器选项、头文件路径和安装目录。make 完成后liveMedia/libliveMedia.a、groupsock/libgroupsock.a、BasicUsageEnvironment/libBasicUsageEnvironment.a这三个静态库是应用层需要链接的。bin 目录下会生成 openRTSP、testOnDemandRTSPServer 等可执行文件后续验证阶段直接用它们当参照物。提示主程序用 C 语言写、链接 live555 时必须用 g 或让 gcc 加上-lstdc否则会报一堆未定义的 C 运行时符号。这是“C 调用 C 协议栈”最常见的编译期坑。3. 基于 live555 的 RTSP 播放器实现3.1 最小播放器骨架openRTSP 是 live555 自带的参考客户端代码量大适合查问题但不适合二次开发。实际项目里写播放器一般把流程压缩成“创建 Environment → 创建 RTSPClient → 发送 DESCRIBE → 在回调里逐级往下走”。下面这个骨架是常见做法的浓缩版#include liveMedia.hh #include BasicUsageEnvironment.hh #include RTSPClient.hh int main(int argc, char** argv) { UsageEnvironment env *BasicUsageEnvironment::createNew(NULL); RTSPClient* client RTSPClient::createNew( env, rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov, 1, rtsp_player); client-sendDescribeCommand(continueAfterDESCRIBE); env.taskScheduler().doEventLoop(); return 0; }createNew的四个参数依次是环境、RTSP URL、verbosity 级别和应用名。verbosity 传 1 时会在终端打印每次信令的收发原文排错期建议打开上线前改成 0。sendDescribeCommand只是把请求发出结果要到回调函数里拿doEventLoop()是 live555 的单线程事件循环程序运行期间不能阻塞它。continueAfterDESCRIBE是自定义的回调函数处理逻辑一般分三步检查 resultCode 是否为 0从sessionDescription创建MediaSession然后遍历每个MediaSubsession调用setupMediaSubsession。这一步成功之后再发 PLAYPLAY 成功就代表码流通道建立完成。3.2 数据回调与 H.264 输出真正干活的是 Sink。live555 里每个轨道对应一个 MediaSubsession需要为它创建一个 MediaSink 的子类。子类里最重要的是afterGettingFrame方法它就是 C 业务回调的入口。收到数据后可以直接交给自己的 C 函数处理class RtspSink : public MediaSink { public: static RtspSink* createNew(UsageEnvironment env, MediaSubsession sub) { return new RtspSink(env, sub); } void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds) override { // 把 H.264 帧数据和 PTS 交给外部 C 函数 c_on_rtsp_video_frame(fReceiveBuffer, frameSize, presentationTime); } };live555 的 Sink 基类维护了一个接收缓冲fReceiveBuffer默认大小可通过构造函数参数调整但有几个关键点必须留意。afterGettingFrame收到的已经是一个完整的帧而不是裸 RTP 包对 H.264 来说一个 IDR 帧很可能被拆分到多个 RTP 包这在 Sink 内部由H264VideoStreamFramer负责拼接回调拿到的已经是连续码流。如果numTruncatedBytes不为 0说明缓冲区不够一帧数据被截断了这通常发生在高分辨率高码率场景需要把接收缓冲调大例如increaseBufferSizeTo(2 * 1024 * 1024)。3.3 Linux C 侧解码与显示格式的衔接播放器拉到流之后最终要把数据交给解码器。常见做法有两种一是把 live555 回调的数据直接送入硬解码器比如海思 MPP、瑞芯微 MPP 的 H.264 解码输入二是走软解交给 FFmpeg 的avcodec_send_packet。无论走哪条路首先要确认回调里出来的数据格式。数据情况处理方式Annex-B 格式起始码 00 00 00 01可以直接送给硬解或 FFmpegAVCC 格式NALU 长度前缀需要转换成 Annex-B 再送解码器缺少 SPS/PPS从 SDP 的 sprop-parameter-sets 提取并插入码流用 C 写个 NAL 检测函数把回调数据按起始码切开是后面的服务器和下载器都要复用的基础能力static int extract_nal_unit(const uint8_t *buf, size_t len, const uint8_t **nal, size_t *nal_len) { for (size_t i 0; i 4 len; i) { if (buf[i] 0x00 buf[i1] 0x00 buf[i2] 0x00 buf[i3] 0x01) { *nal buf i 4; /* 跳过 00 00 00 01 起始码 */ *nal_len /* 取到下一个起始码或帧尾 */; return 1; } } return 0; }拿到 NAL type 之后就能决定是继续等待下一帧还是立刻切文件。整个拉流链路的坑大多在这里回调到数据后没有判断 SPS/PPS 是否完整就直接送解码器导致解码器反复报错。建议在 C 侧维护一个两帧左右的前置缓冲确认拿到关键帧再启动解码。提示live555 按帧回调帧边界不等于 RTP 包边界。自行用原始 socket 去抓 RTP 再手工组帧属于重复造轮子花屏排查到最后往往发现是自己绕过了现成的 Framer。4. 自建 RTSP 服务器会话注册与端口分配4.1 服务器启动与媒体会话注册标题里的 RTSP 服务器部分live555 同样提供了完整实现。它沿用“会话Session 子会话Subsession”的结构一个会话对外暴露成一个 URL一个会话里可以挂视频轨、音频轨。创建一个只服务单路 H.264 文件的实时服务器核心代码是构造 RTSPServer 和 ServerMediaSession#include liveMedia.hh #include BasicUsageEnvironment.hh int main(int argc, char** argv) { UsageEnvironment env *BasicUsageEnvironment::createNew(NULL); RTSPServer* server RTSPServer::createNew(env, 8554, NULL); ServerMediaSession* sms ServerMediaSession::createNew( env, channel1, Channel One, rtsp-live-session); ServerMediaSubsession* sub H264VideoFileServerMediaSubsession::createNew(env, input.h264, True); sms-addSubsession(sub); server-addServerMediaSession(sms); env.taskScheduler().doEventLoop(); return 0; }RTSPServer::createNew的第二个参数是监听端口8554 是 RTSP 测试常用端口生产环境可以直接用 554。ServerMediaSession::createNew的第二个参数决定 URL 路径上面的代码会让这个会话对外变成rtsp://ip:8554/channel1。最后一个True表示重复发送源文件也就是常说的循环推流模式传 False 则只播一遍。H264VideoFileServerMediaSubsession读取裸 H.264 文件并按实时速率发送 RTP 包源文件要求是去掉 MP4 容器头的 Annex-B 流。如果手里是 MP4 或 TS先用 FFmpeg 把轨道提出来ffmpeg -i input.mp4 -c:v copy -bsf:v h264_mp4toannexb -an input.h2644.2 多路流的 URL 映射与实时推流源多路输出只需要重复addServerMediaSession。在同 一个 RTSPServer 上注册两个不同名字的 ServerMediaSession客户端就能用rtsp://ip:8554/channel1和rtsp://ip:8554/channel2分别访问。live555 没有内置路由规则URL 就是会话名的直接映射这反而好管理适合配置文件里维护一对一的通道映射表。如果是 IPC 相机采集、编码器实时输出的场景不适用H264VideoFileServerMediaSubsession应改用H264VideoStreamServerMediaSubsession。后者允许你用自己的线程把编码器产出的 H.264 帧推进 live555 的帧源对象实现真正的实时推流。差异点在于自定义一个 FrameSource把doGetNextFrame和采集线程绑定采集完成后调用框架的afterGetting通知取走数据。这一层是 RTSP 服务器里最需要业务定制的部分也是标题所指工程包的核心价值框架 live555 已经给了但推流源必须自己接。4.3 服务器端 TCP/UDP 传输策略与动态端口分配RTSP 服务器在 SETUP 响应时会给出 RTP 数据走的传输方式。默认是 UDPRTP 使用一个偶数的端口RTCP 占随后的奇数端口端口由服务器从可用端口里挑。这个行为在公网环境经常出问题因为防火墙未必放行这段动态 UDP 端口。传输模式端口特征典型场景RTP/UDP偶端口 相邻奇端口内网直连、低延迟RTP/AVP/TCP复用 8554/554跨防火墙、公网拉流multicast224.x.0.0/16 段局域网多路广播客户端请求 TCP 模式只需在 SETUP 时携带Transport: RTP/AVP/TCP;interleaved0-1live555 服务端会自动响应 interleaved 模式。多路并发时动态端口分配可能存在用尽或重叠风险尤其是同一台机器上多个 RTSPServer 实例。常见的解决办法是把服务器的可用端口范围配置成固定区间给每路流预设端口段和主业务端口隔离。提示服务器验证的第一件事是用 openRTSP 或 VLC 打开rtsp://ip:8554/session。这一步不通过不要急着改业务代码先看信令和端口连通性。5. RTSP 下载器与录像落盘5.1 拉流侧数据重组与最小录制命令标题里的 Linux C 下载器本质是把 RTSP 实时流转成文件的程序。最省事的做法是用 live555 自带的 openRTSP 拉裸流再交给 FFmpeg 封装openRTSP -d 30 -q rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov raw.h264 ffmpeg -f h264 -i raw.h264 -c copy recorded.mp4-d 30控制录制时长单位是秒-q关闭调试输出避免日志混进重定向的码流。下载器内部的要点是openRTSP 从网络拿到的是 RTP 包live555 的H264VideoStreamFramer会把 RTP payload 还原成 Annex-B 码流所以重定向出来的 raw.h264 已经是连续可用的 H.264。FFmpeg 再用-c copy做容器封装不重新编码速度很快。自定义下载器时数据链路有两处要自己处理。一是afterGettingFrame里需要判断当前帧是不是关键帧因为文件分段要在 IDR 处切二是时间戳换算关系。live555 回调带出来的presentationTime已经由 RTCP SR 包校准过直接把它作为录制文件的 PTS 源即可不要在本地自己打时间戳否则播放器的快进慢放会对不上点。5.2 存储时机、文件分片与回调线程解耦录像需求多半要求按时间分片比如每 30 分钟一个 MP4。分片时机不能只看墙钟时间如果正好切在一片连续帧中间切片文件会缺关键帧导致录像打不开。我习惯的做法是检测到 IDR 时再补刀先看上一条分片时长是否达到阈值达到就关闭当前文件否则继续写。static int on_h264_frame(uint8_t *buf, size_t len, struct timeval *pts, rec_ctx *ctx) { int nal_type buf[0] 0x1f; if (nal_type 5) { /* IDR 帧 */ if (ctx-file_open (pts-tv_sec - ctx-segment_start) ctx-segment_duration) { close_segment(ctx); /* 关旧文件 */ open_segment(ctx, pts-tv_sec); /* 开新文件 */ } } return mux_write(ctx, buf, len, pts); }这里的思路是每收到一帧先判断 NAL 类型和分片时长分片超时且当前是关键帧时才切换文件避免切片落在非关键帧上。mux_write是自己实现的 MP4 muxer或者直接调 FFmpeg 的av_interleaved_write_frame。无论哪种都不要把这个写磁盘动作直接放在 live555 的回调线程里。回调线程卡 100ms播放端就可能出现 RTCP 超时卡 500ms 以上基本要被判定断流。下载器设计上一定要有队列接口回调线程只入队落盘线程负责写文件和切分。这是“回调里不落盘”这条铁律的直接应用。5.3 参数调优缓冲、超时与断线重连下载器相比播放器多了一层“持续运行”的约束超时和重连参数决定它能连续跑多久。弱网环境下如果不主动处理会话超时一次断流会让下载器一直空转直到录制定时结束。常见做法是启动一个独立看门狗超过 N 秒没有新帧就主动 TEARDOWN 并重新走一遍“创建客户端 → DESCRIBE → SETUP → PLAY”流程。参数推荐初值调大后的代价看门狗超时5 秒断流识别变慢误重连减少接收缓冲2 MB内存上涨弱网抗抖更好分片时长30 分钟文件变大seek 变慢RTCP 报告间隔跟随丢包率改大后状态更新变慢缓冲大小直接影响高码率下的稳定性。假设视频码率 8 Mbps一秒就产生 1 MB 码流2 MB 缓冲只够扛住 2 秒以内的网络抖动再往上调到 4 MB 也是合理范围。live555 的 RTP 接收器对乱序报文的重排窗口很小超过窗口的包会被当作丢包处理。下载场景优先容忍乱序宁可多等一拍也不要丢关键帧所以回调里发现丢的是非关键帧时优先等待下一个 IDR 再续传而不是一丢包就重连。6. 验证 RTSP 三件套与排错技巧6.1 用公开 RTSP 测试地址验证播放器与下载器拉通验证不需要先自建服务器直接拉公网 RTSP 测试流更快。WOWZA 的演示流是比较稳定的测试源命令如下openRTSP -d 15 -q rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov自己的播放器程序完成后把上面的 URL 换到RTSPClient::createNew里观察能否经过 DESCRIBE 和 PLAY 两轮回调并且回调里真的持续收到帧。如果 openRTSP 能拉通但你的程序不行差异通常出在回调链处理不完整没给每个 Subsession 创建 Sink或共用了一个全局缓冲导致音视频轨道数据互相覆盖。下载器验证则按第 5 章的思路录制 60 秒后直接ffprobe recorded.mp4看时长和关键帧间隔时长不在 60±2 秒说明时间戳换算有误差。自制服务器也走同一套验证先 openRTSP 拉自己起的服务再让播放器程序拉自己起的服务两条线都通才算闭环。服务器绑定 8554 端口时遇到“端口已被占用”用netstat -tlnp看占用进程再考虑换端口或杀掉残留测试进程。6.2 常见故障表与解决手段RTSP 链路故障按信令阶段归类比较好定位故障现象典型原因处理手段DESCRIBE 408 超时控制端口不可达或 DNS 解析失败检查 554/8554 连通性改用 IP 直连SETUP 一直失败服务器 UDP 端口段被防火墙遮挡改用 rtp-over-tcp 模式测试PLAY 成功但无帧RTP 端口进不来或 Sink 缓冲区溢出tcpdump 确认 UDP 是否到本机调大缓冲播放花屏少了 SPS/PPS或丢包严重从 SDP 提取编码参数插入码流一段后自动断开RTCP 超时或没有 keep-alive定期发 OPTIONS 保活调整看门狗阈值PLAY 成功但无帧的问题先用tcpdump -i eth0 udp观察 RTP 包是否到达主机。包到了但应用层读不到问题多半在 Groupsock 绑定的端口或地址上。一个更隐蔽的坑是服务端 SDP 里写的是多播组地址客户端没加入多播组就会像黑洞一样收完包不报错这种情况只有抓包才能看出来。最后的调试技巧临时把RTSPClient::createNew的 verbosity 参数从 0 改成 1终端里会打出每次信令的收发原文包括服务器端不识别的方法、不支持的 Transport 组合。这是排查与第三方 RTSP 设备兼容性问题时最有效的入口对比两端打印的 SDP 选项就能定位是哪一侧的端口或编码参数不一致。本文还有配套的精品资源点击获取
返回列表