ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

EasyDSS Windows安装包部署与内网流媒体排错实战

EasyDSS Windows安装包部署与内网流媒体排错实战

简介:EasyDSS Windows 版 2.2.7 安装包,面向需要在本地快速搭建流媒体视频管理与分发环境的企业运维、开发者及个人站长。它将视频上传、转码、直播推流、点播回放、网页嵌入与分享、多屏适配等功能整合为可直接部署的服务端方案,并提供 API 与 SDK 便于对接现有业务系统,适合在线教育、会议直播、企业宣传等场景的部署验证。压缩包共 207 个文件、约 55.93MB,包含可执行程序、动态链接库、JavaScript 与样式文件构成的前端资源、Lua 业务脚本,以及页面模板、字体图标、说明文档和配置文件,覆盖从安装运行到界面访问的主要组件。已有 708 人学习下载。部署后可按需调整服务参数,借助运行库与前端资源定位环境依赖、页面加载等问题;二次开发时也能对照目录结构快速找到接口与静态资源,降低从零搭建流媒体服务的试错成本。

1. 一个 2019 年的 Windows 安装包,为什么现在还值得拆

机房角落挂着六路 RTSP 摄像头,业务方的需求很朴素:不接公有云,内网浏览器打开就能看,能回放最近三天。这种场景我一般不上重型平台,而是找一个能独立跑的流媒体服务,这份 EasyDSS 的 Windows 安装包(2.2.7,构建时间戳 1912281435)就是常翻出来的那个。它把收流、分发、录像回放和一整套视频管理页面打成一个可执行服务,装完注册成系统服务就能跑,不用额外配数据库和中间件。适合需要在内网快速搭一套「可管可看」视频服务的运维和集成开发者;不适合要做集群和智能分析的场景,单机版的边界摆在那里。

2. 拆包与落地:2.2.7 安装包里到底装了什么

2.1 解压之后先看清单,别急着双击

拿到 zip 的第一动作不是双击里面的 exe。用 7-Zip 打开看一层清单,能提前判断三件事:这是安装器还是绿色目录、有没有捆运行库、配置文件是明文还是编译进 exe。这三件事直接决定后面能不能改端口、能不能整体迁移目录。

常见条目作用提前要看什么
setup.exe / *.msi释放文件并注册服务版本信息里是否写着 2.2.7
config 或 *.ini端口、录像路径、授权信息明文则可直接改,加密只能走页面
www 或 web 目录前端静态资源决定页面标题、Logo 能否替换
ffmpeg.exe转码与切片子进程缺了它会出现「推流成功但不出切片」
logs 目录运行日志排错第一入口,比页面报错详细

具体文件名以你解开的包为准,不同打包批次会挪位置。如果包里只有一个安装器,那就先装到默认路径,装完立刻去目标目录再看一遍——那时候看到的才是真正要读的目录结构。这一步花三分钟,能省掉后面半小时的瞎猜。

2.2 安装路径与服务注册:为什么不建议装进 Program Files

两个原因。第一是权限:Windows 服务默认以 LocalSystem 身份运行,往 Program Files 子目录写文件,在部分系统上会被 UAC 虚拟化重定向到 VirtualStore,日志和录像「看起来写了其实没写」,重启后一片空白。第二是路径解析:服务端调 ffmpeg 时通常把路径拼进命令行,带空格要加引号,带中文在某些代码页下会变成问号,子进程直接起不来。

血泪经验是路径越短越好:D:\EasyDSS这种纯英文、无空格、层级浅的目录,能挡掉一半玄学问题。录像目录再单独挂一块盘,别和程序、系统盘挤在一起。

# 目录规划:程序、录像、日志分开,录像单独一块盘 $root = "D:\EasyDSS" # 程序目录,纯英文无空格 $rec = "E:\rec" # 录像目录,独立磁盘,避免写满拖垮系统盘 $log = "D:\EasyDSS\logs" New-Item -ItemType Directory -Force -Path $root, $rec, $log | Out-Null # 装完后确认服务是否注册成功,服务名以安装器实际注册的为准 Get-Service | Where-Object { $_.Name -like "*DSS*" } | Format-Table Name, Status, StartType # 手动拉起并设为开机自启,断电恢复后不用人肉登录 Start-Service -Name "EasyDSS" Set-Service -Name "EasyDSS" -StartupType Automatic

New-Item -Force是幂等的,重复执行不报错,适合写进初始化脚本。Get-Service加通配符过滤,是为了不靠猜服务名——很多安装器注册的名字和你以为的不一样。StartupType Automatic必须设,默认安装有时是手动启动,机器重启后页面打不开,人被叫起来查半天,最后发现服务根本没起。

还有一点常被忽略:服务以 LocalSystem 跑,它对E:\rec的写权限是有的,但如果你把录像目录指到某个网络映射盘(比如Z:\),LocalSystem 看不到用户会话里的映射盘符,必须写 UNC 路径并给机器账户授权。这一条踩过一次就够记一辈子。

2.3 端口核对与首次登录:先测通再谈配置

文档写的默认端口只能当参考,真正靠谱的是看进程实际监听了什么。装完先干这件事,后面配防火墙、配推流地址都以此为准。

# 找到服务进程,列出它实际监听的端口,比翻文档可靠 $p = Get-Process | Where-Object { $_.Path -like "*EasyDSS*" } Get-NetTCPConnection -State Listen | Where-Object { $_.OwningProcess -in $p.Id } | Select-Object LocalAddress, LocalPort, OwningProcess | Sort-Object LocalPort # 本机连通性自测,端口换成上一步查到的实际值 Test-NetConnection -ComputerName 127.0.0.1 -Port 10080

Get-NetTCPConnection需要 PowerShell 3.0 以上,老系统上用netstat -ano | findstr LISTENING替代。LocalAddress是0.0.0.0说明对外监听,是127.0.0.1说明只绑了本机,外网访问不通就是这里的问题,不用去查防火墙。

常见端口大致是这样,装完务必和上一步的输出对一遍:

用途常见默认端口协议备注
Web 视频管理页面10080TCP首次登录入口
RTMP 收流1935TCP推流地址里的端口
HTTP-FLV / WS-FLV8081TCP部分版本与 Web 合并
RTSP 分发10554TCP/UDP老设备取流常用

登录后先做三件事:改掉默认口令、核对页面上的版本号是不是 2.2.7、把录像目录指到上一步建好的E:\rec。版本号对不上,说明包和手头文档不是一批,后面所有参数都别照抄,以页面实际字段为准。

内网部署还要开防火墙,但别一把梭 Any:

# 只放行内网网段,公网入口一律不给 New-NetFirewallRule -DisplayName "EasyDSS-Web" -Direction Inbound ` -Protocol TCP -LocalPort 10080 -RemoteAddress 192.168.10.0/24 -Action Allow New-NetFirewallRule -DisplayName "EasyDSS-RTMP" -Direction Inbound ` -Protocol TCP -LocalPort 1935 -RemoteAddress 192.168.10.0/24 -Action Allow

-RemoteAddress写网段比写单 IP 好维护,摄像头和办公网各占一个段就写两条规则。规则名带前缀,以后要批量删或者排查冲突,Get-NetFirewallRule -DisplayName "EasyDSS-*"一把就能捞出来。

3. 接一路视频进来:推流地址拼法与 ffmpeg 验证

3.1 先建通道再拿地址:推流地址的三段结构

推流地址不是随手拼的,标准形态是rtmp://{host}:{port}/{app}/{streamId}。这三段任何一段错,表现都是「推流端报错」或者「推流成功但播放端黑屏」,所以别手抄,在视频管理页面里建好通道之后直接复制。

段典型取值常见错误
host:port192.168.10.20:1935写成 127.0.0.1,摄像头推不过来
applive大小写敏感,写成 Live 可能匹配不上
streamIdcam01、test01用中文或空格,URL 编码后对不上

streamId只用字母、数字、下划线、短横线,这条规则能躲掉绝大部分「地址明明一样就是推不上」的问题。通道建完之后页面会给出一组地址:一个推流地址、几个播放地址(HLS、HTTP-FLV、RTSP)。把这组地址抄进配置表,比自己拼靠谱。

3.2 ffmpeg 模拟推流:没有摄像头也能验证整条链路

现场调试最常见的困境是摄像头还没装、网络还没通,但你得先证明服务本身是好的。用合成画面推一路进去,能把「收流—切片—分发—播放」整条链路一次性验完。

# 合成测试画面 + 正弦音,推一路到服务端,完全不需要真实摄像头 ffmpeg -re \ -f lavfi -i testsrc2=size=1280x720:rate=25 \ -f lavfi -i sine=frequency=1000:sample_rate=44100 \ -c:v libx264 -preset veryfast -tune zerolatency \ -g 50 -keyint_min 50 -sc_threshold 0 \ -b:v 1200k -maxrate 1200k -bufsize 2400k \ -c:a aac -b:a 64k -ar 44100 -ac 1 \ -f flv "rtmp://192.168.10.20:1935/live/test01"

-re让 ffmpeg 按真实帧率推,不加会全速灌进去,测不出卡顿。-g 50配合 25fps 就是 2 秒一个关键帧,这个数字后面配 HLS 切片时长要用到,两者必须整除。-sc_threshold 0关掉场景切换自动插关键帧,否则切片时长会飘,回放列表里出现一堆 0.3 秒的碎片。-bufsize一般取maxrate的两倍,太小码率上不去,太大起播慢。

推上去之后立刻做三件事:页面通道列表里看有没有在线、拉一路 HLS 在浏览器里播、再拉一路 HTTP-FLV 比对延迟。三个都过,服务端就没问题,后面出问题一定在摄像头侧或者网络侧。

真实摄像头转推的写法不同,能 copy 就别重编码:

# 把一路 RTSP 摄像头转推成 RTMP,-c copy 不转码,CPU 占用几乎为零 ffmpeg -rtsp_transport tcp \ -i "rtsp://user:pass@192.168.10.31:554/Streaming/Channels/101" \ -c copy -f flv "rtmp://192.168.10.20:1935/live/cam01"

-rtsp_transport tcp必加,UDP 在跨交换机、丢包稍高的链路上会花屏,TCP 稳一点但延迟略高,内网监控场景这点延迟可以接受。-c copy的前提是摄像头出的是 H.264;如果是 H.265,多数浏览器播不了,必须换成-c:v libx264 -preset veryfast重编码,这时 CPU 成本要按路数重新算——一台四核机器软编 1080p25 大概能扛 4 到 6 路,再多就得考虑硬编或者直接让摄像头出 H.264 子码流。

常驻运行别挂在命令行窗口里,用计划任务或者做成服务。我一般写一个带重连的批处理:ffmpeg 退出就等 5 秒重进,避免摄像头重启一次就得人工去拉。

3.3 HLS 与 HTTP-FLV:延迟、兼容、并发怎么取舍

同一路流用哪种协议分发,取决于你更在意什么:

协议延迟量级浏览器兼容适用场景
HLS6~20 秒最好,移动端原生支持大屏轮播、非实时监看
HTTP-FLV1~3 秒需播放器支持,桌面端友好值班室实时监看
RTSP1 秒内浏览器不直接支持二次转发给 NVR 或算法盒子

HLS 的延迟主要由切片时长决定。切片设 2 秒,播放器默认缓冲 3 个切片,光学协议层就 6 秒起步,再加上编码器的 GOP 对齐,实测十几秒很常见。想压下去就三条:切片时长调到 1~2 秒、GOP 严格等于切片时长乘以帧率、播放器缓冲个数调到 2 以内。三条缺一条,延迟照样下不来,这是最容易翻车的地方——只改切片不改 GOP,切出来的片段长度不齐,播放器反而更卡。

HTTP-FLV 延迟低,但它是一条长连接,中间只要挂了反向代理,就必须关掉代理缓冲(nginx 里是proxy_buffering off),否则数据攒在代理层,延迟比 HLS 还夸张。这条坑我在一次内网演示里踩过,页面转圈十几秒,最后发现是代理默认开了缓冲。

4. 录像与磁盘:三个必须先算的数和一个水位脚本

4.1 录像落盘结构怎么读

录像不是一个越滚越大的单文件,而是按通道、按天、按切片组织的一堆小文件。这个结构的好处是检索快、坏一个不影响全部;坏处是断电时最后一个文件没有索引,播放器拖不动进度条。

路径形态内容排错信号
通道目录每路一个,名字对应 streamId目录不生成说明录像没开
日期目录按天分桶日期跳空说明当天服务停过
切片文件单文件几分钟大小异常小说明码率或网络有问题

抽查一个文件是否完整,用 ffprobe 最快:

# 抽查最近写入的几个录像文件,能读出时长说明索引完整 ls -lt "E:/rec/cam01"/*/*.mp4 2>/dev/null | head -n 5 ffprobe -v error -show_entries format=duration,size \ -of default=noprint_wrappers=1 "E:/rec/cam01/20250101/120000.mp4"

第一条命令列出最近修改的五个文件,按时间倒序,出问题的通常是最后那几个。第二条能正常输出duration就说明 moov 索引在;报moov atom not found就是写坏了,只能丢。这也解释了为什么服务器必须配 UPS——不是矫情,是录像文件的索引真的经不起硬断电。

4.2 三个必须提前算的数

上线前不算清楚,磁盘一定在某个凌晨写满。三个数:单路每小时容量、单路每天容量、总需求容量。

单路每天容量(GB)≈ 码率(Mbps)× 3600 × 24 ÷ 8 ÷ 1024 ≈ 码率 × 10.55。

单路码率单路每天8 路 30 天建议磁盘
1 Mbps10.5 GB2.5 TB4 TB
2 Mbps21 GB5.0 TB8 TB
4 Mbps42 GB10.1 TB16 TB

最后留 20% 水位,磁盘写满之后不只是录像停,服务的日志、切片临时文件也会失败,页面直接打不开。上面这张表按 7×24 连续录算,如果只在工作时间录,乘个系数就行。

4.3 清理策略与水位监控

平台一般自带按天清理,把「保留天数」设好就不用管。但自带清理只删自己认识的文件,遇到进程崩溃留下的临时文件、手动导出的碎片,它不认。所以还得有一个外部的水位检查。

# 每天定时跑一次:录像盘剩余低于 20% 就写告警日志 $rec = "E:\rec" $drive = (Split-Path $rec -Qualifier).TrimEnd(':') $d = Get-PSDrive -Name $drive $freePct = [math]::Round($d.Free / ($d.Used + $d.Free) * 100, 1) if ($freePct -lt 20) { $msg = "{0:yyyy-MM-dd HH:mm} WARN {1} free {2}%" -f (Get-Date), $rec, $freePct Add-Content -Path "D:\EasyDSS\logs\disk-check.log" -Value $msg }

Split-Path -Qualifier从路径里取出盘符,TrimEnd(':')去掉冒号才能喂给Get-PSDrive。写日志而不是弹窗,是因为这脚本跑在计划任务里,没人看窗口。日志文件定期清,别让监控脚本自己把盘写满——这事听起来好笑,但确实发生过。

注意:不要用脚本直接删正在写的切片文件。删除动作和写入动作撞上,会导致当前这段录像损坏,且平台侧索引和实际文件对不上,页面列表里出现点进去播不了的条目。

5. 排查清单:五个把演示拖到通宵的坑

5.1 装完浏览器打不开 10080

现象:服务显示 Running,本机Test-NetConnection通,同事的机器打不开。

原因:一是服务只绑了127.0.0.1,二是 Windows 防火墙拦了入站,三是最常见的——安装时选的是「仅本机」,配置里监听地址没放开。

解决:先看Get-NetTCPConnection的LocalAddress,是回环地址就改配置里的监听地址为0.0.0.0再重启服务;是0.0.0.0还打不开,查防火墙规则有没有落到正确的网络配置文件(域/专用/公用三套规则是分开的,笔记本换了网络环境就会切)。

5.2 推流端返回成功,播放端黑屏

现象:ffmpeg 不报错,进度条在走,页面通道显示在线,点播放一直转圈。

原因:九成是编码格式不匹配。摄像头出 H.265,-c copy原样推进去,服务端收下了,但浏览器解不了。剩下一成是音频采样率或者声道数异常,导致整个封装失败。

解决:先ffprobe看源的实际编码,是hevc就改成-c:v libx264重编码再推;音频统一-c:a aac -ar 44100 -ac 1。判断方法很直接:用同样的地址推一路测试画面,测试画面能播就是源的问题,不能播就是服务端或网络的问题。

5.3 HLS 延迟十几秒,怎么调都下不来

现象:切片时长改成 1 秒,延迟还是 12 秒以上。

原因:只改了切片,没改 GOP。切片时长和关键帧间隔不整除,编码器切出来的片段被迫拉长对齐,实际切片远大于设定值。另一种情况是播放器缓冲个数没动,默认还是 3 个。

解决:编码端-g设成「帧率 × 切片时长」,并加-sc_threshold 0禁掉场景切换插帧;播放端把缓冲个数调到 2。两边都改完再测,只改一边等于没改。

5.4 重启之后配置回滚,或者服务起不来

现象:页面上改的端口、录像路径,重启后全变回默认;严重一点直接起不来。

原因:改配置时服务还在跑,退出时把内存里的旧配置覆盖回文件;或者配置目录没有写权限,改动只存在内存里。服务起不来则多半是录像路径指向了不存在的盘符。

解决:改配置前先Stop-Service,改完再Start-Service;确认配置文件所在目录对 LocalSystem 可写;录像路径先用Test-Path验一下再填。养成习惯之后,这类问题基本绝迹。

5.5 录像目录越来越大,页面列表里却没有

现象:磁盘占用涨得很快,但回放页面只显示零星几条。

原因:早期版本崩溃或者强杀进程之后,切片文件写了但索引没写,成了孤儿文件。平台只认索引,不认目录。

解决:定期比对目录文件数和页面条目数,差得多就说明有孤儿。处理方式是停服务、把孤儿目录挪到备份盘、重启,别直接删——万一后面要取证还得用。根治办法是给机器配 UPS,并且用Stop-Service而不是任务管理器结束进程。

6. 上线自检:一个 30 行脚本卡住 80% 的低级故障

演示环境最怕的不是复杂问题,是端口没开、首页 502、录像盘满了这种低级故障,而且往往在领导站在身后的时候才暴露。我现在的习惯是每次交付前跑一遍自检脚本,三十行,覆盖四件事:三个关键端口通不通、HTTP 首页返回码、录像盘水位、进程在不在。

# 交付前自检:端口连通、首页状态、录像盘水位 import socket, shutil, urllib.request HOST = "192.168.10.20" PORTS = {"web": 10080, "rtmp": 1935, "flv": 8081} def port_ok(port, timeout=2): # connect_ex 返回 0 表示三次握手成功,不抛异常 with socket.socket() as s: s.settimeout(timeout) return s.connect_ex((HOST, port)) == 0 for name, port in PORTS.items(): print(f"{name:5s} {port:>5d} {'OK' if port_ok(port) else 'FAIL'}") try: code = urllib.request.urlopen(f"http://{HOST}:10080/", timeout=3).status print(f"http {code}") except Exception as e: print(f"http FAIL {e}") total, used, free = shutil.disk_usage("E:/rec") print(f"rec free {free / total * 100:.1f}%")

几个参数值得说。timeout=2是有意压短的,内网端口探测超过两秒基本就是不通,等下去没意义。connect_ex不抛异常只返错误码,比connect加 try 干净。disk_usage返回的三个值单位是字节,算百分比就行。脚本跑完看输出:三个 OK 加 200 加水位大于 20%,才允许交付;任何一项 FAIL,先解决再谈演示。

进阶一点的做法是把输出直接写进交付记录,和版本号、端口表放在一起。下次出问题,先翻这份记录,能立刻判断是环境变了还是配置被改了。我吃过一次亏:某次演示前一小时换了台机器,端口全按老记录配的,结果新机器的 1935 被另一个服务占了,推流一直失败,人慌着手抖,愣是查了四十分钟。从那以后我每次交付前都强制走一遍这个脚本,宁可多花三分钟,也不在别人面前表演手忙脚乱。

安装包本身没什么神秘的,难的是路径、端口、编码这三件事在动手之前就想清楚。想少走弯路,拿到包之后先按第二节把目录和端口定下来,再按第三节推一路测试流验通,剩下的都是参数活。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进