Hier ist eine Zusammenfassung meines Setups:
Python 3.10
Flask, der MJPEG unter http://192.168.1.105:8080/feed1.mjpeg bereitstellt
FFmpeg-Konvertierung von MJPEG → RTSP unter rtsp://192.168.1.105:8554/screen
Grundlegende ONVIF-SOAP-Endpunkte (GetCapabilities, GetProfiles, GetVideoSources, GetStreamUri)
WS-Discovery in einem Hintergrundthread implementiert
ODM-Protokolle zeigen einen Absturz mit System.ArgumentNullException in SectionSources.LoadChannel.
Mir ist aufgefallen, dass:
Flask MJPEG-Feed im Browser funktioniert
FFmpeg-Befehl funktioniert, wenn er manuell ausgeführt wird
ODM scheint beim Aufruf von GetProfiles oder GetStreamUri abzustürzen
Hier ist mein vereinfachtes Python-Setup:
Code: Select all
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)
Was könnte zum Absturz von ODM führen, wenn von einem benutzerdefinierten ONVIF-Server auf MJPEG/RTSP-Streams zugegriffen wird?
Gibt es eine empfohlene Möglichkeit, MJPEG/RTSP an ODM bereitzustellen, ohne einen Absturz zu verursachen?
Muss ich die WS-Erkennung oder Stream-Bereitschaft sicherstellen, bevor ODM das Gerät abfragt?
Mobile version