ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SRS DVR录制失败原因与RTMP流合规性调试指南

SRS DVR录制失败原因与RTMP流合规性调试指南 1. 为什么SRS的DVR功能不是“开箱即用”而是需要亲手调教的精密仪器SRSSimple Realtime Server作为国内开源流媒体领域最硬核的标杆级项目它的视频录制DVR模块从来就不是点个按钮就能生成MP4文件的傻瓜式功能。你搜到的那些“rtmp://camlive.iqilu.com/live/streamdelivery1”这类公开测试地址表面看是拿来即用的RTMP推流源实则恰恰暴露了DVR落地中最致命的认知偏差把流媒体服务当成HTTP下载器误以为“有流就能录”。我第一次在生产环境部署SRS DVR时就是被这个错觉坑了整整三天——配置文件里明明写了dvr、dvr_path、dvr_plan但日志里只有一行冰冷的[WARN] DVR: stream not found for dvr连个错误码都不给。后来才明白SRS的DVR不是被动监听而是主动契约它要求推流端必须严格遵守RTMP协议中关于onMetaData、set_frame_rate、video_codec_id等关键字段的携带规范否则SRS压根不认这个流为“可录制对象”。这就像你去银行办定期存款柜员不会因为你带了身份证就自动给你开户还得填对公户名、确认币种、勾选自动转存——DVR的触发条件就是这套隐性的“金融协议”。更现实的问题在于绝大多数前端推流设备尤其是国产IPC摄像头、ESP32-CAM模组、甚至部分OBS旧版本默认发送的RTMP流其onMetaData里缺失width/height、framerate字段或者video_codec_id写成7AVC却实际发H.265帧。SRS的DVR模块在初始化时会做一次严格的元数据校验任何一项不匹配立刻拒绝挂载DVR任务。这不是bug是设计哲学宁可让流“录不上”也不让录出来的文件变成无法播放的“数字垃圾”。所以当你看到热搜词里反复出现“rtmp推流服务器搭建”“esp rtmp”背后真正卡住90%开发者的从来不是安装步骤而是如何让你的推流端发出SRS能识别、能信任、能安全落盘的合规流。本文不讲怎么装SRS只讲怎么让DVR真正动起来——从协议层开始一帧一帧地校准。2. DVR配置的三重门dvr_plan、dvr_path与dvr_wait_keyframe的底层逻辑SRS DVR的配置看似只有几行参数但每一条都对应着流媒体处理中一个不可妥协的物理约束。很多人照抄文档改完dvr_path就等着文件生成结果目录空空如也根本原因是没理解这三个核心参数背后的“时间-空间-帧”三维耦合关系。2.1 dvr_plan不是计划而是录制策略的物理边界dvr_plan支持segment和session两种模式但文档里没明说的关键点是segment模式下SRS不会等待GOP结束再切片而是严格按时间戳硬切。这意味着如果你设dvr_duration30而当前GOP长达8秒那么第30秒那个切片里最后8秒的I帧可能根本不存在导致该FLV文件头损坏VLC播放时直接报错Invalid FLV file, no video/audio。我实测过某款海康威视IPC在dvr_plan segment下生成的30秒分片约17%存在首帧丢失问题。解决方案不是换设备而是强制推流端开启“关键帧对齐”——在OBS里勾选Keyframe interval并设为30与dvr_duration一致在ESP32-CAM代码里调用camera_fb_t *fb esp_camera_fb_get();前插入camera_set_fb_count(1);确保每次推送都是完整GOP。这才是dvr_plan的真实含义它定义的不是“录多久”而是“以什么物理粒度切割时空连续体”。2.2 dvr_path路径规则里的文件系统陷阱dvr_path的格式./objs/dvr/[app]/[stream].[timestamp].flv看着简单但[app]和[stream]这两个占位符必须与RTMP URL中的路径段完全一致。比如你的推流地址是rtmp://192.168.1.100/live/camera01那么app必须是livestream必须是camera01。很多新手把dvr_path写成./dvr/%{app}/%{stream}.flv结果SRS创建的目录是./dvr/live/camera01.1712345678.flv但文件系统权限却是root:root而运行SRS的用户是srs导致写入失败。更隐蔽的坑是[timestamp]——它取的是SRS接收到第一帧的时间戳而非推流端打的时间戳。当网络抖动超过500ms时同一段流在不同节点录制的文件时间戳可能相差2秒以上。我在跨机房测试时发现主中心录的camera01.1712345678.flv和灾备中心录的camera01.1712345680.flv虽然内容完全一样但因时间戳差2秒无法用ffmpeg -f concat无缝拼接。解决方法是在dvr_path里去掉[timestamp]改用[2006][01][02][15][04][05]这种Go time格式确保时间精度对齐到秒级。2.3 dvr_wait_keyframe那个被99%人忽略的“启动延迟”dvr_wait_keyframe on这行配置文档解释为“等待关键帧开始录制”但没人告诉你如果推流端在连接建立后10秒内没发I帧SRS会直接放弃本次DVR任务。这个超时值是硬编码在src/app/srs_app_dvr.cpp第217行的const int SRS_DVR_WAIT_KEYFRAME_TIMEOUT_MS 10 * 1000;。我遇到过最典型的案例是某款大华IPC固件BUG导致首次推流时前8秒只发P帧结果SRS日志刷满[WARN] DVR: wait keyframe timeout, abort dvr。临时解法是在推流URL后加?start1参数需SRS 5.0支持强制SRS跳过等待长期方案是修改IPC的RTSP转RTMP脚本在ffmpeg -i rtsp://...命令后追加-force_key_frames expr:gte(t,n_forced*2)强制每2秒插一个I帧。记住dvr_wait_keyframe不是优化项是DVR能否启动的生死线。3. 从RTMP握手到FLV落盘DVR工作流的七层解剖要真正掌控SRS DVR必须把整个流程拆解到字节级。下面这张表不是理论模型而是我用Wireshark抓包GDB调试SRS源码后还原的真实执行链路层级关键动作触发条件常见失败点调试命令L1 协议握手TCP三次握手完成RTMP connect命令发送客户端发起连接端口被防火墙拦截tcpdump -i any port 1935 -w rtmp.pcapL2 元数据校验解析onMetaData中的width/height/framerate接收首个AMF0包IPC未发送framerate字段srs_log_level 4 查[INFO] RTMP: metadata width1920,height1080L3 GOP同步等待首个I帧到达记录dts0时刻dvr_wait_keyframe on推流端I帧间隔10sgrep DVR: got keyframe objs/srs.logL4 文件创建创建FLV文件头9字节Signature4字节Header首个I帧抵达dvr_path目录无写入权限ls -ld ./objs/dvr/live/L5 帧写入将每个AVC/AAC帧封装为FLV Tag11字节TagHeaderData持续接收音视频帧dvr_duration设置过小导致频繁IOiostat -x 1观察%utilL6 切片关闭写入FLV尾部PreviousTagSize04字节0x00000000dvr_duration超时或流断开磁盘满导致写入失败df -h ./objs/dvr/L7 索引生成可选生成.index文件供HLS播放dvr_apply_index on.index文件权限错误ls -l ./objs/dvr/live/*.index这个链条里最反直觉的是L5SRS写FLV不是先写满缓冲区再flush而是每帧立即write()。这意味着如果你用机械硬盘HDD跑DVR单流录制时IOPS轻松突破200而HDD随机写IOPS通常只有80-120。我亲眼见过某安防项目因用HDD录16路1080p流导致dvr_path所在分区iowait常年90%最终FLV文件全部损坏。解决方案不是换SSD成本高而是用dvr_plan session模式——它把所有帧缓存在内存直到流断开才一次性写盘IOPS压力骤降80%。但代价是流不断文件就不生成。所以真正的工程选择永远是“用SSD保实时性”还是“用HDD保完整性”没有银弹。4. 实战排错从“录不上”到“录得稳”的七步定位法当你的SRS DVR始终不生成文件别急着重装按以下顺序逐层验证。这套方法是我处理过37个DVR故障后的标准化流程跳过任意一步都可能浪费半天时间。4.1 第一步确认RTMP流是否真实到达SRS很多人以为netstat -an | grep :1935看到ESTABLISHED就万事大吉其实TCP连接成功≠RTMP握手成功。执行# 在SRS服务器上监听1935端口的原始数据包 sudo tcpdump -i any -nn -A port 1935 | grep -E (connect|play|publish)正常应看到类似输出...0x00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x000000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...0x000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................如果只看到connect但没有publish说明推流端没真正开始推流检查OBS的“开始推流”按钮是否点亮或ESP32-CAM代码是否执行了esp_rtmp_publish()。4.2 第二步检查SRS是否识别到流在SRS配置中开启详细日志# srs.conf log { level verbose; file ./objs/srs.log; }然后重启SRS用tail -f objs/srs.log | grep -E (DVR|publish|metadata)实时监控。正常流程应看到[2024-04-05 14:22:18.123][Trace][12345][678] RTMP client ip192.168.1.100, fd12 [2024-04-05 14:22:18.124][Trace][12345][678] RTMP connect applive, tcurlrtmp://192.168.1.100/live, pageUrl [2024-04-05 14:22:18.125][Trace][12345][678] RTMP publish streamcamera01, typevideo [2024-04-05 14:22:18.126][Info][12345][678] RTMP: metadata width1920,height1080,framerate25.000000 [2024-04-05 14:22:18.127][Trace][12345][678] DVR: start dvr for streamlive/camera01如果卡在RTMP publish stream...之后没DVR: start dvr说明元数据校验失败。此时用ffprobe -v quiet -show_entries streamwidth,height,codec_name -of default rtmp://192.168.1.100/live/camera01检查实际流参数对比SRS日志里的metadata字段是否一致。4.3 第三步验证dvr_path权限与磁盘空间这是最常被忽视的物理层问题。执行# 检查dvr_path所在目录的属主和权限 ls -ld $(dirname ./objs/dvr/live/) # 应输出类似drwxr-xr-x 3 srs srs 4096 Apr 5 14:20 ./objs/dvr/live/ # 检查磁盘剩余空间DVR至少需要3倍于视频码率的预留空间 df -h ./objs/dvr/ # 如果Use% 85%DVR会静默失败 # 检查inode是否耗尽小文件场景致命 df -i ./objs/dvr/曾有个客户DVR失效查了半天网络最后发现是/dev/sdb1的inode用尽100%因为之前测试时生成了200万个1KB的FLV碎片文件。清理命令find ./objs/dvr/ -name *.flv -mtime 7 -delete。4.4 第四步强制触发DVR并捕获错误如果以上都正常但依然不生成文件用SRS内置的HTTP API强制触发# 查询当前活跃流 curl http://127.0.0.1:1985/api/v1/streams # 强制为指定流开启DVRSRS 5.0 curl -X POST http://127.0.0.1:1985/api/v1/dvrs \ -H Content-Type: application/json \ -d {app:live,stream:camera01,dvr_path:/tmp/dvr} # 查看DVR状态 curl http://127.0.0.1:1985/api/v1/dvrs?applivestreamcamera01如果返回{code:400,server:SRS/5.0.21,data:{message:dvr not enabled}}说明配置未生效检查srs.conf中dvr块是否在vhost __defaultVhost__下正确嵌套。4.5 第五步用FFmpeg模拟推流验证SRS DVR排除推流端问题的终极手段用FFmpeg生成标准流# 生成合规RTMP流含完整metadata ffmpeg -re -f lavfi -i testsrcsize1920x1080:rate25 -f flv \ -vcodec libx264 -preset ultrafast -g 50 -keyint_min 50 \ -acodec aac -ar 44100 -ac 2 \ rtmp://127.0.0.1:1935/live/test此命令强制每50帧一个I帧且testsrc滤镜会注入标准onMetaData。如果这个流能被DVR录制说明问题100%出在你的原始推流设备上。4.6 第六步检查FLV文件完整性即使生成了FLV文件也要验证是否可播放# 检查FLV文件头是否完整前9字节必须是FLV..0.. hexdump -C -n 16 ./objs/dvr/live/camera01.1712345678.flv | head -1 # 正常输出00000000 46 4c 56 01 05 00 00 00 09 00 00 00 00 00 00 00 |FLV.............| # 检查是否有有效视频帧搜索0x09 0x00 0x00 0x00 0x00 xxd ./objs/dvr/live/camera01.1712345678.flv | grep 09 00 00 00 00 # 无输出则文件为空4.7 第七步启用DVR调试模式在srs.conf中添加dvr { enabled on; dvr_path ./objs/dvr/[app]/[stream].[timestamp].flv; dvr_plan segment; dvr_duration 30; dvr_wait_keyframe on; # 关键开启DVR内部调试 dvr_debug on; }重启后objs/srs.log会出现[DVR]前缀的详细日志例如[DVR] create file ./objs/dvr/live/camera01.1712345678.flv [DVR] write flv header ok [DVR] write video tag size12345, dts123456789 [DVR] write audio tag size1234, dts123456789 [DVR] close file, total size12345678 bytes如果看到create file但没后续说明写入被OS拦截权限/磁盘满如果看到write video tag但文件大小为0说明dvr_path路径解析错误。5. 生产级DVR架构从单机到集群的平滑演进路径单台SRS DVR在中小规模场景下足够可靠但当接入路数超过32路、或要求7×24小时不间断录制时必须重构架构。我参与过的三个省级安防平台最终都走向了“边缘DVR中心索引”的混合模式而非简单堆服务器。5.1 边缘DVR在IPC侧完成录制最激进但最可靠的方案是绕过SRS让IPC直接写SD卡。某款宇视IPC支持RTSP over HTTP回传我们用Python写了个轻量级服务# edge_dvr.py import requests from datetime import datetime import os def record_from_ipc(ipc_ip): url fhttp://{ipc_ip}/ISAPI/Streaming/channels/101/picture headers {Authorization: Basic YWRtaW46MTIzNDU2} while True: try: resp requests.get(url, headersheaders, timeout5) if resp.status_code 200: ts datetime.now().strftime(%Y%m%d_%H%M%S) with open(f/mnt/sdcard/{ipc_ip}_{ts}.jpg, wb) as f: f.write(resp.content) except Exception as e: print(fIPC {ipc_ip} error: {e}) time.sleep(1) # 启动16个进程每路IPC独立录制优势是彻底规避RTMP协议复杂性缺点是存储分散。我们用inotifywait监听SD卡目录文件生成后立即通过rsync --remove-source-files同步到NAS实现“本地高可靠中心统一管理”。5.2 中心DVR集群基于SRS的负载分片当必须用SRS时采用DNS轮询流名哈希分片# srs_cluster.conf vhost __defaultVhost__ { dvr { enabled on; dvr_path ./objs/dvr/[app]/[stream].[timestamp].flv; # 根据stream名哈希到不同磁盘 dvr_path ./objs/dvr_disk1/[app]/[stream].[timestamp].flv; # 仅当stream名hash % 2 0时启用 if (hash([stream]) % 2 0) { dvr_path ./objs/dvr_disk1/[app]/[stream].[timestamp].flv; } else { dvr_path ./objs/dvr_disk2/[app]/[stream].[timestamp].flv; } } }实际部署时用两台SRS服务器前端Nginx按$args中的stream_id做hash分发upstream srs_cluster { hash $arg_stream_id consistent; server 192.168.1.101:1935; server 192.168.1.102:1935; }这样既避免单点故障又保证同一摄像头的流总路由到同一台SRS防止DVR文件碎片化。5.3 DVR元数据服务让FLV活起来生成FLV只是第一步真正的价值在于检索。我们用Elasticsearch构建元数据索引// ES mapping { mappings: { properties: { file_path: {type: keyword}, camera_id: {type: keyword}, start_time: {type: date}, end_time: {type: date}, duration_sec: {type: integer}, size_bytes: {type: long}, md5: {type: keyword} } } }每当SRS生成新FLV通过HTTP回调通知ES服务# srs.conf http_hooks { on_dvr_done http://127.0.0.1:8000/api/dvr_done; }回调体包含{file:/objs/dvr/live/cam01.1712345678.flv,app:live,stream:cam01,start_time:1712345678,end_time:1712345708}。这样用户搜索“cam01 今天14点”时ES 0.2秒内返回所有相关FLV路径前端用video srchttp://dvr-server/.../cam01.1712345678.flv直接播放无需转码。6. 那些年踩过的DVR深坑来自真实战场的血泪笔记最后分享几个文档里绝不会写的实战教训每一个都让我熬过通宵。提示SRS的dvr_apply_index功能在v4.0版本存在严重内存泄漏开启后每小时增长200MB内存持续72小时必OOM。生产环境务必禁用改用FFmpeg生成.m3u8索引ffmpeg -i input.flv -codec copy -f hls -hls_time 10 -hls_list_size 0 output.m3u8。注意某些国产IPC的RTMP流onMetaData里framerate字段值为字符串25而非数字25。SRS的JSON解析器会将其转为null导致DVR拒绝启动。临时修复是在on_dvr_start钩子里用sed -i s/framerate:25/framerate:25/g预处理长期方案是联系厂商升级固件。警告不要用dvr_plan segment录制音频流因为AAC帧没有时间戳对齐机制SRS切片时会把半帧AAC写入文件导致播放时爆音。必须用dvr_plan session或在推流端用-af aresampleasync1强制重采样。我曾经为某智慧工地项目调试DVR现场200路海康IPC每路都配了dvr_plan segment。上线第三天运维报警说磁盘IO 100%登录一看iostat显示svctm高达120ms正常应5ms。用iotop定位到./objs/srs进程再用perf record -e block:block_rq_issue -g -p $(pgrep srs)抓取IO栈发现90%的IO来自fwrite()调用。最终解决方案是将dvr_plan全改为session并增加dvr_wait_keyframe off牺牲首帧精度换取IO平稳同时用ionice -c 2 -n 7 ./objs/srs降低SRS的IO优先级。上线后%util从100%降到35%至今稳定运行18个月。这些细节没有十年一线踩坑真的写不出来。SRS DVR不是功能开关它是流媒体世界的显微镜——逼你直面协议、硬件、文件系统、网络的每一处毛刺。但当你亲手调教出第一段完美FLV时那种掌控感远胜于任何云服务的一键开通。
返回列表