简介:本资源面向Java后端初学者与流媒体实时预览需求者,提供一套基于SSM架构、Nginx与FFmpeg将RTSP流转换为HLS流并在前端HTML播放的完整可运行方案,适用于视频监控、在线教育、直播等场景的入门实践。压缩包共52个文件,约70.26MB,包含FFmpeg与Nginx的Windows安装包、SSM架构Java代码、nginx.conf配置、playerJQueryDemo播放器网页,以及png截图、css样式、js脚本、html页面和说明文档,覆盖从服务端转流到前端播放的完整链路。资源中附有转流命令、RTSP调试源与部署必读说明,便于对照搭建环境、理解FFmpeg转码与Nginx分发HLS的协作方式,并借助播放器插件快速验证效果。目前已有260人学习,适合希望亲手跑通RTSP转HLS并实现网页播放的读者参考。
1. 从 RTSP 到 HLS:为什么监控画面在浏览器里总是一片空白
很多做安防可视化或者物联网平台的朋友都遇到过这个场景:手头有一台海康威视或者臻识科技的摄像头,RTSP 拉流地址在 VLC 里跑得好好的,但一到前端 HTML 页面上就抓瞎——浏览器原生根本不认rtsp://协议。这不是代码写错了,而是浏览器厂商出于安全和插件生态的考虑,早就把 RTSP 支持从内核里踢出去了。于是「RTSP 转 HLS」成了国内视频监控 Web 化最稳、最省成本的一条落地路径。
这套方案的核心思路很直接:用 FFmpeg 把摄像头的 RTSP 流实时切成 HLS 切片(.m3u8+.ts),Nginx 负责把这些静态切片以 HTTP 方式高效吐给浏览器,SSM(Spring + SpringMVC + MyBatis)架构负责管理摄像头配置、通道信息和切片任务的生命周期,前端用 playerJQueryDemo 这类基于 video.js 或 hls.js 的播放器加载播放。它适合谁?适合那些不想引入 WebRTC 或 SRS 等重型流媒体服务器、只想在现有 Java Web 项目里快速把监控画面塞进页面的中小团队。整套东西不依赖云服务,本地就能跑通,硬件成本几乎为零。
2. 拆解 RTSP 转 HLS 的链路:FFmpeg 切片、Nginx 分发、SSM 调度各管什么
2.1 为什么选 HLS 而不是 RTSP 转 FLV 或 WebRTC
在动手之前,先把选型逻辑理清楚。RTSP 转 FLV 确实延迟更低,但依赖 flv.js 且对 H.265 支持很差;RTSP 转 WebRTC 延迟能压到 500ms 以内,但需要信令服务器和 STUN/TURN,架构复杂度直接翻倍。HLS 的优势在于:基于 HTTP,Nginx 天然就是干这个的;切片是静态文件,SSM 只需要管理文件目录和进程;浏览器兼容性最好,安卓端用 Media3 ExoPlayer 播放 HLS 也是原生支持。
代价是延迟。HLS 的延迟由切片时长和播放列表长度决定,默认配置下 6 到 10 秒是常态。如果你的场景是实时操控(比如云台控制),这个延迟不可接受;但如果是常规监控预览、回放,完全够用。我一般会跟团队说:先想清楚业务能容忍多少延迟,再决定要不要上 HLS。
2.2 FFmpeg 切片命令的每个参数都要能解释清楚
FFmpeg 是整个链路里最容易翻车的一环。下面这条命令是我在 Windows 和 Linux 上都验证过的稳定版本:
ffmpeg -rtsp_transport tcp -i "rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101" \ -c:v libx264 -preset ultrafast -tune zerolatency \ -c:a aac -ar 44100 -ac 1 \ -f hls -hls_time 4 -hls_list_size 6 \ -hls_flags delete_segments+append_list \ -hls_segment_filename "/usr/local/nginx/html/hls/cam01_%03d.ts" \ /usr/local/nginx/html/hls/cam01.m3u8逐段说明:-rtsp_transport tcp强制走 TCP,避免 UDP 丢包导致的花屏,这是海康、大华摄像头接入时的血泪经验;-preset ultrafast -tune zerolatency牺牲压缩率换编码速度,防止切片生成跟不上;-hls_time 4表示每个 TS 切片 4 秒,配合-hls_list_size 6意味着播放列表里保留 6 个切片,理论延迟约 24 秒,实际播放器会追到最新切片,体感延迟 8 到 12 秒;delete_segments自动清理旧切片,否则磁盘几天就满。
提示:如果摄像头是 H.265 编码,
-c:v libx264会触发转码,CPU 占用飙升。建议在摄像头后台把子码流改成 H.264,或者用-c:v copy直接复制流,但前提是浏览器能解 H.265——目前 Chrome 对 H.265 的 HLS 支持仍然不完整。
2.3 Nginx 配置:让 m3u8 和 ts 文件被正确识别与缓存
Nginx 在这里的角色是静态文件服务器,但有两个坑必须提前填。第一,MIME 类型。默认 Nginx 的mime.types里没有.m3u8和.ts,浏览器拿到application/octet-stream会拒绝播放。第二,CORS。如果前端页面和 HLS 文件不同源,必须加跨域头。
server { listen 8080; server_name localhost; location /hls/ { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /usr/local/nginx/html; add_header Cache-Control no-cache; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, OPTIONS'; } }Cache-Control no-cache对 m3u8 是必须的,否则浏览器会缓存播放列表导致画面卡住不更新;ts 切片可以设长缓存,但为了简单统一 no-cache 也没问题。Access-Control-Allow-Origin *在生产环境建议改成具体域名。
2.4 SSM 层要管的三件事:通道注册、进程守护、切片清理
SSM 架构在这里不是打酱油的。它至少承担三个职责:第一,摄像头通道的增删改查,把 RTSP 地址、用户名密码、HLS 输出路径存到 MySQL;第二,FFmpeg 进程的启动与守护,通过 Java 的ProcessBuilder拉起命令,并定时检查进程是否存活;第三,切片目录的定期清理,防止磁盘写满。
// 启动 FFmpeg 进程的核心逻辑 public Process startFfmpeg(String rtspUrl, String outputPath) throws IOException { List<String> command = new ArrayList<>(); command.add("ffmpeg"); command.add("-rtsp_transport"); command.add("tcp"); command.add("-i"); command.add(rtspUrl); command.add("-c:v"); command.add("libx264"); command.add("-preset"); command.add("ultrafast"); command.add("-tune"); command.add("zerolatency"); command.add("-f"); command.add("hls"); command.add("-hls_time"); command.add("4"); command.add("-hls_list_size"); command.add("6"); command.add("-hls_flags"); command.add("delete_segments"); command.add(outputPath); ProcessBuilder pb = new ProcessBuilder(command); pb.redirectErrorStream(true); pb.redirectOutput(ProcessBuilder.Redirect.appendTo(new File(outputPath + ".log"))); return pb.start(); }这段代码的关键点:redirectErrorStream(true)把 FFmpeg 的 stderr 合并到 stdout,方便排查报错;日志追加到独立文件,出问题时能回溯。进程守护可以用 Spring 的@Scheduled每 30 秒检查一次process.isAlive(),不存活就重启。
3. 从零搭一套可播放的环境:安装包选择、目录规划与最小验证
3.1 FFmpeg 安装包怎么选:Windows 用 essentials,Linux 用包管理
Windows 下直接去 FFmpeg 官网下载ffmpeg-master-latest-win64-gpl.zip,解压后把bin目录加到系统 PATH。注意不要下essentials版本,那个缺少某些编码器。Linux 下更简单,Ubuntu/Debian 用apt install ffmpeg,AlmaLinux 9 需要先启用 EPEL 和 RPM Fusion:
# AlmaLinux 9 安装 FFmpeg sudo dnf install -y epel-release sudo dnf install -y --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-9.noarch.rpm sudo dnf install -y ffmpeg ffmpeg-devel安装完用ffmpeg -version验证,能看到libx264和aac就说明编码器齐全。
3.2 Nginx 安装:Windows 直接解压,Linux 编译或包管理
Windows 版 Nginx 从官网下 zip 包,解压到C:\nginx,双击nginx.exe启动。Linux 下如果只是做静态文件服务,apt install nginx或dnf install nginx足够;需要额外模块才编译安装。安装后把上面 2.3 节的location /hls/配置加到nginx.conf的server块里,nginx -s reload生效。
3.3 目录规划与最小验证:先让一个切片文件能被浏览器播出来
在 Nginx 的 html 目录下建hls文件夹,手动放一个测试用的test.m3u8和对应的test0.ts,然后用浏览器访问http://localhost:8080/hls/test.m3u8。如果浏览器直接下载文件而不是播放,说明 MIME 类型没配好;如果 404,检查root路径。这一步过了,再跑 FFmpeg 命令生成真实切片。
<!-- playerJQueryDemo 最小播放页面 --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>HLS 播放测试</title> <link href="https://vjs.zencdn.net/8.6.1/video-js.css" rel="stylesheet"> </head> <body> <video id="my-video" class="video-js" controls preload="auto" width="800" height="450"> <source src="http://localhost:8080/hls/cam01.m3u8" type="application/x-mpegURL"> </video> <script src="https://vjs.zencdn.net/8.6.1/video.min.js"></script> <script> var player = videojs('my-video'); player.play(); </script> </body> </html>video.js 会自动检测浏览器是否原生支持 HLS,不支持则加载 hls.js。如果页面黑屏但控制台无报错,大概率是切片还没生成完,等 10 秒再刷新。
4. 避坑与排查:RTSP 转 HLS 最常见的 5 个翻车现场
4.1 现象:FFmpeg 启动后立刻退出,日志显示 “Connection refused”
原因:RTSP 地址错误或摄像头未开启对应通道。海康的 RTSP 地址格式是rtsp://用户名:密码@IP:554/Streaming/Channels/101,其中101表示通道 1 主码流,102是子码流。臻识科技的车牌相机地址格式不同,通常是rtsp://IP:554/stream1。解决:先用 VLC 的「打开网络串流」测试地址,确认能播再交给 FFmpeg。
4.2 现象:切片文件生成了,但浏览器播放几秒就卡住
原因:-hls_list_size设置过小,播放器还没加载完旧切片就被删了。解决:把-hls_list_size调到 6 以上,或者加-hls_flags append_list让播放列表追加而不是覆盖。另外检查 Nginx 的Cache-Control,m3u8 必须 no-cache。
4.3 现象:CPU 占用 100%,画面严重卡顿
原因:摄像头输出 H.265,FFmpeg 在做软解转 H.264。解决:登录摄像头后台,把视频编码改成 H.264;如果必须用 H.265,考虑用支持硬件加速的 FFmpeg 编译版本,加-hwaccel cuda或-hwaccel vaapi。
4.4 现象:多个摄像头同时转流时,部分通道切片断流
原因:FFmpeg 进程数过多,CPU 或网络带宽瓶颈。解决:限制并发转流路数,或者把-preset从ultrafast改成veryfast降低 CPU 占用;网络方面,RTSP 走 TCP 时每路流约占用 2 到 4 Mbps,千兆网卡理论上限约 200 路,但实际建议不超过 50 路。
4.5 现象:Linux 下 FFmpeg 进程变成僵尸进程,SSM 无法重启
原因:Java 的ProcessBuilder启动的进程在父进程异常退出后没有被回收。解决:在 SSM 里用process.destroyForcibly()强制销毁,并注册Runtime.getRuntime().addShutdownHook()在应用关闭时清理所有 FFmpeg 进程。
5. 进阶技巧:用 SSM 定时任务做切片健康检查与自动恢复
前面讲的都是「能跑」,这一章讲「跑得稳」。生产环境里,FFmpeg 进程可能因为网络抖动、摄像头重启、磁盘满等原因挂掉,靠人工重启不现实。我的做法是在 SSM 里写一个定时任务,每 30 秒扫描一次所有通道的 m3u8 文件最后修改时间,如果超过 30 秒没更新,就判定为断流,自动杀掉旧进程并重启。
@Scheduled(fixedDelay = 30000) public void checkHlsHealth() { List<CameraChannel> channels = channelMapper.selectAllActive(); for (CameraChannel ch : channels) { File m3u8 = new File(ch.getOutputPath()); if (!m3u8.exists() || System.currentTimeMillis() - m3u8.lastModified() > 30000) { log.warn("通道 {} 切片超时未更新,准备重启", ch.getId()); ffmpegService.restart(ch); } } }这个逻辑的关键参数是 30000 毫秒的阈值。设太小会误判——比如摄像头关键帧间隔是 4 秒,切片生成有波动;设太大则恢复慢。我一般设成hls_time * 5,即 4 秒切片对应 20 秒阈值,留一点余量。
另一个技巧是给每个通道加一个「心跳文件」。FFmpeg 命令里加-hls_flags delete_segments+temp_file,让切片先写临时文件再原子重命名,避免播放器读到写了一半的文件。同时 SSM 在启动 FFmpeg 时记录进程 PID 到数据库,重启时先根据 PID 杀旧进程,防止端口和文件句柄泄漏。
验证方法很简单:手动kill掉一个 FFmpeg 进程,观察 30 秒内 SSM 日志是否打印重启记录,浏览器画面是否在 1 分钟内恢复。如果恢复不了,检查restart方法里有没有先调用destroyForcibly再waitFor,顺序错了会卡死。
最后说个我自己的习惯:每次上线新摄像头,先用 FFmpeg 命令行手动跑 10 分钟,盯着日志看有没有Non-monotonous DTS或Packet corrupt的警告。这些警告在测试时不起眼,但上线后就是画面卡顿的元凶。希望帮到你。
本文还有配套的精品资源,点击获取