From 5a7a92b075da808fb1a6cb1263501044fade34f0 Mon Sep 17 00:00:00 2001 From: Jono Targett Date: Mon, 15 May 2023 11:43:15 +0930 Subject: [PATCH] Added customised mediaMTX config file, made streams more robust about subprocess failures --- mediamtx.yml | 246 ++++++++++++++++++++++++++++++++++++++++++++++++ microservice.py | 2 +- radio.py | 18 +++- 3 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 mediamtx.yml diff --git a/mediamtx.yml b/mediamtx.yml new file mode 100644 index 0000000..9965edc --- /dev/null +++ b/mediamtx.yml @@ -0,0 +1,246 @@ + +############################################### +# General parameters + +# Sets the verbosity of the program; available values are "error", "warn", "info", "debug". +logLevel: info +# Destinations of log messages; available values are "stdout", "file" and "syslog". +logDestinations: [stdout] +# If "file" is in logDestinations, this is the file which will receive the logs. +logFile: mediamtx.log + +# Timeout of read operations. +readTimeout: 10s +# Timeout of write operations. +writeTimeout: 10s +# Number of read buffers. +# A higher value allows a wider throughput, a lower value allows to save RAM. +readBufferCount: 512 +# Maximum size of payload of outgoing UDP packets. +# This can be decreased to avoid fragmentation on networks with a low UDP MTU. +udpMaxPayloadSize: 1472 + +# HTTP URL to perform external authentication. +# Every time a user wants to authenticate, the server calls this URL +# with the POST method and a body containing: +# { +# "ip": "ip", +# "user": "user", +# "password": "password", +# "path": "path", +# "protocol": "rtsp|rtmp|hls|webrtc", +# "id": "id", +# "action": "read|publish", +# "query": "query" +# } +# If the response code is 20x, authentication is accepted, otherwise +# it is discarded. +externalAuthenticationURL: + +# Enable the HTTP API. +api: no + +# Enable Prometheus-compatible metrics. +metrics: no + +# Enable pprof-compatible endpoint to monitor performances. +pprof: no + +# Command to run when a client connects to the server. +# This is terminated with SIGINT when a client disconnects from the server. +# The following environment variables are available: +# * RTSP_PORT: server port +runOnConnect: +# Restart the command if it exits suddenly. +runOnConnectRestart: no + +############################################### +# RTSP parameters + +# Disable support for the RTSP protocol. +rtspDisable: no +# List of enabled RTSP transport protocols. +# UDP is the most performant, but doesn't work when there's a NAT/firewall between +# server and clients, and doesn't support encryption. +# UDP-multicast allows to save bandwidth when clients are all in the same LAN. +# TCP is the most versatile, and does support encryption. +# The handshake is always performed with TCP. +protocols: [udp, tcp] +# Encrypt handshakes and TCP streams with TLS (RTSPS). +# Available values are "no", "strict", "optional". +encryption: "no" +# Address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional". +rtspAddress: :8554 +# Address of the TCP/TLS/RTSPS listener. This is needed only when encryption is "strict" or "optional". +rtspsAddress: :8322 +# Address of the UDP/RTP listener. This is needed only when "udp" is in protocols. +rtpAddress: :8000 +# Address of the UDP/RTCP listener. This is needed only when "udp" is in protocols. +rtcpAddress: :8001 +# IP range of all UDP-multicast listeners. This is needed only when "multicast" is in protocols. +multicastIPRange: 224.1.0.0/16 +# Port of all UDP-multicast/RTP listeners. This is needed only when "multicast" is in protocols. +multicastRTPPort: 8002 +# Port of all UDP-multicast/RTCP listeners. This is needed only when "multicast" is in protocols. +multicastRTCPPort: 8003 +# Path to the server key. This is needed only when encryption is "strict" or "optional". +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +serverKey: server.key +# Path to the server certificate. This is needed only when encryption is "strict" or "optional". +serverCert: server.crt +# Authentication methods. +authMethods: [basic, digest] + +############################################### +# RTMP parameters + +# Disable support for the RTMP protocol. +rtmpDisable: yes + +############################################### +# HLS parameters + +# Disable support for the HLS protocol. +hlsDisable: yes + +############################################### +# WebRTC parameters + +# Disable support for the WebRTC protocol. +webrtcDisable: yes + +############################################### +# Path parameters + +# These settings are path-dependent, and the map key is the name of the path. +# It's possible to use regular expressions by using a tilde as prefix. +# For example, "~^(test1|test2)$" will match both "test1" and "test2". +# For example, "~^prefix" will match all paths that start with "prefix". +# The settings under the path "all" are applied to all paths that do not match +# another entry. +paths: + all: + # Source of the stream. This can be: + # * publisher -> the stream is published by a RTSP or RTMP client + # * rtsp://existing-url -> the stream is pulled from another RTSP server / camera + # * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS + # * rtmp://existing-url -> the stream is pulled from another RTMP server / camera + # * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS + # * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server + # * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server with HTTPS + # * udp://ip:port -> the stream is pulled from UDP, by listening on the specified IP and port + # * redirect -> the stream is provided by another path or server + # * rpiCamera -> the stream is provided by a Raspberry Pi Camera + source: publisher + + # If the source is an RTSP or RTSPS URL, this is the protocol that will be used to + # pull the stream. available values are "automatic", "udp", "multicast", "tcp". + sourceProtocol: automatic + + # Tf the source is an RTSP or RTSPS URL, this allows to support sources that + # don't provide server ports or use random server ports. This is a security issue + # and must be used only when interacting with sources that require it. + sourceAnyPortEnable: no + + # If the source is a RTSPS, RTMPS or HTTPS URL, and the source certificate is self-signed + # or invalid, you can provide the fingerprint of the certificate in order to + # validate it anyway. It can be obtained by running: + # openssl s_client -connect source_ip:source_port /dev/null | sed -n '/BEGIN/,/END/p' > server.crt + # openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':' + sourceFingerprint: + + # If the source is an RTSP or RTMP URL, it will be pulled only when at least + # one reader is connected, saving bandwidth. + sourceOnDemand: no + # If sourceOnDemand is "yes", readers will be put on hold until the source is + # ready or until this amount of time has passed. + sourceOnDemandStartTimeout: 10s + # If sourceOnDemand is "yes", the source will be closed when there are no + # readers connected and this amount of time has passed. + sourceOnDemandCloseAfter: 10s + + # If the source is "redirect", this is the RTSP URL which clients will be + # redirected to. + sourceRedirect: + + # If the source is "publisher" and a client is publishing, do not allow another + # client to disconnect the former and publish in its place. + disablePublisherOverride: no + + # If the source is "publisher" and no one is publishing, redirect readers to this + # path. It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. + fallback: + + + # Username required to publish. + # SHA256-hashed values can be inserted with the "sha256:" prefix. + publishUser: + # Password required to publish. + # SHA256-hashed values can be inserted with the "sha256:" prefix. + publishPass: + # IPs or networks (x.x.x.x/24) allowed to publish. + publishIPs: [] + + # Username required to read. + # SHA256-hashed values can be inserted with the "sha256:" prefix. + readUser: + # password required to read. + # SHA256-hashed values can be inserted with the "sha256:" prefix. + readPass: + # IPs or networks (x.x.x.x/24) allowed to read. + readIPs: [] + + # Command to run when this path is initialized. + # This can be used to publish a stream and keep it always opened. + # This is terminated with SIGINT when the program closes. + # The following environment variables are available: + # * RTSP_PATH: path name + # * RTSP_PORT: server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnInit: + # Restart the command if it exits suddenly. + runOnInitRestart: no + + # Command to run when this path is requested. + # This can be used to publish a stream on demand. + # This is terminated with SIGINT when the path is not requested anymore. + # The following environment variables are available: + # * RTSP_PATH: path name + # * RTSP_PORT: server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnDemand: + # Restart the command if it exits suddenly. + runOnDemandRestart: no + # Readers will be put on hold until the runOnDemand command starts publishing + # or until this amount of time has passed. + runOnDemandStartTimeout: 10s + # The command will be closed when there are no + # readers connected and this amount of time has passed. + runOnDemandCloseAfter: 10s + + # Command to run when the stream is ready to be read, whether it is + # published by a client or pulled from a server / camera. + # This is terminated with SIGINT when the stream is not ready anymore. + # The following environment variables are available: + # * RTSP_PATH: path name + # * RTSP_PORT: server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnReady: + # Restart the command if it exits suddenly. + runOnReadyRestart: no + + # Command to run when a clients starts reading. + # This is terminated with SIGINT when a client stops reading. + # The following environment variables are available: + # * RTSP_PATH: path name + # * RTSP_PORT: server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnRead: + # Restart the command if it exits suddenly. + runOnReadRestart: no diff --git a/microservice.py b/microservice.py index 3850743..a6e12fa 100755 --- a/microservice.py +++ b/microservice.py @@ -157,7 +157,7 @@ if __name__ == '__main__': rtsp_relay = subprocess.Popen( [ './dependencies/mediamtx/mediamtx', - './dependencies/mediamtx/mediamtx.yml' + './mediamtx.yml' ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL diff --git a/radio.py b/radio.py index cfc69d3..151a55d 100644 --- a/radio.py +++ b/radio.py @@ -48,7 +48,7 @@ class Radio: return (self.thread and self.thread.is_alive()) def start_stream(self): - if self.thread: + if self.is_streaming(): raise RuntimeError('Stream thread is already running') self.run = True @@ -66,12 +66,20 @@ class Radio: def _stream_thread(self): self._init_stream() + def is_alive(subprocess): + return (subprocess.poll() is None) + while self.run: + # Check that the child processes are still running + if (not is_alive(self.demod)) or (not is_alive(self.playback)): + print('DSP chain error, aborting stream.', file=sys.stderr) + break + result = self.device.readStream(self.stream, [self.buffer], Radio.SAMPLES) if result.ret < 1: - print('Stream read failed, exiting.', file=sys.stderr) - self.keep_going = False + print('Stream read failed, aborting stream.', file=sys.stderr) + break read_size = int(result.ret * 2) self.demod.stdin.write( @@ -85,7 +93,7 @@ class Radio: self.playback = subprocess.Popen( [ '/usr/bin/ffmpeg', '-f', 's16le', '-ar', '32000', '-ac', '2', '-i', '-', - '-f', 'rtsp', f"rtsp://localhost:{Radio.PORT}/radio/{self.name}/stream" + '-f', 'rtsp', f"rtsp://localhost:{Radio.PORT}/radio/{self.name}" ], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, @@ -104,6 +112,8 @@ class Radio: self.device.activateStream(self.stream) def _cleanup_stream(self): + self.run = False + # Cleanup the streaming objects in the reverse order to the _init method. self.device.deactivateStream(self.stream) self.device.closeStream(self.stream)