☰
SSM+Nginx+FFmpeg实现RTSP转HLS网页播放实战
2026/10/14 4:47:50 网站建设 项目流程

简介:这份资源面向Java后端初学者与需要实现实时视频预览的开发者,提供一套基于SSM架构、Nginx与FFmpeg将RTSP流转换为HLS流并在前端HTML播放的完整方案。包内共52个文件,约70.26MB,涵盖FFmpeg与Nginx安装包、SSM架构Java代码、前端播放器Demo及配置说明,其中png截图与css、js、html构成前端播放页面,java与jsp文件负责后端流控制,conf与txt提供Nginx配置和转流命令参考。已有262人学习下载,适合希望快速搭建流媒体服务的小白。读者可参照部署说明与调试源,完成从RTSP拉流、FFmpeg转码到Nginx分发HLS、前端播放器集成的全流程,并借助排错思路与更新日志定位常见问题,省去自行摸索环境配置的时间。

1. 从 RTSP 到 HLS:为什么这套 SSM + Nginx + FFmpeg 组合值得动手

很多做安防、直播或者物联网平台的朋友都遇到过同一个需求:摄像头出来的 RTSP 流,怎么在浏览器里直接播放?浏览器原生不支持 RTSP,而 HLS 是兼容性最好的方案之一。标题里这套组合——SSM 架构做业务后端、Nginx 做流媒体分发、FFmpeg 做协议转换、前端用 playerJQueryDemo 播放——本质上是一条「拉流 → 转封装 → 切片 → HTTP 分发 → 网页播放」的完整链路。

它适合谁?适合手里已经有 Java Web 项目、需要快速把监控画面嵌进管理后台的团队。整套方案不依赖云服务,本地就能跑通,成本可控。我第一次搭这套环境时,光 FFmpeg 参数就调了一下午,后来才明白关键不在代码,而在转码参数和 Nginx 路径的配合。接下来我把这条链路拆开,从环境准备到前端播放,把能复现的步骤和踩过的坑都讲清楚。

2. 环境准备:FFmpeg、Nginx、SSM 三件套怎么装才不打架

2.1 FFmpeg 安装与验证:别用系统自带的老版本

FFmpeg 是整条链路的发动机,负责把 RTSP 拉过来、转成 HLS 切片。安装方式取决于操作系统,我一般推荐直接下载官方静态编译包,避免依赖冲突。

# Linux 下解压静态包并放到统一目录 tar -xvf ffmpeg-release-amd64-static.tar.xz sudo mv ffmpeg-*-amd64-static /usr/local/ffmpeg # 把可执行文件加入 PATH echo 'export PATH=/usr/local/ffmpeg/bin:$PATH' >> ~/.bashrc source ~/.bashrc # 验证版本和关键协议支持 ffmpeg -version ffmpeg -protocols | grep rtsp ffmpeg -formats | grep hls

逻辑说明:静态包不依赖系统库,适合生产环境。-protocols和-formats用来确认 RTSP 输入和 HLS 输出都被编译进去了,有些精简版 FFmpeg 会裁掉这两个模块,跑起来直接报「Protocol not found」。

参数说明:-version看版本号,建议 4.x 以上;如果grep rtsp没有输出,说明这个包不支持 RTSP,换一个完整版。Windows 下把bin目录加进系统环境变量即可,验证命令相同。

2.2 Nginx 安装与 RTMP/HLS 模块配置

Nginx 在这里干两件事:接收 FFmpeg 推过来的 HLS 切片,并通过 HTTP 把切片和 m3u8 索引暴露给前端。原生 Nginx 不带 RTMP 模块,但如果我们只用 HLS 输出,其实不需要 RTMP 模块,只要配置一个静态目录就行。

# Ubuntu 下安装 Nginx sudo apt update sudo apt install nginx -y # 创建 HLS 切片存放目录 sudo mkdir -p /var/www/hls sudo chmod 755 /var/www/hls

然后在/etc/nginx/conf.d/hls.conf里加一段:

server { listen 8080; server_name localhost; location /hls { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /var/www; add_header Cache-Control no-cache; add_header Access-Control-Allow-Origin *; } }

逻辑说明:location /hls把/var/www/hls目录映射到http://ip:8080/hls/。types块确保 m3u8 和 ts 文件返回正确的 MIME 类型,否则浏览器不认。Cache-Control no-cache防止前端拿到过期的切片列表,Access-Control-Allow-Origin *解决跨域,前端和后端不同端口时必加。

参数说明:listen 8080可以改成任意空闲端口;root /var/www配合location /hls实际指向/var/www/hls。改完执行sudo nginx -t测试配置,再sudo nginx -s reload生效。

2.3 SSM 架构包导入与基础配置

SSM 是 Spring + SpringMVC + MyBatis 的组合,负责业务逻辑,比如摄像头列表、用户权限、播放地址下发。导入架构包后,重点改三个地方:数据库连接、端口、静态资源映射。

<!-- applicationContext.xml 中的数据源配置 --> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/video_db?useSSL=false&amp;serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="your_password"/> </bean>

逻辑说明:数据源是 SSM 启动的根基,连不上数据库后面全白搭。serverTimezone=UTC不加的话 MySQL 8 会报时区错误。端口在server.xml或 Spring Boot 的application.properties里改,避免和 Nginx 的 8080 冲突,我一般把 SSM 放 8081。

参数说明:video_db换成你自己的库名;your_password换成真实密码。导入架构包后先跑一次单元测试,确认 MyBatis 能查出数据,再往下做流媒体对接。

3. FFmpeg 拉 RTSP 转 HLS:命令参数逐个拆解

3.1 最小可用转码命令与参数含义

先给一条能直接跑的命,再解释每个参数为什么这么设。

ffmpeg -rtsp_transport tcp -i "rtsp://admin:password@192.168.1.100:554/stream1" \ -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_segment_filename "/var/www/hls/stream_%03d.ts" \ /var/www/hls/stream.m3u8

逻辑说明:-rtsp_transport tcp强制用 TCP 拉流,UDP 在丢包网络下容易花屏。-c:v libx264把视频转成 H.264,HLS 只认这个编码。-preset ultrafast -tune zerolatency牺牲压缩率换低延迟,监控场景够用。-f hls指定输出格式,-hls_time 4每 4 秒切一个 ts 文件,-hls_list_size 6在 m3u8 里保留 6 个切片,约 24 秒窗口。

参数说明:-hls_time越小延迟越低,但切片请求越频繁,建议 2~6 秒之间。-hls_list_size 0表示保留所有切片,适合点播,直播别这么设。-hls_segment_filename里的%03d是序号占位符,必须和 m3u8 路径在同一目录。

3.2 低延迟与画质之间的取舍参数

延迟和画质天生矛盾,监控场景我一般优先保延迟。

参数低延迟取向高画质取向说明
-presetultrafastmedium越慢压缩率越高
-tunezerolatencyfilm低延迟关掉前瞻缓冲
-hls_time210切片时长
-g2×帧率10×帧率关键帧间隔,影响切片对齐
-b:v800k4000k视频码率

逻辑说明:-g是关键帧间隔,HLS 切片必须从关键帧开始,所以-g最好等于帧率乘以-hls_time。比如 25 帧、4 秒切片,-g设 100。不设的话 FFmpeg 会自己算,但切片边界可能对不齐,播放器切换时卡顿。

参数说明:-b:v根据摄像头分辨率和带宽调,1080p 建议 2000k~4000k,720p 用 1000k~2000k。带宽紧张就降码率,别降分辨率,分辨率降了车牌人脸全糊。

3.3 后台常驻运行与自动重连

前台跑 FFmpeg 一关终端就断,生产环境必须后台常驻,还要能自动重连。

# 用 nohup 后台运行,日志重定向 nohup ffmpeg -rtsp_transport tcp -i "rtsp://admin:password@192.168.1.100:554/stream1" \ -c:v libx264 -preset ultrafast -tune zerolatency -g 100 \ -c:a aac -ar 44100 -b:a 64k \ -f hls -hls_time 4 -hls_list_size 6 \ -hls_segment_filename "/var/www/hls/stream_%03d.ts" \ /var/www/hls/stream.m3u8 > /var/log/ffmpeg_stream.log 2>&1 &

逻辑说明:nohup ... &让进程脱离终端,日志写文件方便排查。但 FFmpeg 本身不会重连,RTSP 断了就退出。常见做法是写一个 shell 脚本,用while true循环检测进程,挂了就重启。

#!/bin/bash while true; do if ! pgrep -f "ffmpeg.*stream1" > /dev/null; then echo "$(date) 重启 FFmpeg" >> /var/log/ffmpeg_restart.log nohup ffmpeg -rtsp_transport tcp -i "rtsp://admin:password@192.168.1.100:554/stream1" \ -c:v libx264 -preset ultrafast -tune zerolatency -g 100 \ -c:a aac -ar 44100 -b:a 64k \ -f hls -hls_time 4 -hls_list_size 6 \ -hls_segment_filename "/var/www/hls/stream_%03d.ts" \ /var/www/hls/stream.m3u8 > /var/log/ffmpeg_stream.log 2>&1 & fi sleep 10 done

参数说明:pgrep -f匹配完整命令行,sleep 10每 10 秒检查一次。这个脚本用crontab开机启动,或者做成 systemd 服务更规范。

4. 前端 playerJQueryDemo 播放 HLS:从引入到自动播放

4.1 播放器选型与页面最小结构

浏览器原生<video>标签在 Chrome 上不支持 HLS,Safari 支持。所以要么用 hls.js 这类库做兼容,要么直接用支持 HLS 的播放器。playerJQueryDemo 一般是基于 jQuery 封装的播放器示例,核心还是<video>加 hls.js。

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>视频播放</title> <script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.0/dist/hls.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script> </head> <body> <video id="videoPlayer" controls autoplay muted style="width:800px;"></video> <script> var video = document.getElementById('videoPlayer'); var videoSrc = 'http://localhost:8080/hls/stream.m3u8'; if (Hls.isSupported()) { var hls = new Hls({ liveSyncDurationCount: 3, liveMaxLatencyDurationCount: 6 }); hls.loadSource(videoSrc); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function() { video.play(); }); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoSrc; video.addEventListener('loadedmetadata', function() { video.play(); }); } </script> </body> </html>

逻辑说明:Hls.isSupported()判断浏览器是否支持 MSE,Chrome、Firefox 走 hls.js 分支;Safari 原生支持 HLS,走canPlayType分支。liveSyncDurationCount: 3表示播放器保持落后直播 3 个切片,约 12 秒延迟,设太小容易卡顿。

参数说明:muted属性必须加,否则浏览器自动播放策略会拦截。autoplay配合muted才能自动起播。videoSrc换成你自己的 Nginx 地址和端口。

4.2 延迟优化与卡顿排查

HLS 天生有延迟,切片时长加播放器缓冲,通常 10~30 秒。想再低,调这几个地方。

调整项默认低延迟设置影响
hls_time42切片更短,请求更频繁
liveSyncDurationCount31播放点更靠近直播边缘
hls_list_size63索引更短,切换更快
maxBufferLength3010缓冲更少,抗抖动变差

逻辑说明:liveSyncDurationCount设 1 时播放器只落后一个切片,但网络一抖就卡。我一般设 2 或 3,平衡延迟和稳定。maxBufferLength在 hls.js 配置里设,控制最大缓冲秒数。

参数说明:这些参数没有万能值,内网环境可以激进一点,公网环境保守一点。改完用浏览器开发者工具的 Network 面板看 ts 请求间隔和加载耗时,超过 500ms 就要考虑降码率或加带宽。

4.3 多路视频同屏与资源占用控制

一个页面播多路流时,CPU 和内存会飙升,尤其是 hls.js 解码。

// 多路播放的简化封装 function createPlayer(videoId, src) { var video = document.getElementById(videoId); if (Hls.isSupported()) { var hls = new Hls({ liveSyncDurationCount: 3, maxBufferLength: 15, maxMaxBufferLength: 30 }); hls.loadSource(src); hls.attachMedia(video); hls.on(Hls.Events.ERROR, function(event, data) { if (data.fatal) { console.error('播放器致命错误:', data.type); hls.destroy(); setTimeout(function() { createPlayer(videoId, src); }, 3000); } }); } }

逻辑说明:maxBufferLength和maxMaxBufferLength限制单路缓冲,多路时防止内存爆掉。Hls.Events.ERROR里判断data.fatal,致命错误就销毁重建,3 秒后重试,这是最简单的自愈逻辑。

参数说明:同屏路数建议不超过 4 路 1080p,再多就上服务端转码降分辨率。hls.destroy()必须调用,否则旧实例不释放,内存泄漏。

5. 避坑与排查:这套链路最容易翻车的五个地方

5.1 坑一:m3u8 能下载但播放器一直转圈

现象:浏览器直接访问 m3u8 地址能下载文件,但播放器黑屏或一直缓冲。

原因:Nginx 返回的 MIME 类型不对,或者 ts 切片路径和 m3u8 里的相对路径不一致。m3u8 里写的是stream_001.ts,但实际文件在别的目录。

解决:检查 Nginx 配置里的types块,确保 m3u8 返回application/vnd.apple.mpegurl。用curl -I http://ip:8080/hls/stream.m3u8看 Content-Type。再确认-hls_segment_filename和 m3u8 输出路径在同一目录。

5.2 坑二:FFmpeg 跑几小时就断,日志报「Connection reset」

现象:FFmpeg 进程莫名退出,日志里 RTSP 连接被重置。

原因:摄像头 RTSP 服务有连接数或超时限制,长时间拉流会被踢。或者网络抖动导致 TCP 断开。

解决:用第 3.3 节的 while 循环脚本自动重启。更稳的做法是加-stimeout 5000000(微秒)设置 RTSP 超时,配合-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5让 FFmpeg 自己重连。

5.3 坑三:前端播放几秒就卡住,Network 里 ts 请求 404

现象:播放器起播几秒后卡死,控制台报 404。

原因:-hls_list_size设得太小,旧切片被删了,但播放器还在请求。或者 Nginx 目录权限不对,FFmpeg 写不进去。

解决:-hls_list_size至少设 3,低延迟场景也别低于 2。检查/var/www/hls目录权限,确保运行 FFmpeg 的用户有写权限,Nginx 用户有读权限。chmod 755目录,chmod 644文件。

5.4 坑四:Chrome 能播 Safari 不能播,或者反过来

现象:一个浏览器正常,另一个黑屏。

原因:Safari 原生支持 HLS,但要求 m3u8 的编码是 H.264 + AAC。如果 FFmpeg 输出的是 H.265 或 MP3 音频,Safari 不认。Chrome 走 hls.js,hls.js 对 H.265 支持也有限。

解决:FFmpeg 命令里强制-c:v libx264 -c:a aac,别用copy直接复制流,除非源流确认是 H.264+AAC。用ffprobe检查源流编码。

5.5 坑五:SSM 后端返回的播放地址带内网 IP,外网访问不了

现象:管理后台里播放地址是http://192.168.x.x:8080/hls/stream.m3u8,外网用户打不开。

原因:SSM 里拼接播放地址时用了服务器内网 IP,或者 Nginx 配置里server_name写死。

解决:播放地址用相对路径/hls/stream.m3u8,由前端根据当前域名拼完整 URL。或者 SSM 从请求头Host里取域名动态拼接。Nginx 的server_name改成_或实际域名。

6. 进阶技巧:用 ffprobe 做流健康检查与自动降码率

跑通之后,真正头疼的是稳定性。我现在的习惯是加一个健康检查脚本,用ffprobe定期探测流状态,发现码率异常或帧率掉零就自动切换低码率转码参数。

#!/bin/bash # 用 ffprobe 检测 RTSP 流是否存活 RTSP_URL="rtsp://admin:password@192.168.1.100:554/stream1" RESULT=$(ffprobe -v error -rtsp_transport tcp -select_streams v:0 \ -show_entries stream=codec_name,width,height,avg_frame_rate \ -of csv=p=0 -timeout 5000000 "$RTSP_URL" 2>&1) if echo "$RESULT" | grep -q "h264"; then echo "$(date) 流正常: $RESULT" >> /var/log/stream_health.log else echo "$(date) 流异常: $RESULT" >> /var/log/stream_health.log # 触发重启逻辑 pkill -f "ffmpeg.*stream1" fi

逻辑说明:ffprobe用和 FFmpeg 相同的协议参数拉流,只读流信息不解码,开销极小。-show_entries指定要输出的字段,-of csv方便脚本解析。-timeout 5000000是 5 秒超时,防止卡死。检测到异常就杀掉 FFmpeg 进程,外层 while 循环会自动重启。

参数说明:-select_streams v:0只选第一个视频流,音频流不关心。avg_frame_rate如果是0/0说明流已经挂了。这个脚本放 crontab 里每 30 秒跑一次,比单纯检测进程存活更可靠,因为进程可能在但流已经断了。

另一个实用技巧是动态降码率。当检测到网络带宽不足时,把 FFmpeg 的-b:v从 2000k 降到 800k,-s从 1920x1080 降到 1280x720。做法是准备两套启动脚本,健康检查发现连续三次 ts 请求超时就切换。这套逻辑我用在一个多路监控项目上,把原本每天断好几次的流稳定到了连续跑一周不用管。

最后说个血泪教训:别在 FFmpeg 命令里用-c copy偷懒。源流是 H.265 的时候,copy 出来的 HLS 在 Chrome 上根本播不了,排查了半天才发现是编码问题。老老实实转码,CPU 多花一点,省下的是半夜被叫起来修故障的时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询