2025年9月2日 星期二

IPCAM_server vs NVR 比對

 差異重點

 Flask「ipcam server」是輕量掃描+轉播(RTSP→MJPEG)的工具;

NVR是專用錄影與管理系統

面向Flask ipcam serverNVR
目的掃描 ONVIF、即時監看、臨時預覽24/7 錄影、管理、查詢與回放
儲存無內建錄影;需另接 FFmpeg/GStreamer內建持續/事件錄影、循環覆寫、保留策略
回放/檔案管理無時間軸、索引、事件標記時間軸回放、事件檢索、快照、剪輯、匯出
事件/AI無;需自行串接移動偵測、人形/車牌、事件規則與通知
穩定性走 OpenCV 軟轉 MJPEG,CPU 吃重;多路易爆針對多路設計,支援硬體解碼與多串流
高可用無 HA/熱備常見 RAID、ANR(斷網續錄)、叢集/備援
裝置管理僅列出 IP、拉流通道管理、使用者/權限、健檢、韌體分發
網路/PoE不供電、不交換多數機型內建 PoE 交換機與供電
合規/稽核無水印、無稽核浮水印、審計、日誌、法規報表
成本/彈性成本低、可程式化成本較高,但一體化與穩定度高

何時用哪個

  • 用 Flask 版:實驗、現場勘測、開發驗證、臨時上牆。

  • 用 NVR:正式場域、長期留存、多人權限管理、要事件告警與穩定回放。

若你要把 Flask 版往「準 NVR」靠攏,最低限度需加:

  1. 循環錄影:FFmpeg 分段 -f segment -segment_time 300 -reset_timestamps 1;檔案輪替與清理。

  2. 索引與時間軸:把片段與事件寫 DB(SQLite/Postgres),提供 /api/playback?from=..&to=..

  3. HLS/LL-HLS 輸出:讓瀏覽器原生播放,不靠 MJPEG。

  4. 健康檢查與自動重連:監看每路狀態,失敗即重拉。

  5. 權限與審計:JWT/LDAP、操作日誌、匯出簽章。


IPCAM_server

 直接跑的單檔 Flask 伺服器:

功能:1) 本機 webcam MJPEG 監看、2) 週期性 WS-Discovery 掃描 ONVIF 裝置、3) 清單 API、4) 指定 IP 的 RTSP → MJPEG 轉播。

需求

pip install flask opencv-python-headless onvif-zeep

環境變數(可選)

  • WEBCAM_INDEX:本機攝影機索引,預設 0

  • ONVIF_USER / ONVIF_PASS:若要自動取 RTSP URI

  • RTSP_TEMPLATE:未能自動取 URI 時的模板
    例:rtsp://{user}:{pwd}@{ip}:554/Streaming/Channels/101

程式 app.py

<ipcam_server_app.py  >

啟動

export ONVIF_USER=admin export ONVIF_PASS=123456 # 如需:export RTSP_TEMPLATE='rtsp://{user}:{pwd}@{ip}:554/live/ch0' python3 ipcam_server_app.py

瀏覽 http://<伺服器IP>:8000/

注意

  • 瀏覽器無法原生播 RTSP,程式以 OpenCV 拉流並轉成 MJPEG。多路會吃 CPU。

  • 若 VLAN 阻擋多播,掃描清單會是空的。需允許 UDP/3702。

  • 若裝置 RTSP 需特定路徑,設定 RTSP_TEMPLATE 或提供 ONVIF 帳密讓程式自動查 URI。

WS-Discovery(Web Services Discovery)網路協定

 WS-Discovery(Web Services Discovery)是一種網路協定,主要目的是讓裝置或服務能在本機網路上自動被發現,而不需要使用者手動輸入IP位址或其他設定資訊。

你可以把它想像成一個「尋找服務的廣播系統」。當一個新裝置(例如IP攝影機、印表機或智慧型家電)連上網路後,它會自動發送廣播訊息,宣告自己的存在;而其他裝置(例如NVR主機或電腦)則會監聽這些廣播,或主動發出「尋找」的廣播,來發現這些新裝置。


WS-Discovery 的運作原理

WS-Discovery 的運作基於 多點傳播(Multicast) 技術,這是一種一對多的通訊方式。它的核心流程如下:

  1. 探測(Probe): 尋找服務的客戶端(如您的NVR主機)會透過多點傳播位址,發送一個探測訊息(Probe Message),廣播給同一個區域網路上的所有裝置。這個探測訊息通常會包含它想要尋找的服務類型。

  2. 回應(ProbeMatch): 區域網路上的所有裝置收到這個探測訊息後,如果發現自己提供了客戶端正在尋找的服務,就會直接向該客戶端單獨發送一個回應訊息(ProbeMatch Message),告知自己的服務資訊,例如IP位址、服務名稱等。

  3. 上線(Hello)與離線(Bye): 此外,裝置在首次上線時,也會主動發送一個「Hello」訊息,宣告自己的存在;當裝置正常關機或離線時,則會發送「Bye」訊息,告知其他裝置自己即將離線。

透過這種廣播和回應的機制,裝置之間可以動態地互相發現,不需要依賴一個中央伺服器來管理所有裝置的清單。


在監控領域的應用

WS-Discovery 在監控領域中扮演著非常關鍵的角色,特別是在 ONVIF(開放網路視訊介面論壇) 協定中。

當您將一台支援 ONVIF 的IP攝影機接入網路後,它會自動發送 WS-Discovery 廣播。接著,您的NVR主機(如果它也支援 ONVIF)會自動探測到這台攝影機,並將其顯示在裝置清單中,讓您可以一鍵加入,省去了手動輸入攝影機IP位址的繁瑣步驟。

簡單來說,WS-Discovery 讓「隨插即用」(Plug and Play)在網路監控攝影機上得以實現,大幅簡化了安裝和設定的過程。


WS-Discovery 概要

  • 定義:基於 SOAP over UDP 的裝置自動發現協定。用於同網段的零設定探索。

  • 傳輸:UDP/3702,多播至 239.255.255.250:3702(IPv4)。回覆通常為單播。

  • 物件:以 SOAP 訊息封裝 XML。核心元素為 TypesScopesXAddrs

    • Types:服務類型,例如 ONVIF 常用 dn:NetworkVideoTransmitter

    • Scopes:字串標籤,如 onvif://www.onvif.org/name/StoreCam1.../location/Lobby

    • XAddrs:可連線服務的 URL(如 http://IP:80/onvif/device_service)。

訊息種類

  • Hello:裝置上線廣播。

  • Bye:裝置下線廣播。

  • Probe:用戶端發出「誰符合某 Types/Scopes?」的查詢(多播)。

  • ProbeMatches:裝置回覆,提供 XAddrs

  • Resolve / ResolveMatches:已知 EPR 位址時解析詳細資訊(較少用)。

ONVIF 中的流程

  1. Client 多播 Probe(Types= dn:NetworkVideoTransmitter)。

  2. IPCam 單播 ProbeMatches,內含 XAddrs

  3. Client 對 XAddrs(通常 http://IP:80/onvif/device_service)建立 SOAP 連線。

  4. 呼叫 GetCapabilitiesMedia.GetProfilesGetStreamUri 取得 RTSP。

網路與相容性重點

  • 只保證同 L2 網段可用。跨 VLAN/子網需多播路由或代理。

  • 交換器啟用 IGMP Snooping 時,需正確的 IGMP Querier 才不會丟多播。

  • 防火牆需放行 UDP/3702 多播與單播回覆。

  • 預設 TTL=1。若有跨子網需求,提升 TTL 並配置路由端支援多播。

  • 無加密與驗證。只用於發現,不用於敏感控制。

除錯與測試

  • Wireshark 篩選:udp.port == 3702

  • 常見失敗原因:

    • 不同網段或 Wi-Fi AP 隔離多播 → 看不到回覆。

    • Windows/Linux 防火牆擋 UDP/3702。

    • 裝置未啟用 ONVIF 或關閉 WS-Discovery。

實作提示(Python)

  • 發送多播:設 IP_MULTICAST_TTLIP_MULTICAST_IF,送出 Probe。

  • 接收回覆:用 recvfrom 收單播,解析 XML 找 XAddrs 抽 IP。

  • 取得串流:對 XAddrs 走 ONVIF Media 服務拿 RTSP,再交給 ffplay/OpenCV。

適用場景

  • 初次佈署 IPCam/NVR 時的自動掃描。

  • 動態環境上線/下線通知(靠 Hello/Bye)做即時資源表更新。


Python 腳本:輸入 IP/帳密,列出所有 Profile 的 RTSP 與快照 URL

 一支可直接用的 Python 腳本:輸入 IP/帳密,列出所有 Profile 的 RTSP 與快照 URL。

依序嘗試取得 Profile S/T。無外部播放器相依,只用 ONVIF API。


安裝:

pip install onvif-zeep

腳本 onvif_get_rtsp.py

#!/usr/bin/env python3 # -*- coding: utf-8 -*- # 列出 ONVIF 裝置的 RTSP 與 Snapshot URI import argparse, sys from onvif import ONVIFCamera from zeep.exceptions import Fault def connect(ip, port, user, pwd): try: cam = ONVIFCamera(ip, port, user, pwd) return cam except Exception: # 某些環境需手動指定 WSDL 目錄 try: import pkg_resources wsdl_dir = pkg_resources.resource_filename('onvif', 'wsdl') cam = ONVIFCamera(ip, port, user, pwd, wsdl_dir=wsdl_dir) return cam except Exception as e: print(f"連線失敗:{e}") sys.exit(2) def get_device_info(cam): try: dev = cam.create_devicemgmt_service() info = dev.GetDeviceInformation() return f"{info.Manufacturer} {info.Model} FW:{info.FirmwareVersion} SN:{info.SerialNumber}" except Exception: return "(無法讀取裝置資訊)" def list_streams(cam): media = cam.create_media_service() try: profiles = media.GetProfiles() except Fault as e: print(f"讀取 Profiles 失敗:{e}") sys.exit(3) results = [] for p in profiles: token = p.token name = getattr(p, 'Name', token) # RTSP rtsp = "" try: req = media.create_type('GetStreamUri') req.ProfileToken = token req.StreamSetup = {'Stream': 'RTP-Unicast', 'Transport': {'Protocol': 'RTSP'}} rtsp = media.GetStreamUri(req).Uri or "" except Fault: rtsp = "" # Snapshot(若支援) snap = "" try: req2 = media.create_type('GetSnapshotUri') req2.ProfileToken = token snap = media.GetSnapshotUri(req2).Uri or "" except Fault: snap = "" results.append((name, token, rtsp, snap)) return results def main(): ap = argparse.ArgumentParser(description="ONVIF RTSP/Snapshot URI 列表") ap.add_argument("--ip", required=True, help="裝置 IP") ap.add_argument("--user", required=True, help="使用者名稱") ap.add_argument("--pwd", required=True, help="密碼") ap.add_argument("--port", type=int, default=80, help="ONVIF 服務埠,預設 80") args = ap.parse_args() cam = connect(args.ip, args.port, args.user, args.pwd) print("裝置資訊:", get_device_info(cam)) rows = list_streams(cam) if not rows: print("未找到任何 Profile") sys.exit(4) print("\nProfiles 與串流:") for i, (name, token, rtsp, snap) in enumerate(rows, 1): print(f"{i:02d}. Name={name} Token={token}") print(f" RTSP : {rtsp or '(無)'}") print(f" Snapshot: {snap or '(無)'}") if __name__ == "__main__": main()

用法:

python3 onvif_get_rtsp.py --ip 192.168.1.50 --user admin --pwd 123456 --port 80

後續直接用 ffplay 監看:

ffplay -rtsp_transport tcp "rtsp://USER:PASS@IP:554/由上面取得的PATH"

註記:

  • 多數 IPC 的 RTSP 埠為 554。ONVIF 回的 URI 若不含帳密,需在 URI 前段補 user:pass@

  • 少數品牌把 ONVIF 服務放在 8899、8000 等,改 --port

  • 若沒有 Snapshot URI,代表裝置未實作或需特定 Profile。

掃描內網的所有[ONVIF] 的設備IP

 作法:用 WS-Discovery 在 239.255.255.250:3702 發 Probe,蒐集回覆的 XAddrs,再抽出 IP。下例為純 Python3,無外部套件。

#!/usr/bin/env python3 # scan_onvif.py import socket, uuid, time, re, xml.etree.ElementTree as ET MCAST_GRP = ('239.255.255.250', 3702) TIMEOUT_S = 3.0 # 掃描等待時間 IFACE_IP = '' # 綁定本機介面IP;留空自動 PROBE = f'''<?xml version="1.0" encoding="utf-8"?> <e:Envelope xmlns:e="http://www.w3.org/2003/05/soap-envelope" xmlns:w="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:dn="http://www.onvif.org/ver10/network/wsdl"> <e:Header> <w:MessageID>uuid:{uuid.uuid4()}</w:MessageID> <w:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To> <w:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action> </e:Header> <e:Body> <d:Probe> <d:Types>dn:NetworkVideoTransmitter</d:Types> <d:Scopes/> </d:Probe> </e:Body> </e:Envelope>'''.encode('utf-8') def parse_xaddrs(xml_bytes): try: # 去掉可能的垃圾字元 m = re.search(b'<\\?xml|<e:Envelope', xml_bytes) if m: xml_bytes = xml_bytes[m.start():] ns = { 'e': 'http://www.w3.org/2003/05/soap-envelope', 'd': 'http://schemas.xmlsoap.org/ws/2005/04/discovery', 'a': 'http://schemas.xmlsoap.org/ws/2004/08/addressing', } root = ET.fromstring(xml_bytes) for x in root.iter(): if x.tag.endswith('XAddrs'): return x.text or '' except Exception: pass return '' def extract_ips(xaddrs): ips = set() for url in re.findall(r'https?://([^/]+)', xaddrs): host = url.split(':')[0] if re.match(r'^\d{1,3}(\.\d{1,3}){3}$', host): ips.add(host) return ips def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) sock.settimeout(0.5) # 允許廣播與多播 sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) if IFACE_IP: sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(IFACE_IP)) sock.bind((IFACE_IP, 0)) # 發送多播 Probe 多次提高命中 for _ in range(3): sock.sendto(PROBE, MCAST_GRP) found = {} t0 = time.time() while time.time() - t0 < TIMEOUT_S: try: data, (addr, port) = sock.recvfrom(65535) xaddrs = parse_xaddrs(data) ips = extract_ips(xaddrs) or {addr} for ip in ips: if ip not in found: found[ip] = {'from': addr, 'xaddrs': xaddrs} except socket.timeout: continue except Exception: continue if not found: print('未發現 ONVIF 設備') return print('發現的 ONVIF 設備:') for i, (ip, meta) in enumerate(sorted(found.items()), 1): print(f'{i:02d}. IP={ip} reply_from={meta["from"]}') if meta['xaddrs']: print(f' XAddrs: {meta["xaddrs"]}') else: print(' XAddrs: (無)') if __name__ == '__main__': main()

使用:

python3 scan_onvif.py

輸出:列出每台裝置的 IP 與回覆的 XAddrs。
若你要進一步拿 RTSP URL,需對每台 IP 執行 ONVIF GetProfiles/GetStreamUri(多數設備需要帳密)。


需要我補一支後續查詢的腳本(輸入 IP/帳密 → 列出 RTSP)嗎?

ONVIF 通訊協定說明

 ONVIF 通訊協定說明

  • 定義:ONVIF (Open Network Video Interface Forum) 是一個開放標準,定義網路攝影機、NVR、VMS 之間的互通協定。

  • 目的:解決不同品牌設備之間的相容問題,讓 IPCAM、NVR、VMS 能透過統一的 SOAP / XML-based Web Services API 溝通。

  • 常見 Profile

    • Profile S:IP 攝影機基本串流(RTSP、影像、音訊、事件)。

    • Profile G:錄影、儲存與回放控制。

    • Profile T:支援 H.265/H.264 影像壓縮,含 metadata。

    • Profile M:AI / Metadata 傳輸(人臉、車牌等辨識)。


應用於串流視訊的流程

  1. 設備發現 (Device Discovery)

    • 使用 WS-Discovery (UDP multicast)。

    • 可透過 onvif 工具或 SDK 掃描區域網路上的 ONVIF 設備。

  2. 能力查詢 (GetCapabilities)

    • 確認設備支援的 Profile(S/T/G/M)。

  3. 媒體設定 (Media Service)

    • 呼叫 GetProfiles() 取得 Profile token。

    • 呼叫 GetStreamUri() 取得 RTSP URL,例如:

      rtsp://192.168.1.50:554/Streaming/Channels/101
  4. 串流播放

    • 使用 VLC、FFmpeg、GStreamer、OpenCV 直接拉 RTSP 串流。


Python 範例(使用 onvif-zeep + OpenCV)

from onvif import ONVIFCamera import cv2 # 連接攝影機 cam = ONVIFCamera('192.168.1.50', 80, 'admin', 'password') # 取得媒體服務 media = cam.create_media_service() # 取得 profiles profiles = media.GetProfiles() token = profiles[0].token # 取得 RTSP 串流 URI stream_uri = media.GetStreamUri({'StreamSetup': {'Stream': 'RTP-Unicast', 'Transport': {'Protocol': 'RTSP'}}, 'ProfileToken': token}) print("RTSP URL:", stream_uri.Uri) # 使用 OpenCV 播放串流 cap = cv2.VideoCapture(stream_uri.Uri) while True: ret, frame = cap.read() if not ret: break cv2.imshow("ONVIF Stream", frame) if cv2.waitKey(1) == 27: # ESC 退出 break cap.release() cv2.destroyAllWindows()

要不要我再給你一個 FFmpeg 或 GStreamer 指令範例,用於命令列直接串流播放/錄影?


Linux (FFmpeg ) Video Play URL

============================

以下指令可直接套用。把 USER:PASS@IP:554/PATH 換成你的攝影機 RTSP 位址。

播放(監看)

ffplay -rtsp_transport tcp -stimeout 5000000 \ "rtsp://USER:PASS@IP:554/PATH"

說明:TCP 穩定;-stimeout=5,000,000µs 逾時。

原碼錄影(不重編,最省 CPU)

# 儲存成 MKV(建議容器) ffmpeg -rtsp_transport tcp -i "rtsp://USER:PASS@IP:554/PATH" \ -c copy -map 0 -f matroska rec.mkv # 或儲存成 TS(方便切片) ffmpeg -rtsp_transport tcp -i "rtsp://USER:PASS@IP:554/PATH" \ -c copy -map 0 -f mpegts rec.ts

說明:-c copy 不轉碼;RTSP→MP4 可能索引不穩,改用 MKV/TS。

轉碼並壓到固定碼率(H.264)

ffmpeg -rtsp_transport tcp -i "rtsp://USER:PASS@IP:554/PATH" \ -c:v libx264 -preset veryfast -tune zerolatency -b:v 4M -maxrate 4M -bufsize 8M \ -c:a aac -b:a 128k -movflags +faststart out.mp4

轉 HLS(HTTP 播放)

ffmpeg -rtsp_transport tcp -i "rtsp://USER:PASS@IP:554/PATH" \ -c:v libx264 -preset veryfast -g 50 -sc_threshold 0 \ -c:a aac -b:a 128k \ -f hls -hls_time 6 -hls_list_size 10 -hls_flags delete_segments \ hls/out.m3u8

說明:-g 50≈50fps 的 1 秒 GOP;依實際 FPS 調整。

轉推 RTMP(例如 YouTube/自架 nginx-rtmp)

ffmpeg -rtsp_transport tcp -i "rtsp://USER:PASS@IP:554/PATH" \ -c:v libx264 -preset veryfast -tune zerolatency -b:v 4M -maxrate 4M -bufsize 8M \ -c:a aac -b:a 128k -f flv "rtmp://RTMP_SERVER/app/stream_key"

低延遲監看參數組

ffplay -fflags nobuffer -flags low_delay -probesize 32k -analyzeduration 0 \ -rtsp_transport tcp "rtsp://USER:PASS@IP:554/PATH"

週期性截圖

# 每 5 秒一張 ffmpeg -rtsp_transport tcp -i "rtsp://USER:PASS@IP:554/PATH" \ -vf fps=1/5 -q:v 2 shots/frame-%06d.jpg

斷線自動重連(錄影)

ffmpeg -reconnect 1 -reconnect_streamed 1 -reconnect_at_eof 1 \ -rtsp_transport tcp -i "rtsp://USER:PASS@IP:554/PATH" \ -c copy -f mpegts rec.ts

備註

  • IF-5301 多為 H.265/H.264。若拉 H.265 串流且播放器不支援,改 -c:v libx264 轉碼

  • 有聲音就保留 -c:a aac;沒麥克風可加 -an 關閉音訊。

  • ONVIF GetStreamUri() 拿到的 URL 直接套上述指令即可



2025年8月28日 星期四

小雞幼雛育雛條件

 小雞幼雛的最佳溫度會隨著週齡的增加而下降,一般第一週建議在34-35°C 之間,之後每週逐漸降低約2-3°C,直到環境溫度適合成雞的20°C 左右。 若溫度過低,小雞會擠在一起;若過高,則會張口喘氣,飼養者可觀察小雞的行為來判斷並及時調整。 

各週齡最佳溫度參考: 
  • 1-3 天:34-35°C
  • 4-7 天:32-33°C
  • 第2 周:30-28°C
  • 第3 周:28-26°C
  • 以後:每週平穩下調2-3°C,直至室溫20°C 左右。
如何判斷溫度是否合適: 
  • 溫度過低:小雞會聚集在熱源下,發出持續的叫聲。
  • 溫度過高:小雞會張口喘氣,頭頸伸長。
其他注意事項:
  • 保持通風:在保持溫度的同時,也要確保良好的通風換氣,避免氨氣積聚造成健康問題。 
  • 調整溫度:溫度應根據雞群的行為、季節、氣候等因素進行靈活調整。 
  • 提供飲水和飼料:雛雞進入育雛舍後,應先飲用溫水,再自由採食飼料