Anonymous
Сбой в диспетчере устройств Onvi
Сообщение
Anonymous » 05 мар 2026, 12:32
Я создаю сервер ONVIF на базе Python, который осуществляет потоковую передачу рабочего стола с помощью Flask (MJPEG) и FFmpeg (RTSP). Поток MJPEG работает нормально, FFmpeg работает, но диспетчер устройств ONVIF (ODM) дает сбой при попытке доступа к потоку.
Вот краткое описание моей настройки:
Python 3.10
Flask, обслуживающий MJPEG на http://192.168.1.105:8080/feed1.mjpeg
FFmpeg, конвертирующий MJPEG → RTSP по адресу rtsp://192.168.1.105:8554/screen
Базовые конечные точки SOAP ONVIF (GetCapabilities, GetProfiles, GetVideoSources, GetStreamUri)
WS-Discovery, реализованный в фоновом потоке.
Журналы ODM показывают сбой с System.ArgumentNullException в DivisionSources.LoadChannel.
Я заметил, что:
Канал Flask MJPEG работает в браузере.
Команда FFmpeg работает, если ее запустить. вручную
Похоже, что ODM дает сбой при вызове GetProfiles или GetStreamUri
Вот моя упрощенная настройка Python:
Код: Выделить всё
import cv2
import mss
import numpy as np
import threading
import time
import socket
import struct
import uuid
from flask import Flask, Response, request
import subprocess
import select
# ===========================
# CONFIG
# ===========================
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
FPS = 15
MJPEG_PORT = 8080
RTSP_PORT = 8554
RTSP_PATH = "screen"
FFMPEG_EXE = ".\\ffmpeg\\bin\\ffmpeg.exe"
PROFILE_TOKEN = "profile1"
SOURCE_TOKEN = "source1"
ENCODER_TOKEN = "enc1"
PROFILE_NAME = "ScreenCam"
rtsp_url = f"rtsp://192.168.1.105:{RTSP_PORT}/{RTSP_PATH}"
# ===========================
# FLASK MJPEG SERVER
# ===========================
app = Flask(__name__)
def generate_mjpeg():
with mss.mss() as sct:
monitor = {"top": 0, "left": 0, "width": SCREEN_WIDTH, "height": SCREEN_HEIGHT}
while True:
start = time.time()
img = np.array(sct.grab(monitor))
frame = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
ret, jpeg = cv2.imencode('.jpg', frame)
if not ret:
continue
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + jpeg.tobytes() + b'\r\n')
elapsed = time.time() - start
time.sleep(max(0, 1/FPS - elapsed))
@app.route('/feed1.mjpeg')
def mjpeg_feed():
return Response(generate_mjpeg(),
mimetype='multipart/x-mixed-replace; boundary=frame')
# ===========================
# ONVIF SOAP
# ===========================
def create_soap_response(body_content):
message_id = f"uuid:{uuid.uuid4()}"
response = f"""
{message_id}
http://192.168.1.105:{MJPEG_PORT}/onvif/device_service
http://www.w3.org/2005/08/addressing/anonymous
{body_content}
"""
return response
@app.route('/onvif/device_service', methods=['POST'])
def onvif_service():
data = request.data.decode()
print("ONVIF Request:", data[:300].replace("\n", ""))
if "GetCapabilities" in data:
body = f"""
http://192.168.1.105:{MJPEG_PORT}/onvif/device_service
http://192.168.1.105:{MJPEG_PORT}/onvif/device_service
"""
return Response(create_soap_response(body), mimetype='application/soap+xml')
elif "GetProfiles" in data:
body = f"""
{PROFILE_NAME}
{SOURCE_TOKEN}
H264
H264
{SCREEN_WIDTH}
{SCREEN_HEIGHT}
5
"""
return Response(create_soap_response(body), mimetype='application/soap+xml')
elif "GetVideoSources" in data:
body = f"""
{PROFILE_NAME}
{SOURCE_TOKEN}
"""
return Response(create_soap_response(body), mimetype='application/soap+xml')
elif "GetStreamUri" in data:
body = f"""
{rtsp_url}
false
false
PT0S
"""
return Response(create_soap_response(body), mimetype='application/soap+xml')
return Response(create_soap_response("Unknown Request"),
mimetype='application/soap+xml')
# ===========================
# WS-DISCOVERY
# ===========================
def ws_discovery():
multicast_group = ('239.255.255.250', 3702)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('192.168.1.105', 3702))
mreq = struct.pack("=4sl", socket.inet_aton(multicast_group[0]), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
sock.setblocking(0)
while True:
try:
rlist, _, _ = select.select([sock], [], [], 0.5)
if sock in rlist:
try:
data, addr = sock.recvfrom(4096)
except Exception:
continue
if b'Probe' in data:
print(f"WS-Discovery Probe from {addr}")
resp_id = str(uuid.uuid4())
resp = f"""
uuid:{resp_id}
{resp_id}
{addr[0]}
http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches
urn:uuid:{uuid.uuid4()}
dn:NetworkVideoTransmitter
http://192.168.1.105:{MJPEG_PORT}/onvif/device_service
1
"""
sock.sendto(resp.encode(), addr)
except Exception as e:
print("WS-Discovery main loop error:", e)
# ===========================
# FFmpeg MJPEG -> RTSP
# ===========================
def run_ffmpeg():
ffmpeg_cmd = [
FFMPEG_EXE,
"-f", "mjpeg",
"-i", f"http://127.0.0.1:{MJPEG_PORT}/feed1.mjpeg",
"-c:v", "libx264",
"-profile:v", "baseline",
"-level", "3.0",
"-pix_fmt", "yuv420p",
"-preset", "veryfast",
"-tune", "zerolatency",
"-f", "rtsp",
"-rtsp_transport", "tcp",
f"{rtsp_url}"
]
print("Starting RTSP stream...")
subprocess.Popen(ffmpeg_cmd)
# ===========================
# MAIN
# ===========================
if __name__ == "__main__":
flask_thread = threading.Thread(target=lambda: app.run(host='0.0.0.0', port=MJPEG_PORT, threaded=True), daemon=True)
flask_thread.start()
ws_thread = threading.Thread(target=ws_discovery, daemon=True)
ws_thread.start()
time.sleep(2)
run_ffmpeg()
print(" MJPEG:", MJPEG_PORT, "RTSP:", RTSP_PORT)
while True:
time.sleep(1)
Вопросы:
Что может привести к сбою ODM при доступе к потокам MJPEG/RTSP с пользовательского сервера ONVIF?
Существует ли рекомендуемый способ подачи MJPEG/RTSP в ODM, не вызывая сбоя?
Нужно ли мне гарантировать WS-Discovery или готовность потока перед тем, как ODM запрашивает ODM устройство?
Подробнее здесь:
https://stackoverflow.com/questions/798 ... ager-crash
1772703130
Anonymous
Я создаю сервер ONVIF на базе Python, который осуществляет потоковую передачу рабочего стола с помощью Flask (MJPEG) и FFmpeg (RTSP). Поток MJPEG работает нормально, FFmpeg работает, но диспетчер устройств ONVIF (ODM) дает сбой при попытке доступа к потоку. Вот краткое описание моей настройки: [list] [*]Python 3.10 [*]Flask, обслуживающий MJPEG на http://192.168.1.105:8080/feed1.mjpeg [*]FFmpeg, конвертирующий MJPEG → RTSP по адресу rtsp://192.168.1.105:8554/screen [*]Базовые конечные точки SOAP ONVIF (GetCapabilities, GetProfiles, GetVideoSources, GetStreamUri) [*]WS-Discovery, реализованный в фоновом потоке. [/list] Журналы ODM показывают сбой с System.ArgumentNullException в DivisionSources.LoadChannel. Я заметил, что: [list] [*]Канал Flask MJPEG работает в браузере. [*]Команда FFmpeg работает, если ее запустить. вручную [*]Похоже, что ODM дает сбой при вызове GetProfiles или GetStreamUri [/list] Вот моя упрощенная настройка Python: [code]import cv2 import mss import numpy as np import threading import time import socket import struct import uuid from flask import Flask, Response, request import subprocess import select # =========================== # CONFIG # =========================== SCREEN_WIDTH = 1280 SCREEN_HEIGHT = 720 FPS = 15 MJPEG_PORT = 8080 RTSP_PORT = 8554 RTSP_PATH = "screen" FFMPEG_EXE = ".\\ffmpeg\\bin\\ffmpeg.exe" PROFILE_TOKEN = "profile1" SOURCE_TOKEN = "source1" ENCODER_TOKEN = "enc1" PROFILE_NAME = "ScreenCam" rtsp_url = f"rtsp://192.168.1.105:{RTSP_PORT}/{RTSP_PATH}" # =========================== # FLASK MJPEG SERVER # =========================== app = Flask(__name__) def generate_mjpeg(): with mss.mss() as sct: monitor = {"top": 0, "left": 0, "width": SCREEN_WIDTH, "height": SCREEN_HEIGHT} while True: start = time.time() img = np.array(sct.grab(monitor)) frame = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR) ret, jpeg = cv2.imencode('.jpg', frame) if not ret: continue yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + jpeg.tobytes() + b'\r\n') elapsed = time.time() - start time.sleep(max(0, 1/FPS - elapsed)) @app.route('/feed1.mjpeg') def mjpeg_feed(): return Response(generate_mjpeg(), mimetype='multipart/x-mixed-replace; boundary=frame') # =========================== # ONVIF SOAP # =========================== def create_soap_response(body_content): message_id = f"uuid:{uuid.uuid4()}" response = f""" {message_id} http://192.168.1.105:{MJPEG_PORT}/onvif/device_service http://www.w3.org/2005/08/addressing/anonymous {body_content} """ return response @app.route('/onvif/device_service', methods=['POST']) def onvif_service(): data = request.data.decode() print("ONVIF Request:", data[:300].replace("\n", "")) if "GetCapabilities" in data: body = f""" http://192.168.1.105:{MJPEG_PORT}/onvif/device_service http://192.168.1.105:{MJPEG_PORT}/onvif/device_service """ return Response(create_soap_response(body), mimetype='application/soap+xml') elif "GetProfiles" in data: body = f""" {PROFILE_NAME} {SOURCE_TOKEN} H264 H264 {SCREEN_WIDTH} {SCREEN_HEIGHT} 5 """ return Response(create_soap_response(body), mimetype='application/soap+xml') elif "GetVideoSources" in data: body = f""" {PROFILE_NAME} {SOURCE_TOKEN} """ return Response(create_soap_response(body), mimetype='application/soap+xml') elif "GetStreamUri" in data: body = f""" {rtsp_url} false false PT0S """ return Response(create_soap_response(body), mimetype='application/soap+xml') return Response(create_soap_response("Unknown Request"), mimetype='application/soap+xml') # =========================== # WS-DISCOVERY # =========================== def ws_discovery(): multicast_group = ('239.255.255.250', 3702) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('192.168.1.105', 3702)) mreq = struct.pack("=4sl", socket.inet_aton(multicast_group[0]), socket.INADDR_ANY) sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) sock.setblocking(0) while True: try: rlist, _, _ = select.select([sock], [], [], 0.5) if sock in rlist: try: data, addr = sock.recvfrom(4096) except Exception: continue if b'Probe' in data: print(f"WS-Discovery Probe from {addr}") resp_id = str(uuid.uuid4()) resp = f""" uuid:{resp_id} {resp_id} {addr[0]} http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches urn:uuid:{uuid.uuid4()} dn:NetworkVideoTransmitter http://192.168.1.105:{MJPEG_PORT}/onvif/device_service 1 """ sock.sendto(resp.encode(), addr) except Exception as e: print("WS-Discovery main loop error:", e) # =========================== # FFmpeg MJPEG -> RTSP # =========================== def run_ffmpeg(): ffmpeg_cmd = [ FFMPEG_EXE, "-f", "mjpeg", "-i", f"http://127.0.0.1:{MJPEG_PORT}/feed1.mjpeg", "-c:v", "libx264", "-profile:v", "baseline", "-level", "3.0", "-pix_fmt", "yuv420p", "-preset", "veryfast", "-tune", "zerolatency", "-f", "rtsp", "-rtsp_transport", "tcp", f"{rtsp_url}" ] print("Starting RTSP stream...") subprocess.Popen(ffmpeg_cmd) # =========================== # MAIN # =========================== if __name__ == "__main__": flask_thread = threading.Thread(target=lambda: app.run(host='0.0.0.0', port=MJPEG_PORT, threaded=True), daemon=True) flask_thread.start() ws_thread = threading.Thread(target=ws_discovery, daemon=True) ws_thread.start() time.sleep(2) run_ffmpeg() print(" MJPEG:", MJPEG_PORT, "RTSP:", RTSP_PORT) while True: time.sleep(1) [/code] Вопросы: [list] [*]Что может привести к сбою ODM при доступе к потокам MJPEG/RTSP с пользовательского сервера ONVIF? [*]Существует ли рекомендуемый способ подачи MJPEG/RTSP в ODM, не вызывая сбоя? [*]Нужно ли мне гарантировать WS-Discovery или готовность потока перед тем, как ODM запрашивает ODM устройство? [/list] Подробнее здесь: [url]https://stackoverflow.com/questions/79861671/onvif-device-manager-crash[/url]