尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
用librtsp搭建RTSP H.264 Server:从NALU到会话管理
简介这是一份面向网络流媒体开发者的轻量级RTSP服务器实现源码包基于librtsp-master库改造专注于H.264视频流的传输与控制适合需要快速搭建RTSP服务或深入理解RTSP/RTP/RTCP协议栈的初中级开发者。压缩包共19个文件以12个头文件和3个源文件为核心涵盖rtsp_server.h、rtp.h、rtcp.h、hash.h等模块另含Makefile.in构建配置、configure.ac自动配置脚本及MIT许可证文本整体仅30KB结构精简便于阅读和二次开发。目前已有401人学习下载热度适中。通过这份代码读者可以深入理解DESCRIBE、SETUP、PLAY等RTSP方法的状态机处理掌握RTP数据打包与RTCP控制信息交互学习H.264 NAL单元的解析与封装并借鉴其线程管理、内存池和定时器等底层设计为实际项目中的RTSP服务模块开发或协议学习提供完整参考。1. 用 librtsp 搭 rtsph264server 之前先想清楚这三件事如果你的项目里出现了librtsp_rtsp_rtsph264server_这类名字多半不是为跑通一个 VLC 预览而是想把一路实时 H.264 码流变成可以被多个终端同时拉取的 RTSP server。这个方向的典型诉求包括把海康相机主码流转发给视觉算法服务器、给上位机提供统一取流地址、或者在边缘设备上把编码器输出分发给多个客户端。直接跑现成的mediamtx或ffserver也能实现但当你需要在协议层控制 SPS/PPS、NALU 分割、传输模式时基于 librtsp 这类轻量库自己搭一个 server 反而是性价比最高的选择。但动手之前我必须先泼一盆冷水RTSP 本身不是“管道”它是一套状态机H.264 也不是“视频流”它是一串有依赖关系的 NALU。这三件事没想清楚后面全是翻车点一是协议交互顺序不能错二是 H.264 上 RTP 的打包规则必须逐位执行三是服务端不负责转码只负责搬运。只有把这三点立住你才真正需要看后面的代码怎么组织。2. 先把协议层立住RTSP 与 H.264 打包server 的成败全在这一公里2.1 RTSP 状态机DESCRIBE / SETUP / PLAY 之外你还得处理什么RTSP 通讯模型低层是一个控制通道加一到两个数据通道基本交互顺序为客户端先发OPTIONS探活然后DESCRIBE要 SDP再对视频轨发起SETUP协商传输方式收到200 OK之后发PLAY服务端才开始真正向对应地址推 RTP 包。TEARDOWN负责结束会话。很多人只实现了“PLAY 以后开始推流”这正是客户端反复报connection refused或卡在 buffering 的根源。在协议处理上有两个细节比状态码本身更重要。一个是每个会话要持有独立的CSeqlibrtsp这类库大多是单例管理但如果一个 server 实例同时服务多路请求CSeq乱序会导致控制命令被客户端直接丢弃。另一个是SETUP响应里的Session字段客户端会把它放在后续请求的Session头里回传服务端如果不去校验它而直接按新会话处理多个客户端就会互相把对方的流顶掉。// 伪代码按状态机处理四个核心方法 rtsp_server_set_on_describe(srv, on_describe); rtsp_server_set_on_setup(srv, on_setup); // 解析 transport, 记住 client 的 RTP/RTCP 端口 rtsp_server_set_on_play(srv, on_play); // 开始从源读 H.264 并推流 rtsp_server_set_on_teardown(srv, on_teardown); // 停止推流, 释放接收端口 static int on_setup(rtsp_session_t *s, const char *track) { // 同一个 session 必须存储 cseq、transport 模式、目的地址 s-cseq s-request_cseq; s-rtp_port parse_transport(s-transport, RTSP_TRANSPORT_RTP); // 在 SETUP 回复里带 Session: s-session_id return write_setup_response(s); }这里的要点是每个rtsp_session_t必须是一个完整上下文对象而不是临时变量。否则你没法在PLAY时拿到SETUP阶段解析出的客户端端口这是新手自己实现最容易漏的连接。如果你拿到的 librtsp 版本没有自带 session 结构就用一个哈希表自己维护session_id - {transport, cseq, ssrc}别省这一步。2.2 H.264 上 RTP 的两种打包Single NAL 与 FU-A边界怎么切H.264 的原始码流叫 Annex B 格式也就是用00 00 01或00 00 00 01做起始码后面跟一个完整的 NALU而 RTP 传输并不直接搬运整个 NALU它要把 NALU 切成不超过 MTU通常 12001400 字节的 RTP 包。小 NALU 直接整包塞进 RTP payload叫 Single NAL Unit大 NALU 则切成多个分片用 FU-A 头标记分片的开始与结束。这里有一个非常容易被忽略的“边界判定”问题00 00 01起始码只表示一个 NALU 的开始但你要靠读下一个起始码才能判断上一个 NALU 在哪结束。如果你用“找到起始码就截断一次”的简单思路一个 NALU 的尾部会被切到下一个 NALU 的头里播放端就会花屏。正确做法是顺序扫描字节流把每个起始码的位置记录成数组再按数组区间切出完整 NALU。static int split_annexb(uint8_t *buf, size_t len, uint8_t **nalus, size_t *nalu_len, int max_count) { int count 0; size_t i 0, start 0; while (i 3 len) { if (buf[i] 0 buf[i1] 0 (buf[i2] 1 || (buf[i2] 0 buf[i3] 1))) { size_t start_code_len (buf[i2] 1) ? 3 : 4; if (i 0) { // 上一段结束位置 i if (count max_count) return count; nalus[count] buf start (start ? (buf[start2] 1 ? 3 : 4) : 0); nalu_len[count] (i - start) - (start ? 1 : 0); // 折算起始码勿直接减错 // 真实项目中这里要从 start 到 i 减去 start_code 长度上面只是示意 count; } start i; i start_code_len; } else i; } return count; }上面那个注释我特意强调“不要直接减”是因为这里藏着一个经典翻车第一个起始码前的字节如果是00 00它也可能被误识别为起始码的一部分。更稳妥的方案是找到起始码后跳过start_code_len字节往下找同时用nalu_len next_start - current_start - start_code_len计算长度最后再判断 NALU 类型type b[4] 0x1F。H.264 的 type 123 里type 7 是 SPS、8 是 PPS、5 是关键帧做 server 时至少要保留 SPS/PPS 用于生成 SDP关键帧信息用于判断能否在非关键帧处开始推流。2.3 选型对比为什么用 librtsp 而不是 GStreamer / FFmpeg / Live555很多人的第一反应是用gst-rtsp-server或ffmpeg -f rtsp直接拉一路源再推出去。优点是无脑缺点是可控制性差GStreamer 内部经过编码器封装延迟和缓冲不可控FFmpeg 的 RTSP muxer 对 multithread 支持有限多客户端场景下会互相抢占。Live555 是最接近 librtsp 的替代品但它的源码较厚重H.264 打包逻辑封装得比较深想对 SPS/PPS 和 NALU 分片做定制改动的学习成本接近重写。librtsp 的价值在于薄它只负责 RTSP 控制协议把 SDP、RTP 打包、发送线程都交给你。你可以把网络协议栈完全握在自己手里这对特殊时钟同步和低延迟场景是决定性的。下表是我反复权衡后的选择依据方案协议层可控性H.264/RTP 打包多客户端复杂度适合场景librtsp 自建高完全自己写需自己管理 session嵌入式、定制协议、推流给 YOLO 等算法live555中框架已实现自动处理部分快速可用但不好改gst-rtsp-server中依赖 caps较高大量音视频处理管道mediamtx低黑匣子极低纯转发、无需定制如果你已经在用 GStreamer 做采集和编码gst-rtsp-server 是更自然的衔接点但如果你只是想把一段裸 H.264 变成对外可拉流的 rtsph264serverlibrtsp 的最小依赖和直接可见的 NALU 处理流程会让你排查“画面卡在 I 帧前”这类问题时省一半时间。我自己的习惯是算法盒子里的转发服务用 librtsp视频推拉流工具链里的中间层用 GStreamer。3. 用 librtsp 把一路 H.264 推成 RTSP server最小可跑实现3.1 定义 SDPprofile-level-id、sprop-parameter-sets 不能靠猜RTSP 的DESCRIBE响应体是一段 SDP里面sprop-parameter-sets这个字段直接决定客户端能不能解码你的流。它由 H.264 的 SPS 与 PPS 做 Base64 编码后拼在一起中间用逗号分隔。还有一个profile-level-id是由 SPS 的前三个字节转成十六进制得到的通常长这样42C01F这三样东西之间是强关联如果profile-level-id与 SPS 内容不一致部分严格客户端尤其是 iOS 原生播放器会拒绝解码。// 十六进制转 Base64这只是项目中一个辅助函数 static char *base64_encode(const uint8_t *data, size_t len, char *out, size_t out_len); static void build_sdp(const uint8_t *sps, size_t sps_len, const uint8_t *pps, size_t pps_len, char *sdp, size_t sdp_len) { char sps_b64[128], pps_b64[64]; base64_encode(sps, sps_len, sps_b64, sizeof(sps_b64)); base64_encode(pps, pps_len, pps_b64, sizeof(pps_b64)); snprintf(sdp, sdp_len, v0\r\n o- 0 0 IN IP4 0.0.0.0\r\n sH264 stream\r\n t0 0\r\n mvideo 0 RTP/AVP 96\r\n cIN IP4 0.0.0.0\r\n artpmap:96 H264/90000\r\n afmtp:96 packetization-mode1; profile-level-id%02X%02X%02X; sprop-parameter-sets%s,%s\r\n, sps[1], sps[2], sps[3], sps_b64, pps_b64); }这段代码中mvideo 0的端口写 0 是因为 RTSP 里端口信息由SETUP阶段动态替换SDP 里固定端口没有意义。packetization-mode1表示允许除 single NAL 外的 FU-A 分片这是 VLC、ffplay 和监测平台最常用的模式。另外留意rtpmap:96 H264/90000这个 90000 是 RTP 时间戳的采样时钟H.264 视频固定用它写别的值播放器会按音频速率解析导致倍速。我在实际项目中见过一个常见的坑直接把从别处抄来的 SDP 拿来用导致 sps 与真实码流不匹配。解码器拿到错误 SPS 后是不会报具体错误的结果就是“有的机器能放、有的机器黑屏”。所以sprop-parameter-sets一定要从你实际要发的流中解析出来不能依赖“大部分摄像头都是这个参数”的假设。3.2 从原始码流读取 NALU同步、拼接与缓冲策略真正的 rtsph264server 的输入通常来自两种途径从文件循环读取或者从摄像头海康/大华 SDK的编码回调里拿裸流。无论哪种你都要自己完成“字节流到 NALU 列表”的切分。文件场景相对简单按 2.2 节给出的边界扫描逻辑做即可回调场景则是“不定长 可能跨回调边界”一个 NALU 的上半段在上一次回调、下半段在下次回调这需要你维护一个跨回调的粘包缓冲。typedef struct { uint8_t *buf; size_t cap, len; } bitstream_t; int bitstream_push(bitstream_t *bs, uint8_t *data, size_t len) { if (bs-len len bs-cap) { // 原 NALU 还没拼完整新数据就来了 bs-cap (bs-len len) * 2; // 直接扩大不要硬截断 bs-buf realloc(bs-buf, bs-cap); } memcpy(bs-buf bs-len, data, len); bs-len len; return 0; }参数上有一个建议缓冲区大小不要设成固定 1MB 完事而应给目标 NALU 预计最大长度留余量。1080p 的 IDR 帧加上 SEI 和 SPS/PPS一个 GOP 的首包可能有 300KB 以上如果你把回调切片按 4KB 提供realloc触发的拷贝次数会显著影响编码线程与网络线程的竞争。我一般做法是解析后的 NALU 立即送去 RTP 发送队列发送完成就释放只有跨回调片段才滞留在 bitstream 里。3.3 处理四个核心会话以请求 CSeq 为上下文很多轻量 RTSP 实现喜欢用全局变量保存“当前客户端端口”“当前播放状态”这在同时连两个客户端时直接崩溃。正确姿势是做到请求级别上下文。librtsp本身往往只回传到你的回调函数一个请求对象你要从请求对象里取出CSeq、Session、Transport再拿Session去做映射。// 每个回调的第一步拿到当前会话上下文 static rtsp_session_t *get_session_from_request(rtsp_server_t *srv, rtsp_request_t *req) { const char *sid rtsp_request_get_header(req, Session); if (sid) return server_find_session(srv, sid); // 如果不是新 SETUP就按已有 Session 走 return server_create_session(srv); // SETUP 首访, 生成新 ID } static int on_play(rtsp_server_t *srv, rtsp_request_t *req) { rtsp_session_t *s get_session_from_request(srv, req); s-state RTSP_STATE_PLAYING; s-base_seq 0; // 新会话从 0 开始也行但必须不同于上一个会话 s-base_timestamp get_rtp_clock_ms(); // 90kHz 时钟, 不是系统毫秒 return write_play_response(srv, s); }一个会话对应一个 RTP 序列号空间和时间戳空间。PLAY时置零或置成某一随机起始值都行但多个会话之间绝不能共享同一个递增的 seq。如果第二个会话推的数据包 seq 从第一个会话末尾继续播放端会认为丢包严重并触发重传请求表现就是画面一直卡住缓冲。这也解释了为什么“只用一个全局 seq 变量”的实现总是二客户端必挂。3.4 组装 RTP 包并推送给客户端FU-A 分包与发送线程这是整个 server 的核心。RTP 头部 12 字节version、payload type、sequence、timestamp、ssrc。对于 H.264如果 NALU 长度小于mtu_size - 12且不是 SPS/PPS 这种必须单独打的 type 7/8就 Single NAL 整包发超过 MTU就切 FU-A。SPS/PPS 必须作为单独包发而且最好在关键帧前再各发一遍这样新加入的客户端能快速解码。static int send_h264_nalu(rtp_session_t *r, const uint8_t *nalu, size_t len) { if (len RTP_FIXED_HEADER_LEN r-mtu_size) { // Single NAL: 原 NALU 直接放 payload return rtp_send_packet(r, nalu, len); } // FU-A 分片: 每个包小于 MTU uint8_t fu_indicator (nalu[0] 0xE0) | 28; // type28, FU-A uint8_t fu_header nalu[0] 0x1F; // 低5位是原 nal type size_t payload_len len - 1; uint8_t *p (uint8_t *)(nalu 1); size_t remain payload_len; const size_t max_fu_payload r-mtu_size - RTP_FIXED_HEADER_LEN - 2; int start_flag 1; while (remain 0) { size_t sz remain max_fu_payload ? max_fu_payload : remain; uint8_t *rtp_payload r-sendbuf RTP_FIXED_HEADER_LEN; rtp_payload[0] fu_indicator; rtp_payload[1] (start_flag ? 0x80 : 0x00) | (remain sz ? 0x40 : 0x00) | fu_header; memcpy(rtp_payload 2, p, sz); rtp_send_packet(r, rtp_payload, sz 2); p sz; remain - sz; start_flag 0; } return 0; }逻辑说明分片时第一个 FU 的S位置 1最后一个的E位置 1中间包 S/E 都置 0。如果发送过程中丢了一个中间片解码端会主动丢弃整个 NALU 直到下一个关键帧。fu_indicator 保留原 NALU 的 F 与 NRI 比特type固定填 28。这部分参数直接决定播放器是否将 RTP 载荷识别为合法 H.264所以不要为了省字节做任何宽容处理。发送线程建议用环形缓冲加条件变量实现编码/读取线程往队列投递 RTP 包网络线程从队尾取包并发往对应会话的 UDP 地址或 TCP socket。这里不展开队列代码但如果你用的是一个简单链表在两种线程间共享变量请给链表加锁否则高帧率下会产生“一半状态”的系统性问题表现形式是偶发的花屏和丢包。4. 三个必调参数与传输模式选择TCP/UDP、低延迟与兼容性4.1 参数表buffer、MTU、SPS 重发间隔、超时时间给出一组我在嵌入式板端调过的起始参数适合 1080p/30fps 的 H.264 主码流机器为 Arm Cortex-A53。这些不是“最优”而是“不会被坑”的起点。参数建议值说明RTP MTU12001400一般局域网用 1400跨广域要降发送队列长度300 包约半秒钟的 1080p RTP 包量再多内存压力变大SPS/PPS 重发间隔每路关键帧前新客户端快速解码的关键RTSP session 超时3060 秒超过后主动 TEARDOWN避免僵尸会话时间戳时钟90000 HzRTP 标准不能按系统 tickRTCP 间隔每 35 秒一次控制端口不可省部分客户端靠它维持MTU 这个参数最微妙。设小了IDR 帧会被切成更多片CPU 占用升高设大了跨路由器时包会被 IP 层分片一旦其中一片丢失整个 FU-A NALU 全报废观感就是周期性的卡帧。所以靠谱的原则是局域网摸到 1400 可以稳上云或跨网降到 1200。发送队列长度同样是个平衡点。队列短了当算法侧因为 YOLO 处理卡顿导致拉流源暂时停顿时客户端立刻表现成丢帧、跳帧队列长了客户端播放延迟不断累积。若你的客户端是 VLC 这种自带 jitter buffer 的话300 包以内体验最好。我的习惯是服务端不补帧、不当缓存用——RTSP server 的职责是转发不是缓冲。4.2 TCP 与 UDP一个低延迟一个免 NAT别把兼容性搞复杂RTSP 原生走 UDP 传输 RTP。UDP 没有重传所以延迟最低丢包率在网络出问题时会直接表现为马赛克。TCP 用 RTP over RTSPinterleaved传输RTSP 控制端口和 RTP 数据共用一条 TCP 连接可以穿越 NAT但会有 TCP 重传导致的数据阻塞画面延迟通常比 UDP 高 50150ms。我在SETUP响应里是这样处理的优先支持TCP/RTP/AVP/TCP同时兼容UDP。因为不少取流工具例如某些 APP 端的安卓 rtsp 拉流默认只试 TCP 模式如果 server 只回 UDP 传输客户端会选择性的黑屏或等超时。你在on_setup里解析到Transport头后直接按照客户端指定模式回传不要自己强行指定。if (strstr(transport, TCP)) { // 交错模式: RTP 数据通过同一个 socket, 用 $ 标识 channel id s-mode RTP_TRANSPORT_TCP; s-rtp_channel 0; s-rtcp_channel 1; snprintf(resp, ..., Transport: RTP/AVP/TCP;unicast;interleaved0-1\r\n); } else { s-mode RTP_TRANSPORT_UDP; snprintf(resp, ..., Transport: RTP/AVP/UDP;unicast;client_port%d-%d\r\n, ...); }如果走 TCP 交错模式注意 RTP 包前面要加 4 字节头0x24即$、channel id、16 位长度。很多人第一路客户端拉流正常第二路直接乱就是因为把 channel id 写死成 0两条会话的数据全挤在通一个通道里。5. 避坑与排查RTSP H.264 server 最常见的 5 个坑5.1 客户端花屏或 VLC 卡停在“BUFFERING 33%”现象打开 VLC 拉流一直显示 buffering 不进画面或播放后前两三秒花屏随后恢复。原因SDP 中sprop-parameter-sets与码流不符或者 server 从未独立发送过 SPS/PPS 包播放器等到下一个关键帧才成功解码。解决在每路关键帧前强制重发 type 7/8 NALU不要依赖 SDP 里的参数。VLC 并不会在所有时间点都主动从 SDP 里读取参数它更信任 RTP 流里带出来的 SPS/PPS。在调试阶段可以用wireshark过滤h264看 RTP 载荷中是否规律出现0x67SPS和0x68PPS。5.2 第二路客户端连不上单会话全局变量惹的祸现象第一个客户端正常第二个连接总是停在SETUP阶段拿不到响应或拿到 200 后PLAY不推流。原因代码里用了全局rtp_send_socket、全局seq、全局session第一路会话的状态在SETUP后被第二路覆盖。解决把 socket、seq、ssrc、时间戳全部收进rtsp_session_t。每次SETUP创建新会话上下文PLAY时只启动属于该会话的发送协程或线程。注意发送线程不能和会话生命周期解耦客户端TEARDOWN时必须能终止对应发送线程否则线程会持续往一个死端口塞 UDP 包资源最终耗尽。5.3 画面流畅但音量/速度异常时间戳错误现象视频比真实速度快一倍或慢一倍但画面不花屏偶发跳帧。原因RTP 时间戳按 90000 Hz 计算但代码里用系统毫秒time(NULL) * 1000当采样时间或者按帧率 30fps 推算时没有乘 90000/30。例如 30fps 下每帧时间戳增量应为 3000而不是 33 或 1000。解决统一用 64 位单调时钟如clock_gettime(CLOCK_MONOTONIC)计算 RTP 时间戳ts (now_us - start_us) * 90。不要把帧率换算和系统时间混用。同时把 SSRC 固定成每次会话随机值别从时间戳派生否则播放端会把不同会话识别为同一发送源。5.4 拉流源是海康摄像头裸流长度字段不对现象从海康 SDK 回调拿到的H.264数据前四个字节有时不是00 00 00 01而是00 00 00 02或一个长度字段data_len前缀按 Annex B 解析时切出的 NALU 长度全是错的。原因海康部分型号 SDK 输出的码流是 AVCC 格式4 字节长度 NALU需要通过解码帧类型判断是否要转封装。解决在接入层统一做格式归一化先判断前几个字节如果第三字节等于 0x01 则是 Annex B直接解析如果是长度字段则按长度字段跳过并补写起始码。建议在回调入口就归一这样后面所有逻辑只要面对 Annex B 一种格式避免在 NALU 切分里做两套分支逻辑。static int normalize_to_annexb(uint8_t **buf, size_t *len) { if (*len 4) return -1; if ((*buf)[2] 0x01) return 0; // 00 00 01 if ((*buf)[3] 0x01) return 0; // 00 00 00 01 uint32_t nal_len ((*buf)[0] 24) | ((*buf)[1] 16) | ((*buf)[2] 8) | (*buf)[3]; if (nal_len 0 nal_len *len) { // 前端拼接 4 字节起始码, 后移长度 } return 0; }调这套逻辑时最烦躁的是回调帧头一会是 Annex B、一会是长字段海康子码流的格式还经常与主码流不同。最稳妥的是不看头字节看帧类型从 SDK 回调的type字段里判断是否为关键帧再从关键帧里查找 SPO 起始码所在位置来确认格式。5.5 RTSP 拉流延时从 200ms 涨到 800ms越拉越卡现象客户端长时间拉流延迟逐步增大重连后恢复正常。原因发送队列堆积。当网络偶发阻塞或客户端读取慢时队列入包速度大于出包速度延迟不断累积而 server 没有丢包或超限策略。解决给发送队列设置水位线队列超过 80% 时直接丢最老的P帧非关键帧保留关键帧。如果网络中同时有几个高延迟客户端则在发送策略上对每个会话独立判断不要让最慢的客户端拖累整体发送循环。RTSP server 不是可靠文件传输适度丢旧帧保证实时性是对的。6. 进阶把 server 接进监控拉流与 YOLO 检测链路验证与压测6.1 用 ffplay / VLC 做标准验证与延迟测量写完了 server先别急着接算法。验证时我最常用的命令是ffplay -fflags nobuffer -flags low_delay -rtsp_flags tcp \ -analyzeduration 100000 -probesize 100000 rtsp://127.0.0.1:8554/h264其中-rtsp_flags tcp强制走 TCP 交错模式能排除 UDP 丢包干扰-analyzeduration和-probesize把起播时间压到最低。如果这条命令起播 0.2 秒内出现画面说明 SDP 与推流节奏基本没问题。延迟测量的做法是在摄像头前放一个毫秒级计时器画面然后手机相机同时拍真实时钟与屏幕里的时钟差值就是端到端延迟。注意包含采集与编码延迟的话RTSP server 本身的转发延迟应小于 30ms。6.2 对接监控取流与 YOLOserver 后面挂视觉算法监控视频拉流 RTSP 后接 YOLO 是典型场景但很多人把 RTSP 作为 YOLO 的输入直接解码这会让 GPU 解码和网络拉流耦合你会踩到“拉流中断时检测进程直接崩溃”的坑。正确做法是用 librtsp 拉一路 H.264 子码流解码之后只取关键帧做推理同时把它作为“边缘预览流”推给另一路客户端。子码流分辨率低、码率小YOLO 推理延迟可以控制在最低水平。# 伪代码: 两个线程互不干扰 # thread 1: rtsp_pull_and_yolo() - 解码, 推理, 只存结果 # thread 2: rtsp_server_push() - 直接转发原始码流给客户端看这个架构的核心收益是解耦。算法端卡顿不会影响预览端客户端多了不会拖垮推理线程。rtsph264server 在中间做一个纯转发代理本地网络压力很小。我遇到的实际案例是现场用一台低功耗板卡同时做 4 路海康子码流的 YOLO 安全帽检测和 4 路 RTSP 预览CPU 占用稳定在 40% 以下起决定性作用的就是“server 不做转码只处理 RTP 打包和会话管理”。6.3 压测方法论与我的习惯压测最直接的办法是同时开 10 个 ffplay 或 VLC 拉同一路流观察 server 的内存和 CPU。如果你的实现里每个会话独立发送线程10 路客户端会占用大量线程资源我一般改造为单发送线程 每会话一个队列用 select/poll 或 epoll 一次发完所有会话的待发包。这样压测到 20 路以内 CPU 都不会明显波动。如果要看更细的丢包就用ffprobe -show_packets去抓 RTP 包长重点观察关键帧分片是否完整。如果分片后第一个 FU 与最后一个 FU 的 sequence 不连续说明发送线程枪内存或队列被覆盖这通常不是网络问题而是同步问题。我最深的一个教训是第一次上线时把所有调试日志都开着运行。RTSP 控制通道低频日志还好打印每个 RTP 包的 seq 和 timestamp 会直接拖垮发送循环导致延迟飙到秒级。后来我形成两个习惯日志按会话级别分级默认只开控制面RTP 数据面用循环计数器和原子变量暴露指标而不是打日志。这套思路我沿用到现在对 librtsp 相关的小规模转发服务尤其有效。如果你准备把 rtsph264server 用于生产环境我希望这些经验能帮你少走一圈弯路。本文还有配套的精品资源点击获取
RELATED

相关推荐

AI日报:轻量模型、多Agent协作与AI编程工具链最新进展

AI日报:轻量模型、多Agent协作与AI编程工具链最新进展

2026年9月28日,AI圈的新闻又多到刷不完。我一边刷着各大技术社区和开源仓库的最新更新,一边把手头几个项目的验证结果记下来,这才敢说今天有资格写这份日报。今天的内容会覆盖轻量大模型、Agent协作、AI编程工具链、短剧与绘画工作流&#xf…

📅 2026/10/31 9:32:57
焊接机器人单面坡口填充技术解析

焊接机器人单面坡口填充技术解析

1. 项目概述:为什么单面坡口填充能成为埋弧焊工艺的“分水岭” “焊接机器人的坡口填充功能”这个标题乍看平平无奇,但把后半句“老师傅都赞叹的埋弧焊工艺:对接接头的单面焊接”叠加上去,整件事的分量就完全不同了。我干焊接自动…

📅 2026/10/31 9:27:57
电压表读数不准?旋转校正与真实工作原理揭秘

电压表读数不准?旋转校正与真实工作原理揭秘

1. 这不是教科书里的电压表,而是你手边那块“会歪的表”该怎么用 你拆开过一块老式指针电压表吗?表盘上那根细长的金属指针,轻轻一碰就晃,调零螺丝拧三圈反而更偏;测同一节干电池,换个角度读数差0.2V&#…

📅 2026/10/31 9:27:57
MORE NEWS

更多资讯

📰

马尾辫扎发全攻略:从发质判断到场景化技巧一次讲透

马尾辫这东西,看着简单,实际操作起来门道比想象中多得多。十个人里有八个会说"马尾谁不会扎啊",但真到了要拍照、要见人、要运动、要通勤的时候,你就会发现——扎得好不好看、服不服帖、勒不勒头、撑不撑得住&#xff0…

📰

动态稀疏网络做时间序列分类:PyTorch实现与TopK门控详解

简介:面向对动态稀疏网络(DSN)与时间序列分类感兴趣的开发者和研究者,这份资源以Python形式给出完整代码与配套数据,可直接用于复现模型训练过程。压缩包共13个文件,以10个.py源码文件为主,另有…

📰

CNN人脸识别考勤从demo到可靠部署:特征提取、阈值调控与避坑指南

简介:这是一份基于卷积神经网络的人脸识别考勤演示项目,面向Python初学者与大学生课程设计场景,完整展示从模型调用到界面操作的流程。资源打包了训练好的Caffe模型与网络配置,提供摄像头实时采集、人脸检测、特征比对以及签到签退…

📰

openrig开源六轴机械臂DIY全攻略:硬件选型、运动学与视觉引导

那段时间我几乎天天泡在工作台前调机,刷到 openrig 这个项目的时候,第一反应是“又来了一个开源机械臂”,毕竟这类项目在社区里一抓一大把。真正把文件下载下来、打完第一批结构件、写完第一版控制代码后,我才意识到它跟那些半成品…

📰

数字签名与数字认证:哈希、证书链及openssl验签实践

数字签名和数字认证这两个概念,大部分人在课本上见过,真正在接口联调、配置 HTTPS、签发证书的时候才会发现它们差别有多大。之前帮一个团队排查接口验签失败的问题,两边打印出来的哈希值一模一样,最后定位到是拼接待签名串时把空…

📰

Agent从单兵到摇人:多Agent系统安全边界重构与权限衰减实践

1. 从“单兵作战”到“摇人干活”:Agent 能力跃迁背后的安全逻辑断层 过去两年,我接触过不少 Agent 项目,从最简单的“读文件、调接口、返回结果”的脚本型 Agent,到能自主规划任务、调用多个工具、甚至动态生成子任务的多层 Agen…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬