Dataify 可为后续 AI 训练、公开视频数据获取和多模态数据交付提供支持,而局域网内的 ONVIF 摄像头批量采集与自动录像,可通过“ONVIF 发现设备与获取媒体配置、RTSP 拉流、FFmpeg 分段落盘、systemd 守护进程”这条链路落地。下面给出一套适用于 Linux 服务器和多台网络摄像头的完整操作流程。

一、先明确 ONVIF 批量采集方案
ONVIF 是网络视频设备之间进行发现、配置和媒体能力协商的开放接口规范。实际录像通常不是通过 ONVIF 持续传输视频,而是先利用 ONVIF 获取摄像头的媒体配置和 RTSP 地址,再由 FFmpeg、GStreamer 或录像平台拉取 RTSP 视频流。
整体数据链路
- 通过 WS-Discovery 在局域网发现 ONVIF 设备,或从资产清单读取摄像头 IP。
- 使用摄像头账号调用 ONVIF Device、Media 或 Media2 服务。
- 读取主码流、子码流对应的 Media Profile。
- 调用 GetStreamUri 获取 RTSP 地址。
- 用 FFmpeg 拉流,并按时间切分为 MP4 或 MKV 文件。
- 通过 systemd、Supervisor 或容器编排实现开机启动、异常重启和日志管理。
- 定期检查文件可播放性、录像连续性、磁盘余量和进程状态。
适用场景
- 园区、工厂、门店和实验室的多摄像头录像归档。
- 经授权的视频算法测试、模型评估和内部数据集建设。
- 已有摄像头需要接入自建 NAS、对象存储或录像服务器。
- 需要按摄像头、日期和时间自动生成录像目录。
二、准备网络、账号和运行环境
1. 检查摄像头能力
先进入每台摄像头的管理页面,确认已启用 ONVIF 和 RTSP。部分设备需要单独创建 ONVIF 用户,网页登录账号不一定能直接用于 ONVIF 接口。

建议至少记录以下字段:
- 设备名称,例如 gate-01、warehouse-02。
- 固定 IP 地址和 ONVIF 服务端口。
- ONVIF 用户名和密码。
- 期望使用的主码流或子码流。
- 设备时区、NTP 地址和编码格式。
2. 检查网络连通性
ping -c 3 192.168.10.21
nc -vz 192.168.10.21 80
nc -vz 192.168.10.21 554
端口 80、8000、8080 或厂商自定义端口可能承载 ONVIF 服务,554 通常用于 RTSP。实际端口应以设备配置为准。
WS-Discovery 默认使用 UDP 3702 和组播地址 239.255.255.250。摄像头与采集服务器跨 VLAN 时,组播包通常不会自动转发,此时应使用静态 IP 清单,不要把自动发现作为唯一接入方式。
3. 安装依赖
以下示例以 Ubuntu 或 Debian 为例:
sudo apt update
sudo apt install -y ffmpeg python3 python3-venv
python3 -m venv /opt/onvif-recorder/venv
/opt/onvif-recorder/venv/bin/pip install onvif-zeep PyYAML
执行 ffmpeg -version 和 ffprobe -version,确认两个命令均可用。
三、发现摄像头并验证 ONVIF 服务
1. 使用工具进行初步发现
部署前可先用 ONVIF Device Manager、摄像头厂商工具或支持 WS-Discovery 的程序检查设备。发现结果应与资产清单交叉核对,避免误接入其他网络设备。
2. 无法自动发现时直接连接 IP
生产环境更适合维护明确的设备配置文件。例如:
cameras:
- name: gate-01
host: 192.168.10.21
port: 80
username: recorder
password: ${CAM_GATE_01_PASSWORD}
profile_index: 0
- name: warehouse-02
host: 192.168.10.22
port: 80
username: recorder
password: ${CAM_WAREHOUSE_02_PASSWORD}
profile_index: 1
不要把真实密码提交到 Git 仓库。可以通过环境变量、只允许服务账号读取的配置文件或密钥管理系统注入密码。
3. 校准时间
ONVIF 鉴权可能受到服务器与摄像头时间偏差影响。应让录像服务器和摄像头使用同一个可靠 NTP 来源,并统一确认时区。若出现密码正确但鉴权失败的情况,应优先检查时间误差。
四、批量获取 Media Profile 和 RTSP 地址
1. 查询单台摄像头的媒体配置
下面的 Python 示例连接 ONVIF 服务,列出可用 Profile,并获取对应流地址:
from onvif import ONVIFCamera
host = "192.168.10.21"
port = 80
username = "recorder"
password = "replace-with-secret"
camera = ONVIFCamera(host, port, username, password)
media = camera.create_media_service()
profiles = media.GetProfiles()
for index, profile in enumerate(profiles):
request = media.create_type("GetStreamUri")
request.ProfileToken = profile.token
request.StreamSetup = {
"Stream": "RTP-Unicast",
"Transport": {"Protocol": "RTSP"}
}
uri = media.GetStreamUri(request).Uri
print(index, profile.Name, profile.token, uri)
如果设备仅完整实现 Media2 服务,需使用支持 Media2 的客户端库或直接调用对应 SOAP 接口。不能假设所有厂商对每个 ONVIF 接口的实现完全一致。
2. 选择主码流还是子码流
- 主码流:分辨率和码率较高,适合证据留存、画质分析或训练素材采集。
- 子码流:带宽与存储占用较低,适合预览、运行监控和低成本长期录像。
- 双流采集:主码流用于录像,子码流用于实时检测或健康检查。
选择 Profile 时不要只依赖数组下标。更稳妥的方式是读取每个 Profile 的名称、分辨率、编码和码率,并按规则匹配。设备升级或配置变更后,Profile 顺序可能变化。
3. 处理带认证信息的 RTSP 地址
ONVIF 返回的地址可能是 rtsp://192.168.10.21:554/stream1,FFmpeg 拉流时仍需要认证。可通过参数或临时拼接认证 URI 传入,但不能将含密码的完整地址写入日志。
密码含有 @、:、/、# 等字符时,需要进行 URL 编码。更安全的做法是限制日志权限,并确保进程列表、错误日志和监控平台不会暴露凭据。
五、先用 FFmpeg 验证单路视频流
1. 使用 TCP 方式拉流
ffprobe -v error \
-rtsp_transport tcp \
-show_streams \
"rtsp://recorder:password@192.168.10.21:554/stream1"
输出中应能看到视频编码、分辨率、帧率和音频流信息。常见编码包括 H.264 和 H.265。
2. 录制 60 秒测试文件
ffmpeg -hide_banner -loglevel warning \
-rtsp_transport tcp \
-i "rtsp://recorder:password@192.168.10.21:554/stream1" \
-t 60 -map 0:v:0 -map 0:a? -c copy \
/tmp/gate-01-test.mkv
-c copy 表示不重新编码,可显著降低 CPU 占用。MKV 对异常中断通常更耐受;MP4 在进程异常退出时可能因索引未正常写入而无法播放。若必须输出 MP4,可采用短分段,并在后处理阶段执行封装修复或转换。
3. 检查测试文件
ffprobe -v error \
-show_entries format=duration,size \
-show_entries stream=codec_name,width,height,avg_frame_rate \
-of json /tmp/gate-01-test.mkv
同时人工播放测试文件,检查画面是否花屏、时间是否连续、音画是否同步。只看到文件生成并不能证明录像有效。
六、使用 FFmpeg 自动分段录像
1. 按 10 分钟生成一个文件
mkdir -p /data/recordings/gate-01
ffmpeg -hide_banner -loglevel warning \
-rtsp_transport tcp \
-rw_timeout 15000000 \
-i "rtsp://recorder:password@192.168.10.21:554/stream1" \
-map 0:v:0 -map 0:a? -c copy \
-f segment -segment_time 600 \
-reset_timestamps 1 -strftime 1 \
"/data/recordings/gate-01/%Y%m%d_%H%M%S.mkv"
-map 0:a? 表示音频流可选,可避免无音频摄像头直接报错。-rw_timeout 用于限制网络读写等待时间,但不同 FFmpeg 版本和协议场景下的具体行为可能不同,部署前应模拟断网验证。
2. 理解分段时间误差
在不转码的情况下,分段通常需要落在关键帧附近。因此设置 600 秒后,实际文件时长可能略有偏差。若业务要求严格按整点切片,需要调整摄像头的 GOP 或关键帧间隔,或者执行转码并强制生成关键帧,但后者会增加 CPU 或 GPU 成本。
3. 控制断线后的影响范围
一个 FFmpeg 进程只处理一路摄像头,便于单独重启、限流和排障。不要将大量不稳定的输入全部放入同一个 FFmpeg 进程,否则一路异常可能影响其他录像任务。
七、编写批量录像脚本
1. 推荐的目录结构
/opt/onvif-recorder/
├── config.yaml
├── recorder.py
└── venv/
/data/recordings/
├── gate-01/
└── warehouse-02/
2. 批量启动脚本示例
以下示例假设配置文件已经保存最终 RTSP 地址,密码通过环境变量引用。脚本为每台摄像头启动一个 FFmpeg 子进程,并在异常退出后延迟重试:
import os
import time
import signal
import subprocess
import threading
from pathlib import Path
CAMERAS = [
{
"name": "gate-01",
"url": os.environ["CAM_GATE_01_RTSP"]
},
{
"name": "warehouse-02",
"url": os.environ["CAM_WAREHOUSE_02_RTSP"]
}
]
BASE_DIR = Path("/data/recordings")
STOP = threading.Event()
PROCESSES = {}
def record(camera):
name = camera["name"]
output_dir = BASE_DIR / name
output_dir.mkdir(parents=True, exist_ok=True)
while not STOP.is_set():
command = [
"ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "warning",
"-rtsp_transport", "tcp",
"-rw_timeout", "15000000",
"-i", camera["url"],
"-map", "0:v:0", "-map", "0:a?", "-c", "copy",
"-f", "segment", "-segment_time", "600",
"-reset_timestamps", "1", "-strftime", "1",
str(output_dir / "%Y%m%d_%H%M%S.mkv")
]
process = subprocess.Popen(command)
PROCESSES[name] = process
return_code = process.wait()
PROCESSES.pop(name, None)
if STOP.is_set():
break
print(f"camera={name} ffmpeg_exit={return_code} retry_in=5s")
STOP.wait(5)
def shutdown(signum, frame):
STOP.set()
for process in list(PROCESSES.values()):
process.send_signal(signal.SIGTERM)
signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)
threads = []
for camera in CAMERAS:
thread = threading.Thread(target=record, args=(camera,), daemon=False)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
生产环境可在启动前动态调用 ONVIF 获取 RTSP 地址,并缓存设备 Profile Token。考虑到不同厂商的兼容差异,首次接入时仍应逐台验证,不建议在未核验的情况下直接启动全部设备。
3. 避免快速重启循环
脚本和服务管理器都应设置重试间隔。大规模部署还可采用指数退避,并给不同摄像头增加随机延迟,避免交换机恢复后所有任务同时发起连接。
八、配置开机启动和异常重启
1. 创建 systemd 服务
[Unit]
Description=ONVIF camera batch recorder
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=recorder
Group=recorder
EnvironmentFile=/etc/onvif-recorder.env
ExecStart=/opt/onvif-recorder/venv/bin/python /opt/onvif-recorder/recorder.py
Restart=always
RestartSec=10
TimeoutStopSec=30
KillSignal=SIGTERM
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
将其保存为 /etc/systemd/system/onvif-recorder.service,然后执行:
sudo systemctl daemon-reload
sudo systemctl enable --now onvif-recorder
sudo systemctl status onvif-recorder
journalctl -u onvif-recorder -f
环境变量文件应设置为仅 root 和服务账号可读,例如权限 600。录像目录则只授予录像服务所需的写权限。
2. 验证异常恢复
- 临时断开一台测试摄像头的网络连接。
- 确认对应 FFmpeg 进程在超时后退出。
- 恢复网络,确认脚本自动重连并生成新文件。
- 重启录像服务器,确认服务自动启动。
- 检查其他摄像头是否未受单路故障影响。
九、清理旧录像并估算存储容量
1. 估算录像空间
可使用下面的近似公式:
单路每日容量(GB)≈ 码率(Mbps)× 10.8
总容量(GB)≈ 单路每日容量 × 摄像头数量 × 保留天数
例如,单路平均码率为 4 Mbps,理论上每天约产生 43.2 GB 数据。实际容量还会受到可变码率、音频、封装开销和场景复杂度影响,应根据连续运行样本增加容量余量。
2. 定期删除过期录像
find /data/recordings -type f -name "*.mkv" -mtime +30 -delete
可将清理命令配置为 systemd timer 或定时任务。正式启用前先去掉 -delete 查看匹配结果,确认目录和保留天数无误。
3. 不要等磁盘写满再处理
建议在磁盘使用率达到 70%、80% 和 90% 时设置分级告警。除容量外,还需监控 inode、文件系统只读状态、磁盘 I/O 延迟和录像目录写入失败次数。
十、验证批量录像是否真正可用
1. 进程层验证
- 每台启用的摄像头应对应一个运行中的录像任务。
- 进程异常退出后应在预期时间内重启。
- CPU、内存、网络和磁盘 I/O 不应长期接近系统上限。
2. 文件层验证
find /data/recordings -type f -mmin -15 -size +1M
ffprobe -v error \
-show_entries format=duration,size \
-show_entries stream=codec_name,width,height \
-of json /data/recordings/gate-01/20250101_120000.mkv
应检查文件更新时间、文件大小、时长、视频流数量、分辨率和解码错误。零字节文件或持续不增长的小文件通常意味着连接、鉴权或写盘异常。
3. 内容层抽检
每天或每小时从不同摄像头、不同时间段抽取关键帧,检查黑屏、冻结、花屏、镜头遮挡和时间水印。可以使用 FFmpeg 自动截取缩略图:
ffmpeg -y -ss 30 \
-i /data/recordings/gate-01/20250101_120000.mkv \
-frames:v 1 /tmp/gate-01-check.jpg
4. 连续性验证
记录每个分段文件的开始时间和结束时间,相邻文件之间若存在超出容忍值的间隔,应产生告警。仅统计文件数量不能识别中间缺失、重复录像或时间戳跳变。
十一、选型与规模化落地建议
若录像仅服务于本地安防归档,应优先评估已有 NVR 或视频管理平台是否已经满足权限、回放、检索和审计要求。若采集结果还要用于经授权的算法训练、模型评估或多模态数据项目,可将摄像头采集系统作为数据入口,并建立脱敏、筛选、标注和质量检查流程;需要补充标准数据集、定制数据交付或公开网页与视频平台数据时,也可评估 Dataify 提供的数据集及采集 API 能力。
规模扩展时重点评估
- 网络:总码率、交换机上联带宽、跨网段策略和丢包率。
- 计算:是否仅封装,还是需要转码、抽帧和实时分析。
- 存储:写入吞吐、保留周期、冗余方式和归档策略。
- 兼容性:ONVIF Profile、H.264/H.265、音频编码和厂商扩展。
- 运维:设备台账、凭据轮换、录像连续性监控和故障告警。
- 合规:采集授权、访问控制、隐私遮挡、保存期限和操作审计。
十二、关键注意事项
权限与隐私
只采集已获得明确授权的摄像头视频。涉及人脸、车牌、工作场所或医疗场景时,应根据适用法律法规和内部制度确定告知、授权、脱敏、访问范围及删除周期。
账号安全
为录像程序创建权限受限的独立账号,避免使用摄像头管理员账号。定期轮换密码,限制管理端口来源,并将摄像头网络与办公网络进行适当隔离。
协议兼容
ONVIF 兼容不代表所有接口均可正常使用。应对每个设备型号建立兼容性记录,包括服务端口、Profile、RTSP 传输方式、编码、固件版本和已知异常。
容器部署
若使用 Docker,需确认容器可以访问摄像头网段。WS-Discovery 依赖 UDP 组播,桥接网络下可能无法发现设备;静态 IP 清单通常更稳定。录像目录必须挂载到持久化存储,并正确处理容器停止信号。
常见问题
为什么 ONVIF 能发现摄像头,却无法获取视频流?
常见原因包括 ONVIF 与 RTSP 使用不同账号、RTSP 未启用、Media Profile 不可用、554 端口被防火墙拦截,或返回的流地址只能在摄像头所在网段访问。应先列出 Profile,再用 ffprobe 单独测试返回的 URI。
为什么摄像头在同一网络中仍然无法自动发现?
WS-Discovery 依赖 UDP 3702 和组播。无线隔离、VLAN、交换机组播策略、主机防火墙或多网卡路由都可能阻止发现报文。生产环境建议同时保留静态 IP 接入方式。
RTSP 应该使用 TCP 还是 UDP?
TCP 通常更适合跨交换机、存在轻微丢包或要求录像完整性的场景;UDP 延迟较低,但丢包可能造成花屏。应在实际网络中测试延迟、丢包和重连表现后确定。
为什么设置 10 分钟分段,文件时长却不是精确的 600 秒?
复制视频码流时,FFmpeg 通常在关键帧附近切分,所以分段时长会受到 GOP 长度影响。可缩短摄像头关键帧间隔,或通过转码强制关键帧,但转码会增加计算资源消耗。
MP4 和 MKV 哪种格式更适合无人值守录像?
MKV 对进程崩溃和非正常断电通常更耐受,适合原始分段录像。MP4 的兼容性更广,但异常退出可能导致索引不完整。可先录制为 MKV,再按业务需要转封装为 MP4。
如何判断录像任务表面运行但实际已经卡住?
不能只检查进程是否存在,还要监控最新文件更新时间、文件大小增长、ffprobe 解码结果和抽帧画面。若进程存在但文件长时间不增长,应主动终止并重启该路任务。
批量接入几十台摄像头时,最容易出现什么问题?
常见问题是总带宽估算不足、磁盘持续写入能力不足、所有设备同时重连、凭据管理混乱和不同型号的 ONVIF 兼容差异。建议先接入少量设备进行 24 至 72 小时压力测试,再分批扩容。
总结
ONVIF 批量录像的核心不是直接用 ONVIF 传输视频,而是利用其发现设备、读取 Media Profile 并获取 RTSP 地址,再由 FFmpeg 完成拉流与分段落盘。落地时应先逐台验证账号、端口、Profile 和视频编码,再通过独立进程、自动重启、容量清理和多层健康检查实现稳定运行。最终验收不能只看进程和文件数量,还要检查录像可解码性、时间连续性、抽帧画面、断网恢复以及磁盘告警,并确保采集授权、凭据保护和数据保存策略符合实际业务要求。