ARTICLE DETAIL

资讯详情

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

SSM+Nginx+FFmpeg 实现 RTSP 转 HLS 浏览器播放全链路

SSM+Nginx+FFmpeg 实现 RTSP 转 HLS 浏览器播放全链路 简介这份资源面向Java后端初学者与需要实现实时视频预览的开发者提供一套基于SSM架构、Nginx与FFmpeg将RTSP流转为HLS流并在前端HTML播放的完整方案。包内共52个文件涵盖16个png截图、12个css与8个js前端资源、5个html页面、2个java源码及jsp、conf、hosts等配置另附FFmpeg与Nginx的Windows安装包压缩包约70.26MB。资源包含RtspHlsController与RTSPtoHLS等核心代码、nginx.conf配置、playerJQueryDemo播放器页面及转流命令与调试源说明可帮助读者快速搭建从流媒体转码到网页播放的链路。已有260人学习适合希望掌握流媒体服务搭建与排错思路的入门者参考。1. 从摄像头到浏览器SSMNginxFFmpeg 把 RTSP 转成 HLS 的完整链路海康、大华、臻识这类网络摄像头的 RTSP 流浏览器原生播不了这是很多人第一次做视频监控页面时踩的坑。你拿到的项目标题里塞了四样东西SSM 架构、Nginx、FFmpeg、playerJQueryDemo 网页本质是一条「摄像头 RTSP → FFmpeg 切片 → Nginx 托管 → 前端 HTML 播放」的流水线。SSM 在这里不是主角它负责提供接口、管理设备信息和切片任务真正干活的是 FFmpeg 和 Nginx。这套方案适合做内网监控大屏、园区视频预览、设备演示页面的后端开发者尤其是已经用 SSM 搭好管理后台、现在要往里塞一路实时画面的场景。它不追求毫秒级延迟胜在浏览器兼容性好、部署简单、不依赖额外流媒体服务器。2. RTSP 与 HLS 的协议差异为什么不能直接播非要转一道2.1 两种协议在传输层就分道扬镳RTSP 走的是 TCP/UDP 上的独立控制通道媒体数据多用 RTP 承载典型端口 554。它的设计目标是「遥控器」式的会话控制PLAY、PAUSE、TEARDOWN客户端和服务器保持长连接延迟通常在一秒以内。HLS 则完全反过来它把视频切成一个个 TS 切片文件配一个 m3u8 索引客户端通过普通 HTTP 请求按序拉取。浏览器里video标签只认 HTTP 渐进式下载或 HLSSafari 原生支持Chrome 需要 hls.js根本不认识 RTSP。所以转码这一步不是可选项是硬门槛。2.2 转码方案选型FFmpeg 切片 vs 流媒体服务器常见做法有三种一是 FFmpeg 直接输出 HLS 切片到磁盘Nginx 当静态文件服务器二是用 SRS、ZLMediaKit 这类流媒体服务器做 RTSP 拉流再转 HLS三是商业方案。这个项目标题明确带了 Nginx 和 FFmpeg走的是第一种。它的好处是依赖最少一台机器上装好 FFmpeg 和 Nginx 就能跑不需要额外编译流媒体服务。代价是切片有延迟通常 6 到 15 秒取决于切片时长和播放列表长度。如果你要做实时对讲或云台控制这个延迟会让人抓狂但做监控预览、状态查看完全够用。2.3 最小可复现的 FFmpeg 切片命令先确认 FFmpeg 能拉到摄像头的流。海康的 RTSP 地址格式一般是rtsp://admin:密码IP:554/Streaming/Channels/101臻识科技 500 万摄像头类似具体路径查设备手册。下面这条命令把 RTSP 转成 HLSffmpeg -rtsp_transport tcp -i rtsp://admin:pass192.168.1.64:554/Streaming/Channels/101 \ -c:v libx264 -preset ultrafast -tune zerolatency \ -c:a aac -ar 44100 -b:a 64k \ -f hls -hls_time 4 -hls_list_size 6 \ -hls_flags delete_segmentsappend_list \ -hls_segment_filename /usr/local/nginx/html/hls/stream_%03d.ts \ /usr/local/nginx/html/hls/stream.m3u8-rtsp_transport tcp强制走 TCP避免 UDP 丢包导致花屏这是内网监控最稳的选择。-preset ultrafast和-tune zerolatency牺牲压缩率换编码速度降低首帧延迟。-hls_time 4表示每个切片 4 秒-hls_list_size 6表示 m3u8 里保留 6 个切片算下来播放列表覆盖 24 秒实际延迟大约 8 到 12 秒。delete_segments自动删旧切片防止磁盘被撑爆。append_list让 m3u8 追加而不是每次重写减少播放器重新加载。提示如果 FFmpeg 报Connection refused先确认摄像头 RTSP 端口是否开放用telnet IP 554测一下。如果报401 Unauthorized检查用户名密码里的特殊字符是否需要 URL 编码。3. Nginx 托管 HLS 切片路径、MIME 与跨域的三个配置点3.1 为什么用 Nginx 而不是 Tomcat 直接发文件SSM 跑在 Tomcat 上理论上把切片丢进 webapp 目录也能访问。但 Tomcat 对静态文件的并发处理远不如 Nginx而且 HLS 播放时客户端会频繁请求 ts 切片每个切片几百 KB 到几 MBTomcat 的线程池很容易被占满。Nginx 用 epoll 处理静态文件几千并发不在话下。另外 Nginx 可以单独配缓存和跨域头不影响 SSM 的业务接口。所以标准做法是FFmpeg 把切片写到 Nginx 的 html 目录SSM 只负责启动/停止 FFmpeg 进程和返回 m3u8 地址。3.2 Nginx 最小配置一个 location 搞定 HLS在nginx.conf的server块里加location /hls/ { alias /usr/local/nginx/html/hls/; add_header Cache-Control no-cache; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods GET, OPTIONS; types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } }alias指向切片实际目录注意结尾斜杠要和 location 一致。Cache-Control no-cache让浏览器每次拉最新的 m3u8否则播放器可能一直读旧列表。Access-Control-Allow-Origin *解决跨域如果你的网页和 Nginx 不同源这个头必须加。types块显式声明 m3u8 和 ts 的 MIME 类型有些 Nginx 默认不认 m3u8会返回application/octet-stream导致 hls.js 解析失败。3.3 验证 Nginx 是否正常吐切片配置改完执行nginx -t检查语法然后nginx -s reload。用 curl 验证curl -I http://127.0.0.1/hls/stream.m3u8正常应该返回200 OK和Content-Type: application/vnd.apple.mpegurl。如果返回 404检查alias路径和 FFmpeg 输出路径是否一致。如果返回 403检查 Nginx 工作进程用户对切片目录有没有读权限常见做法是chmod 755目录、chmod 644文件。注意Windows 下 Nginx 的路径要用正斜杠或双反斜杠alias C:/nginx/html/hls/;这样写别用单反斜杠。4. SSM 后端怎么管住 FFmpeg 进程接口设计与进程保活4.1 用 Java 的 ProcessBuilder 拉起 FFmpegSSM 里写一个 Service接收摄像头 ID拼出 RTSP 地址启动 FFmpeg 进程。核心代码public Process startHls(String rtspUrl, String outputM3u8) throws IOException { ListString cmd new ArrayList(); cmd.add(ffmpeg); cmd.add(-rtsp_transport); cmd.add(tcp); cmd.add(-i); cmd.add(rtspUrl); cmd.add(-c:v); cmd.add(libx264); cmd.add(-preset); cmd.add(ultrafast); cmd.add(-tune); cmd.add(zerolatency); cmd.add(-c:a); cmd.add(aac); cmd.add(-f); cmd.add(hls); cmd.add(-hls_time); cmd.add(4); cmd.add(-hls_list_size); cmd.add(6); cmd.add(-hls_flags); cmd.add(delete_segmentsappend_list); cmd.add(-hls_segment_filename); cmd.add(outputM3u8.replace(.m3u8, _%03d.ts)); cmd.add(outputM3u8); ProcessBuilder pb new ProcessBuilder(cmd); pb.redirectErrorStream(true); pb.redirectOutput(ProcessBuilder.Redirect.to(new File(/var/log/ffmpeg_hls.log))); return pb.start(); }redirectErrorStream(true)把 stderr 合并到 stdout方便统一看日志。redirectOutput把 FFmpeg 的输出写到文件不然进程缓冲区满了会卡住。返回的Process对象存到一个ConcurrentHashMapString, Process里key 用摄像头 ID方便后续停止。4.2 进程保活别让 FFmpeg 悄悄死掉FFmpeg 遇到网络抖动或摄像头重启会退出进程没了但数据库里还标着「在线」。常见做法是起一个定时任务每 30 秒检查 map 里的进程是否isAlive()不 alive 就重新拉。更稳的做法是写一个 shell 脚本用nohup跑 FFmpegSSM 只记录 PID但这样跨平台差。Java 方案在 Linux 和 Windows 都能跑适合这个项目的定位。Scheduled(fixedDelay 30000) public void keepAlive() { for (Map.EntryString, Process e : processMap.entrySet()) { if (!e.getValue().isAlive()) { String camId e.getKey(); // 从数据库查 rtspUrl 和 outputPath重新 startHls restart(camId); } } }Scheduled需要在 spring 配置里开task:annotation-driven/。fixedDelay表示上一次执行完再等 30 秒避免任务重叠。4.3 停止切片与资源清理停止时先process.destroy()等 2 秒还不死就destroyForcibly()。然后删掉对应的 ts 和 m3u8 文件不然磁盘会攒一堆垃圾。删除前确认没有其他进程在写同一个目录多摄像头场景下每个摄像头用独立子目录比如/hls/cam_001/stream.m3u8避免文件名冲突。5. 前端 playerJQueryDemo 播放页hls.js 接入与自动重连5.1 为什么用 hls.js 而不是 video.jsplayerJQueryDemo 这个名字暗示用 jQuery 做 DOM 操作播放器核心用 hls.js 最轻。video.js 功能全但体积大配置项多对于「播一路 HLS」这个需求属于杀鸡用牛刀。hls.js 只有几十 KBAPI 简单Chrome、Firefox、Edge 都支持。Safari 和 iOS 原生支持 HLS可以直接video srcxxx.m3u8但为了统一代码还是用 hls.js 判断Hls.isSupported()再决定。5.2 最小播放页面代码!DOCTYPE html html head meta charsetUTF-8 title视频预览/title script srchttps://cdn.jsdelivr.net/npm/hls.js1/script script srchttps://cdn.jsdelivr.net/npm/jquery3/dist/jquery.min.js/script /head body video idplayer controls autoplay muted stylewidth:800px;/video script $(function () { var video document.getElementById(player); var m3u8Url http://127.0.0.1/hls/cam_001/stream.m3u8; if (Hls.isSupported()) { var hls new Hls({ liveSyncDurationCount: 3, liveMaxLatencyDurationCount: 10, maxBufferLength: 10 }); hls.loadSource(m3u8Url); hls.attachMedia(video); hls.on(Hls.Events.ERROR, function (event, data) { if (data.fatal) { switch (data.type) { case Hls.ErrorTypes.NETWORK_ERROR: hls.startLoad(); break; case Hls.ErrorTypes.MEDIA_ERROR: hls.recoverMediaError(); break; default: hls.destroy(); setTimeout(initPlayer, 3000); } } }); } else if (video.canPlayType(application/vnd.apple.mpegurl)) { video.src m3u8Url; } }); /script /body /htmlliveSyncDurationCount: 3表示播放器尽量落后直播边缘 3 个切片配合hls_time 4就是 12 秒延迟。liveMaxLatencyDurationCount: 10是最大容忍延迟超过就跳帧追。maxBufferLength: 10限制缓冲区大小防止内存涨太快。错误处理里NETWORK_ERROR调startLoad()重试MEDIA_ERROR调recoverMediaError()其他致命错误销毁后 3 秒重建。5.3 自动重连与延迟调优监控场景下网络抖动很常见hls.js 的默认重试策略有时候不够。可以在ERROR回调里加一个计数器连续失败 5 次就提示用户「信号丢失」。延迟方面如果觉得 12 秒太长把hls_time降到 2 秒、hls_list_size降到 3延迟能压到 6 秒左右但切片请求频率翻倍Nginx 和磁盘压力增大。这是个权衡没有免费午餐。提示autoplay和muted要一起加Chrome 的自动播放策略要求视频静音才能自动播否则控制台会报NotAllowedError。6. 避坑与排查RTSP 转 HLS 最常见的五个翻车现场6.1 现象页面一直转圈m3u8 请求 404原因FFmpeg 还没生成第一个切片或者输出路径和 Nginx alias 不一致。FFmpeg 启动后需要几秒才写第一个 ts 和 m3u8前端如果立刻请求就会 404。解决SSM 启动 FFmpeg 后延迟 3 到 5 秒再返回播放地址或者前端轮询 m3u8 直到 200 再初始化 hls.js。6.2 现象播放几秒后卡住控制台报bufferStalledError原因切片时长和播放列表长度不匹配或者 Nginx 返回的 ts 文件不完整。常见于 FFmpeg 还在写某个 ts 时播放器就请求了它。解决hls_list_size至少是liveSyncDurationCount的两倍让播放器有足够缓冲。另外确认 Nginx 的sendfile开启减少文件传输延迟。6.3 现象FFmpeg 进程 CPU 占用 100%机器卡死原因-preset ultrafast虽然快但多路摄像头同时转码时 CPU 还是扛不住。每路 1080p 转码大约吃 1 到 2 个核。解决限制并发路数或者用-vf scale640:360降分辨率监控预览不需要原画质。有条件上带硬件编码的机器用-c:v h264_nvenc或h264_qsv。6.4 现象Windows 下 FFmpeg 路径带空格ProcessBuilder 启动失败原因Java 的 ProcessBuilder 不会自动处理带空格的路径参数。解决把 FFmpeg 装在无空格路径下比如C:\ffmpeg\bin\ffmpeg.exe或者用cmd.add(\C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe\)手动加引号。更稳的做法是配环境变量直接调ffmpeg。6.5 现象HLS 延迟越来越大从 10 秒涨到 1 分钟原因播放器没有追帧一直按自己的节奏播而直播边缘在往前走。解决hls.js 配liveMaxLatencyDurationCount超过阈值自动跳。或者在 m3u8 里加#EXT-X-PROGRAM-DATE-TIME让播放器知道真实时间。FFmpeg 加-hls_flags program_date_time就能输出这个标签。7. 进阶技巧用 Nginx 缓存和 FFmpeg 参数把延迟压到 5 秒以内延迟是 HLS 方案最被诟病的地方但通过几个参数组合可以压到可接受范围。核心思路是缩短切片时长、减少播放列表长度、让播放器更激进地追直播边缘。下面这套参数是我在多个内网监控项目里调出来的1080p 单路延迟稳定在 5 到 7 秒ffmpeg -rtsp_transport tcp -i rtsp://admin:pass192.168.1.64:554/Streaming/Channels/101 \ -c:v libx264 -preset ultrafast -tune zerolatency -g 20 -keyint_min 20 \ -c:a aac -ar 44100 -b:a 64k \ -f hls -hls_time 2 -hls_list_size 3 \ -hls_flags delete_segmentsappend_listomit_endlist \ -hls_segment_filename /usr/local/nginx/html/hls/cam_001/seg_%03d.ts \ /usr/local/nginx/html/hls/cam_001/stream.m3u8关键变化-g 20强制每 20 帧一个关键帧配合-hls_time 2让切片边界对齐关键帧播放器切换切片时不用等下一个关键帧。omit_endlist告诉播放器这是直播流不要等结束标记。hls_list_size 3只保留 3 个切片播放列表覆盖 6 秒。前端 hls.js 对应调整var hls new Hls({ liveSyncDurationCount: 2, liveMaxLatencyDurationCount: 5, maxBufferLength: 6, maxMaxBufferLength: 10, liveBackBufferLength: 0 });liveBackBufferLength: 0禁止回看缓冲省内存也省 CPU。liveSyncDurationCount: 2让播放器只落后 2 个切片也就是 4 秒。加上网络传输和编码时间实测端到端 5 到 7 秒。Nginx 侧再加一层缓存减少磁盘 IOlocation /hls/ { alias /usr/local/nginx/html/hls/; add_header Cache-Control no-cache; add_header Access-Control-Allow-Origin *; open_file_cache max1000 inactive20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } }open_file_cache把最近打开的 ts 文件句柄缓存住播放器连续请求切片时不用反复 open/close在高并发下能明显降低 CPU。inactive20s表示 20 秒没访问就淘汰valid30s表示缓存 30 秒内有效。验证延迟的方法在摄像头前放一个秒表网页上截图看秒表读数和画面时间的差值。如果超过 10 秒先检查hls_time是不是设大了再检查播放器是不是没追帧。我一般会在页面角落加一个video.currentTime和hls.latency的显示方便现场调试。这套方案我用了三年多从海康到臻识从内网到专线翻车最多的地方不是 FFmpeg 参数而是 Nginx 的 MIME 类型和跨域头。每次新环境部署先把这两个确认了能省一半排查时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表