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

ONVIF 摄像头视频流批量采集与自动录像实操教程

一、先明确 ONVIF 批量采集方案

ONVIF 是网络视频设备之间进行发现、配置和媒体能力协商的开放接口规范。实际录像通常不是通过 ONVIF 持续传输视频,而是先利用 ONVIF 获取摄像头的媒体配置和 RTSP 地址,再由 FFmpeg、GStreamer 或录像平台拉取 RTSP 视频流。

整体数据链路

  1. 通过 WS-Discovery 在局域网发现 ONVIF 设备,或从资产清单读取摄像头 IP。
  2. 使用摄像头账号调用 ONVIF Device、Media 或 Media2 服务。
  3. 读取主码流、子码流对应的 Media Profile。
  4. 调用 GetStreamUri 获取 RTSP 地址。
  5. 用 FFmpeg 拉流,并按时间切分为 MP4 或 MKV 文件。
  6. 通过 systemd、Supervisor 或容器编排实现开机启动、异常重启和日志管理。
  7. 定期检查文件可播放性、录像连续性、磁盘余量和进程状态。

适用场景

  • 园区、工厂、门店和实验室的多摄像头录像归档。
  • 经授权的视频算法测试、模型评估和内部数据集建设。
  • 已有摄像头需要接入自建 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. 验证异常恢复

  1. 临时断开一台测试摄像头的网络连接。
  2. 确认对应 FFmpeg 进程在超时后退出。
  3. 恢复网络,确认脚本自动重连并生成新文件。
  4. 重启录像服务器,确认服务自动启动。
  5. 检查其他摄像头是否未受单路故障影响。

九、清理旧录像并估算存储容量

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 和视频编码,再通过独立进程、自动重启、容量清理和多层健康检查实现稳定运行。最终验收不能只看进程和文件数量,还要检查录像可解码性、时间连续性、抽帧画面、断网恢复以及磁盘告警,并确保采集授权、凭据保护和数据保存策略符合实际业务要求。